Käyttöliittymä (oliosuuntautunut ohjelmointi)

Kokeneet kirjoittajat eivät ole vielä tarkistaneet sivun nykyistä versiota, ja se voi poiketa merkittävästi 7. joulukuuta 2017 tarkistetusta versiosta . tarkastukset vaativat 27 muokkausta .

Käyttöliittymä ( englanninkielinen  käyttöliittymä ) - ohjelma/syntaksirakenne, joka määrittää suhteen objekteihin, joita yhdistää vain jokin käyttäytyminen. Luokkia suunniteltaessa rajapinnan suunnittelu on sama kuin spesifikaation suunnittelu (metodijoukko, joka jokaisen rajapintaa käyttävän luokan tulee toteuttaa).

Liitännät yhdessä abstraktien luokkien ja protokollien kanssa muodostavat keskinäisiä velvoitteita ohjelmistojärjestelmän elementtien välille, mikä on sopimusohjelmoinnin ( eng.  design by contract , DbC) perusta. Liitäntä määrittää vuorovaikutusrajan luokkien tai komponenttien välillä määrittämällä tietyn abstraktion , jonka toteuttaja toteuttaa.

OOP :n käyttöliittymä on tiukasti formalisoitu oliokielen elementti ja sitä käytetään laajalti ohjelmien lähdekoodissa.

Liitännät mahdollistavat objektien usean perimisen ja samalla ratkaisevat vinoneliön muotoisen perinnön ongelman . C++-kielessä se ratkaistaan ​​luokkaperinnön kautta käyttämällä virtual.

Liitäntöjen kuvaus ja käyttö

OOP-rajapinnan kuvaus koostuu tiettyjen kielten syntaksien yksityiskohtien lisäksi kahdesta osasta: käyttöliittymän nimestä ja menetelmistä .

Liitäntöjä voidaan käyttää kahdella tavalla:

Pääsääntöisesti olio-ohjelmointikielissä rajapinnat, kuten luokat, voivat periytyä toisiltaan. Tässä tapauksessa lapsirajapinta sisältää kaikki esi-isärajapinnan menetelmät ja valinnaisesti lisää niihin omat menetelmänsä.

Siten toisaalta rajapinta on "sopimus", jonka toteuttava luokka sitoutuu täyttämään, toisaalta rajapinta on tietotyyppi, koska sen kuvaus määrittelee riittävän selkeästi objektien ominaisuudet, jotta se voidaan kirjoittaa. muuttujat tasa-arvoisesti luokan kanssa. On kuitenkin syytä korostaa, että rajapinta ei ole täydellinen tietotyyppi, koska se määrittelee vain objektien ulkoisen käyttäytymisen. Rajapinnan määrittelemän käyttäytymisen sisäisen rakenteen ja toteutuksen tarjoaa rajapinnan toteuttava luokka; siksi ei ole olemassa "rajapinta-ilmentymiä" sen puhtaassa muodossa, ja mikä tahansa "rajapinta"-tyyppinen muuttuja sisältää konkreettisten luokkien esiintymiä.

Liitäntöjen käyttö on yksi vaihtoehto polymorfismin tarjoamiseksi objektikielissä ja ympäristöissä. Kaikki luokat, jotka toteuttavat saman rajapinnan määrittämänsä käyttäytymisen suhteen, käyttäytyvät samalla tavalla ulkoisesti. Näin voit kirjoittaa yleisiä tietojenkäsittelyalgoritmeja, jotka käyttävät rajapintaparametreja tyyppeinä ja soveltavat niitä erityyppisiin objekteihin, jolloin saadaan joka kerta vaadittu tulos.

Esimerkiksi " " käyttöliittymä Cloneablevoi kuvata objektien kloonauksen abstraktiota (tarkkojen kopioiden luomista) määrittämällä menetelmän " Clone", jonka pitäisi kopioida objektin sisältö toiseen samantyyppiseen objektiin. Sitten minkä tahansa luokan, jonka objektit on ehkä kopioitava, tulee toteuttaa käyttöliittymä Cloneableja tarjota menetelmä Clone, ja missä tahansa ohjelmassa, jossa objektin kloonaus vaaditaan, menetelmä kutsutaan objektiin tätä tarkoitusta varten Clone. Lisäksi tätä menetelmää käyttävässä koodissa tarvitsee olla vain käyttöliittymän kuvaus, se ei välttämättä tiedä mitään varsinaisesta luokasta, jonka objektit kopioidaan. Siten rajapintojen avulla voit hajottaa ohjelmistojärjestelmän moduuleiksi ilman keskinäistä koodiriippuvuutta.

Liitännät ja abstraktit luokat

Voidaan nähdä, että rajapinta on muodollisesta näkökulmasta vain puhdas abstrakti luokka , eli luokka, jossa ei ole määritelty mitään muuta kuin abstrakteja menetelmiä . Jos ohjelmointikieli tukee useita periytyviä ja abstrakteja menetelmiä (kuten esimerkiksi C++ ), ei ole tarvetta lisätä erillistä "rajapinnan" käsitettä kielen syntaksiin. Nämä entiteetit kuvataan käyttämällä abstrakteja luokkia, ja luokat perivät ne abstraktien menetelmien toteuttamiseksi.

Moniperinnön tukeminen kokonaisuudessaan on kuitenkin melko monimutkaista ja aiheuttaa monia ongelmia sekä kielen toteutustasolla että sovellusarkkitehtuurin tasolla. Liitäntöjen käsitteen käyttöönotto on kompromissi, jonka avulla voit saada monia moniperinnön etuja (erityisesti mahdollisuuden määritellä kätevästi loogisesti toisiinsa liittyvät menetelmäjoukot luokkamaisina kokonaisuuksina, jotka mahdollistavat perinnän ja toteutuksen) ilman toteutusta. sen kokonaan ja siten ilman useimpia siihen liittyviä vaikeuksia.

Suoritustasolla klassinen moniperinnön järjestelmä aiheuttaa useita lisähaittoja:

Kaaman käyttäminen liitäntöjen kanssa (moniperinnön sijaan) välttää nämä ongelmat, lukuun ottamatta liitäntämenetelmien kutsumista (eli virtuaaliset menetelmäkutsut useissa perinnöissä, katso edellä). Klassinen ratkaisu on (esimerkiksi JVM : ssä Javalle tai CLR :ssä C#:lle), että rajapintamenetelmiä kutsutaan vähemmän tehokkaalla tavalla, ilman virtuaalitaulukon apua: jokaisella kutsulla määritetään ensin tietty objektiluokka, ja sitten siitä etsitään haluttu menetelmä (tietysti lukuisilla optimoinnilla).

Useita perintö- ja käyttöliittymätoteutuksia

Tyypillisesti ohjelmointikielet sallivat käyttöliittymän perimisen useilta esi-isiltä. Kaikista esi-isien liitännöissä ilmoitetuista menetelmistä tulee osa lapsirajapinnan ilmoitusta. Toisin kuin luokkaperinnössä, rajapintojen moninkertainen periytyminen on paljon helpompi toteuttaa, eikä se aiheuta merkittäviä vaikeuksia.

Yksi törmäys rajapintojen usean periytymisen ja useiden rajapintojen toteuttamisen kanssa yhden luokan toimesta on kuitenkin edelleen mahdollinen. Se tapahtuu, kun kahdella tai useammalla uuden rajapinnan perimällä tai luokan toteuttamalla rajapinnalla on menetelmiä, joilla on sama allekirjoitus. Ohjelmointikielten kehittäjät joutuvat valitsemaan tällaisissa tapauksissa tietyt menetelmät ristiriitojen ratkaisemiseksi. Tässä on useita vaihtoehtoja: toteutuskielto, nimenomainen osoitus tietystä ja perusrajapinnan tai -luokan toteutus.

Liitännät tietyillä kielillä ja järjestelmillä

Liitäntöjen toteutus määräytyy pitkälti kielen alkuominaisuuksien ja käyttötarkoituksen mukaan, jota varten siihen liitännät tuodaan. Java- , Object Pascal- , Delphi- ja C++ -rajapintojen käyttöominaisuudet ovat hyvin suuntaa-antavia , koska ne osoittavat kolme pohjimmiltaan erilaista tilannetta: kielen alkusuuntaus käyttämään rajapintojen käsitettä, niiden käyttö yhteensopivuuden varmistamiseen ja niiden emulointi luokkiittain.

Delphi

Delphiin otettiin käyttöön liitännät tukemaan Microsoftin COM - tekniikkaa . Kuitenkin, kun Kylix julkaistiin , rajapinnat kielen elementtinä erotettiin COM-tekniikasta. Kaikki liitännät periytyvät [1] -liitännästä , joka win32-alustalla on sama kuin samanniminen standardi COM-liitäntä, aivan kuten kaikki sen luokat ovat luokan jälkeläisiä . IUnknown:n eksplisiittinen käyttö esi-isänä on varattu COM-tekniikkaa käyttävälle koodille. IInterface IUnknownTObject

Esimerkki käyttöliittymän ilmoituksesta:

IMyInterface = käyttöliittymämenettely DoSomething ; _ loppu ;

Jotta liitäntöjen toteutus voidaan ilmoittaa, luokan kuvauksessa on määritettävä niiden nimet suluissa avainsanan classjälkeen esi-isäluokan nimen jälkeen. Koska "rajapinta on sopimus, joka on täytettävä", ohjelma ei käänny ennen kuin se on toteutettu toteutusluokassaprocedure DoSomething;

Edellä mainittu Delphi-rajapintojen keskittyminen COM-tekniikkaan on johtanut joihinkin hankaluuksiin. Tosiasia on, että rajapinta IInterface(josta kaikki muut liitännät peritään) sisältää jo kolme menetelmää, jotka ovat pakollisia COM-liitännöille: QueryInterface, _AddRef, _Release. Siksi minkä tahansa rajapinnan toteuttavan luokan on toteutettava nämä menetelmät, vaikka ohjelman logiikan mukaan rajapinnalla ja luokalla ei olisi mitään tekemistä COM:n kanssa. On huomattava, että näitä kolmea menetelmää käytetään myös kohteen eliniän ohjaamiseen ja liitäntäpyyntömekanismin toteuttamiseen asoperaattorin " " kautta.

Esimerkki luokasta, joka toteuttaa rajapinnan:

TMyClass = luokka ( TMyParentClass , IMyInterface ) - menettely DoSomething ; toiminto QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; stdcall ; function _AddRef : Kokonaisluku ; stdcall ; function _Release : Integer ; stdcall ; loppu ; toteutus

Ohjelmoijan tulee toteuttaa oikein menetelmät QueryInterface, _AddRef, _Release. Vakiomenetelmien kirjoittamistarpeen poistamiseksi tarjotaan kirjastoluokka TInterfacedObject - se toteuttaa edellä mainitut kolme menetelmää, ja mikä tahansa luokka, joka perii siitä ja sen jälkeläisistä, saa tämän toteutuksen. Näiden menetelmien toteutus TInterfacedObjectolettaa objektin käyttöiän automaattisen hallinnan laskemalla viittauksia _AddRefja -menetelmien kautta _Release, joita kutsutaan automaattisesti laajuudelta tullessa ja poistettaessa.

Esimerkki luokan perillisestä TInterfacedObject:

TMyClass = luokan ( TInterfacedObject , IMyInterface ) - menettely DoSomething ; loppu ;

Periessään rajapinnan toteuttavaa luokkaa luokasta, jossa ei ole rajapintoja, ohjelmoijan tulee toteuttaa mainitut menetelmät manuaalisesti määrittämällä viitteiden laskentaohjauksen olemassaolo tai puuttuminen sekä hankkimalla rajapinta sisään QueryInterface.

Esimerkki mielivaltaisesta luokasta ilman viitteiden laskemista:

TMyClass = luokka ( TObject , IIInterface , IMyInterface ) // IIrajapintatoiminto QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; stdcall ; function _AddRef : Kokonaisluku ; stdcall ; function _Release : Integer ; stdcall ; //IMyInterface- menettely DoSomething ; loppu ; { TmyClass } toiminto TMyClass . QueryInterface ( const IID : TGUID ; out Obj ) : HResult ; begin if GetInterface ( IID , Obj ) then Result := 0 else Tulos := E_NOINTERFACE ; loppu ; toiminto TMyClass . _AddRef : Kokonaisluku ; alkaa Tulos := - 1 ; loppu ; toiminto TMyClass . _Release : Integer ; alkaa Tulos := - 1 ; loppu ; menettely TMyClass . DoSomething ; begin //Tee jotain lopeta ;

C++

C++ tukee useita periytyviä ja abstrakteja luokkia , joten, kuten edellä mainittiin, erillistä syntaktista rakennetta tämän kielen liitäntöille ei tarvita. Liitännät määritellään abstraktien luokkien avulla ja rajapinnan toteutus tapahtuu perimällä näistä luokista.

Esimerkki käyttöliittymän määrittelystä :

/** * käyttöliittymä.Openable.h * */ #ifndef INTERFACE_OPENABLE_HPP #define INTERFACE_OPENABLE_HPP // iAvattava käyttöliittymäluokka. Määrittää, voidaanko jotain avata/sulkea. luokka iAvaa { julkinen : virtuaalinen ~ iOpenable (){} virtuaalinen tyhjiö auki () = 0 ; virtuaalinen tyhjä sulkeminen () = 0 ; }; #loppu Jos

Rajapinta toteutetaan periytymisen kautta ( moniperinnön olemassaolon vuoksi voidaan tarvittaessa toteuttaa useita rajapintoja samassa luokassa ; alla olevassa esimerkissä periytyminen ei ole useita):

/** * luokka.Ovi.h * */ #include "interface.openable.h" #include <iostream> luokan ovi : julkinen iAvattava { julkinen : Ovi (){ std :: cout << "Oviobjekti luotu" << std :: endl ;} virtuaalinen ~ Ovi (){} //IOpenable-rajapintamenetelmien kasvattaminen Door-luokan virtuaaliselle void open (){ std :: cout << "Ovi auki" << std :: endl ;} virtual void close (){ std :: cout << "Ovi kiinni" << std :: endl ;} //Oviluokkakohtaiset ominaisuudet ja menetelmät std :: string mMaterial ; std :: merkkijono mColor ; //... }; /** * luokka.Kirja.h * */ #include "interface.openable.h" #include <iostream> luokka Kirja : julkinen iAvaa { julkinen : Kirja (){ std :: cout << "Kirjaobjekti luotu" << std :: endl ;} virtuaalinen ~ Kirja (){} //IOpenable-rajapintamenetelmien lisääminen Book-luokan virtuaaliselle void open (){ std :: cout << "Kirja avattu" << std :: endl ;} virtual void close (){ std :: cout << "Kirja suljettu" << std :: endl ;} //Kirjakohtaiset ominaisuudet ja menetelmät std :: string mTitle ; std :: merkkijono mAuthor ; //... };

Testataan kaikki yhdessä:

/** * test.openable.cpp * */ #include "interface.openable.h" #include "luokka.Ovi.h" #include "luokka.kirja.h" //Toiminto kaikkien heterogeenisten objektien avaamiseen/sulkemiseen, jotka toteuttavat iOpenable-liittymän void openAndCloseSomething ( iOpenable & smth ) { jtk _ auki (); jtk _ sulje (); } int main () { Ovi myDoor ; BookmyBook ; _ openAndCloseSomething ( myDoor ); openAndCloseSomething ( myBook ); system ( "tauko" ); paluu 0 ; }

Java

Toisin kuin C++, Java ei salli useamman kuin yhden luokan perimistä. Vaihtoehtona moniperinnölle on olemassa rajapintoja. Jokainen Java- luokka voi toteuttaa mitä tahansa rajapintoja. Objekteja ei ole mahdollista johtaa Java -rajapinnoilla.

Rajapintailmoitukset

Rajapintailmoitus on hyvin samanlainen kuin yksinkertaistettu luokkailmoitus.

Se alkaa otsikolla. Muokkaajat luetellaan ensin . Liitännäksi voidaan ilmoittaa public, jolloin se on julkisessa käytössä, tai käyttöoikeusmuuttaja voidaan jättää pois, jolloin rajapinta on käytettävissä vain sen tyypeille . Liitäntämuuntajaa abstractei tarvita, koska kaikki rajapinnat ovat abstrakteja luokkia . Se voidaan määrittää, mutta sitä ei suositella, jotta .

interfaceSeuraavaksi kirjoitetaan avainsana ja käyttöliittymän nimi.

Tätä voi seurata avainsana extendsja luettelo liitännöistä, joista ilmoitettu käyttöliittymä perii . Vanhempatyyppejä (luokkia ja/tai rajapintoja) voi olla monia - pääasia, että toistoja ei ole ja että periytymissuhde ei muodosta syklistä riippuvuutta.

Käyttöliittymän periytyminen on todellakin erittäin joustavaa. Jos siis on kaksi rajapintaa Aja B, ja Bse on peritty osoitteesta A, uusi käyttöliittymä Cvoidaan periä molemmista. On kuitenkin selvää, että kun peritään osoitteesta B, perinnän osoittaminen kohteesta Aon redundantti, koska kaikki tämän rajapinnan elementit peritään jo rajapinnan B kautta.

Tämän jälkeen käyttöliittymän runko kirjoitetaan hakasulkeisiin.

Esimerkki käyttöliittymän määrittelystä (Virhe Colorable- ja Resizable-luokissa: Tyyppi Colorable ja Resizable ei voi olla Drawablen superliitäntä; superliitännän on oltava käyttöliittymä):

julkinen käyttöliittymä Piirrettävä laajenee värillinen , kokoa muutettava { }

Käyttöliittymän runko koostuu elementtien määrittelystä eli kentistä - vakioista ja abstrakteista menetelmistä . Kaikki käyttöliittymäkentät ovat automaattisesti public final static, joten nämä muokkaimet ovat valinnaisia ​​ja jopa ei-toivottuja, jotta koodi ei sotkeudu. Koska kentät ovat lopullisia, ne on alustettava heti .

julkinen käyttöliittymä Ohjeet { int OIKEA = 1 ; int VASEN = 2 ; int UP = 3 ; int ALAS = 4 ; }

Kaikki käyttöliittymämenetelmät ovat public abstract, ja myös nämä modifioinnit ovat valinnaisia.

julkinen käyttöliittymä Siirrettävä { void moveRight (); tyhjä siirto Vasen (); void moveUp (); void moveDown (); }

Kuten näet, käyttöliittymän kuvaus on paljon yksinkertaisempi kuin luokkailmoitus.

Käyttöliittymän toteutus

Rajapinnan toteuttamiseksi se on määritettävä luokkailmoituksessa käyttämällä implements. Esimerkki:

käyttöliittymä I { void interfaceMethod (); } public class ImplementingInterface toteuttaa I { void interfaceMethod () { System . ulos . println ( "Tämä menetelmä on toteutettu rajapinnasta I" ); } } public static void main ( String [] args ) { ImplementingInterface temp = new ImplementingInterface (); temp . käyttöliittymämenetelmä (); }

Jokainen luokka voi toteuttaa mitä tahansa saatavilla olevia rajapintoja. Samanaikaisesti kaikki rajapinnoista tai yläluokasta periytyessään esiintyneet abstraktit menetelmät tulee toteuttaa luokassa , jotta uusi luokka voidaan julistaa ei-abstraktiksi.

Jos menetelmät, joilla on sama allekirjoitus , on peritty eri lähteistä , riittää, että kuvataan toteutus kerran, ja sitä sovelletaan kaikkiin näihin menetelmiin. Jos niillä on kuitenkin eri palautusarvo, syntyy ristiriita. Esimerkki:

käyttöliittymä A { int getValue (); } käyttöliittymä B { double getValue (); } käyttöliittymä C { int getValue (); } public class Oikea toteuttaa A , C // luokka perii oikein menetelmät samalla allekirjoituksella { int getValue () { return 5 ; } } class Väärä toteuttaa A , B // luokka antaa käännösaikavirheen { int getValue () { return 5 ; } double getValue () { return 5.5 ; } }

C#

C# : ssa liitännät voivat periä yhdeltä tai useammalta muulta rajapinnalta. Käyttöliittymän jäsenet voivat olla menetelmiä, ominaisuuksia, tapahtumia ja indeksoijia:

rajapinta I1 { void Method1 (); } rajapinta I2 { void Method2 (); } rajapinta I : I1 , I2 { void Method (); int Count { get ; } event EventHandler SomeEvent ; merkkijono tämä [ int index ] { get ; asettaa ; } }

Rajapintaa toteutettaessa luokan tulee toteuttaa sekä itse rajapinnan menetelmät että sen perusrajapinnat:

julkinen luokka C : I { public void Method () { } public int Count { get { throw new NotImplementedException (); } } julkinen tapahtuma EventHandler SomeEvent ; julkinen merkkijono this [ int index ] { get { throw new NotImplementedException (); } set { heittää uusi NotImplementedException (); } } public void Menetelmä1 () { } public void Menetelmä2 () { } }

Liitännät UML:ssä

UML :n rajapintoja käytetään visualisoimaan, määrittämään, rakentamaan ja dokumentoimaan UML-telakointisolmuja järjestelmän komponenttien välillä. Tyypit ja UML-roolit tarjoavat mekanismin abstraktion staattisen ja dynaamisen kartoituksen mallintamiseen rajapintaan tietyssä kontekstissa.

UML:ssä rajapinnat on kuvattu luokkina , joissa on stereotyyppi "rajapinta", tai ympyröinä (tässä tapauksessa käyttöliittymän sisältämiä UML-toimintoja ei näytetä).

Katso myös

Muistiinpanot

Linkit