Személyes eszközök
Keresés

 

A InfoWiki wikiből

A lap korábbi változatát látod, amilyen WikiSysop (vita | szerkesztései) 2009. szeptember 3., 20:57-kor történt szerkesztése után volt.
(eltér) ←Régebbi változat | Aktuális változat (eltér) | Újabb változat→ (eltér)




Tartalomjegyzék

Interface

Az öröklődés, és ezzel kapcsolatosan a típuskompatibilitás kapcsán volt már szó olyan technikákról, amelyek segítségével próbálunk különböző objektumosztályokat egyforma szemmel nézni és kezeleni. Ennek során megállapítottuk, hogy kereshető olyan típus, amellyel annak gyerekosztályai mindíg kompatibilisek, és ha olyan metódust fejlesztünk, amely ilyen típusú paramétert fogad - akkor minden gyerekosztályának példányait is képes az fogadni és kezelni.

Ez a szemlélet nagyon hasznos, mivel a típusbiztonságot a fordítóprogram ellenőrzi, és kódolás szempontjából pedig nagyon hatékony megoldás. Ezt a szemléletet próbáljuk kiterjeszteni a következőkben is.

Tegyük fel, hogy szeretnénk egy olyan univerzális rendezőalgoritmust kifejleszteni, amelynek átadható több alaptípusra épülő vektor, és ezen rendezőalgoritmus mindegyikkel képes lesz dolgozni. Mit igényel ez a rendezőalgoritmus? A vektor alaptípusára végrehajtható legyen az összehasonlítás valamilyen formában, vagyis eldönthető legyen két ilyen alaptípusú példányról, hogy melyik a kisebb, melyik a nagyobb, illetve egyenlők-e. Másra gyakorlatilag nincs is szükség.

Ezen összehasonlítást általában operátorokkal szoktuk megoldani ('<', '<=', '>', '>=', '=='), de tegyük fel hogy nem kérjük ezen típusra az összehasonlító operátorok kidolgozottságát, mindössze annyit kérünk, hogy rendelkezzen ezen alaptípus egy 'CompareTo(...)' függvénnyel, melyen keresztül le tudjuk kérdezni az összehasonlítás eredményét:

class AlapTipus
{
    // térjen vissza -1  -el,  ha this < b
    // térjen vissza  0  -val, ha this == b
    // térjen vissza +1  -el,  ha this > b
    public int CompareTo( AlapTipus b )
    {
       ...
    }
}

Igazából minden olyan objektumosztállyal tud a rendezőalgoritmus dolgozni, amely rendelkezik ilyen nevű, paraméterezésű, és viselkedésű metódussal. De melyik típussal írjuk ezt le? Létrehozhatnánk egy 'Rendezheto' nevű absztrakt osztályt, amely tartalmazza ezt a metódust, és rávehetnénk minden objektumosztályt, amely használni akarja a fenti metódust, hogy legyen ezen osztály gyerekosztálya:


// az alaposztály
abstract class Rendezheto
{
  public int CompareTo( Rendezheto b );
}
 
// a metódus váza
public static Rendezes( Rendezheto[] adatok )
{
  ..
}
 
// például a String osztály
class String:Rendezheto
{
  public override int CompareTo( String b )
  {
    ... a kód ...
  }
}

A fenti esetben például egy 'String[]' vektor esetén használhatnánk a 'Rendezes' metódust, hiszen a paraméterezése kompatibilis.

De ez a kérés nagyon nehezen terjeszthető ki széles körre. Ha ugyanis az az objektum-osztály, amelyet ősként szeretnénk választani - nem a 'Rendezheto' osztály gyermekosztálya, akkor a mi fejlesztendő osztályunk már nem is válhat azzá. Például tegyük fel, hogy a 'Kor' osztályból szeretnénk származtatni egy 'Ellipszis' osztályt, de a 'Kor' osztály ősei között nem szerepel a 'Rendezheto'. Ugyanakkor szeretnénk az 'Ellipszis'-ekből egy vektort készíteni, és a fenti hatékony rendezőeljárás segítségével rendezni azokat. Mi szivesen írnánk ilyen 'CompareTo' metódust az 'Ellipszis' osztályunk belsejébe, mondjuk azon szempont szerint, hogy az ellipszisek területe lenne a kisebb-nagyobb-egyenlő kérdés döntő szempontja. De hiába tennénk ezt meg - a 'Rendezes' eljárás nem fogadná paraméterként az 'Ellipszis[]' vektort, mivel annak típusa nem kompatibilis a 'Rendezheto' osztállyal. Ráadásul egyetlen osztálynak csak egyetlen őse lehet, tehát nem írhatjuk le az 'Ellipszis' osztályhoz, hogy őse a 'Kor' és őse a 'Rendezhető' osztály is.

Interface

Igazából tehát a típuskompatibilitás nagyon jó dolog, de nem ad minden problémára megoldást. Nagyon gyenge kiterjeszthetősége, hiszen egy osztálynak csak egy őse lehet. Ugyanakkor a fenti esetben ez egyszerűen túl nagy kérés a szóban forgó alaptípustól. Nem kívánjuk valójában hogy őse legyen ez a bizonyos 'Rendezheto' osztály, csak azt kívánjuk, hogy legyen ilyen 'CompareTo(...)' metódusa.

A fenti problémát helyesen nem is így kell kezelni. Nem abstract osztályt kell létrehozni erre a célra, hanem ennek gyakorlatilag a mását - egy 'interface'-t:

// az alaposztály
interface IRendezheto
{
  int CompareTo( Rendezheto b );
}
 
class String:IRendezheto
{
  public override int CompareTo( String b )
  {
    ... a kód ...
  }
}

Az 'interface' az gyakorlatilag nem más, mint egy teljesen letisztult absztrakt osztály. Egy ilyen 'interface' tartalmazhat:

  • "metódus fejrészét": visszatérési érték típusa, metódusnév, formális paraméterlista
  • "property": a property típusát, nevét, és a 'get' és 'set' részek jelölését
  • "indexelő property": hasonlóan a közönséges property-hez


Mit nem tartalmazhat egy interface:

  • metódus törzset: csak a metódusok fejrészét kell leírni
  • a metódusok nem jelölhetők meg védelmi szintekkel ('public', 'protected', 'private')
  • a metódusok nem jelölhetőek meg 'abstract' sem 'virtual' sem 'override' jelzésekkel
  • a property-k hasonlóan nem jelölhetőek védelmi szintekkel, sem egyéb módosítókkal
  • nem tartalmazhat mezők deklarációját!
  • nem tartalmazhat sem konstruktort, sem destruktort


Az 'interface' ennek megfelelően hasonlít egy absztakt osztályra, de valójában esélytelen tőle érdemi dolgot örökölni. Csakis fejrészeket tartalmazhat, kódot és mezőket semmilyen formában nem.

Ugyanakkor az 'interface' is típusnak minősül, ennek megfelelően lehet ilyen típusú változókat,paramétereket deklarálni:


// a metódus váza
public static Rendezes( IRendezheto[] adatok )
{
  ..
}

De ki lehet kompatibilis egy interface típussal? Hát továbbra is az a válasz, hogy aki ősnek választja!

Minden objektumosztálynak csak egy közvetlen másik objektumosztály-őse lehet. De mindegyiknek lehet számtalan másik interface őse!

class Ellipszis: Kor, IRendezheto
{
  ..
}

Amennyiben egy osztály őseként megad egy interface-t, azzal azt a "kötelezettséget" vállalja magára, hogy az 'interface'-ben megjelölt metódusok és propertyk mindegyikét kidolgozza. Amennyiben egyről is "elfedkezne", a forditóprogram keményen figyelmezteti az osztályt (szintaktikai hiba), és megtagadja annak lefordítását!

Amennyiben egy osztály őseként választ egy interface-t, és megvalósítja az abban deklarált összes metódust, property-t, indexelőt, akkor azt mondjuk, hogy az osztály implementálta az adott interface-t.

Ha egy osztály őséül jelöl meg egy interface-t, akkor ezzel annak implementációját vállalja magára. Ha valamely, az interface-ben jelölt metódust, property-t, indexelőt nem dolgozna ki, akkor azt a fordítóprogram hibának tekinti, és megtagadja az adott osztály fordítását. Ekkor az alábbiak közül választhatunk:

  • kidolgozzuk a hiányzó elemet is, és az interface implementálását befejezzük.
  • eltávolítjuk az interface-t az ősök listájáról.
  • a hiányzó metódust, indexelőt vagy property-t csak abstract formában vesszük be az osztályba. Ekkor az implementálást a gyerekosztály nyakába "sózzuk". Ekkor természetesen az osztály maga is absztrakt jelzésűvé kell váljon.

Ha egy osztály valamely interface-t implementálni szeretne, és az abban jelölt metódusokat, property-ket, indexelőket már eleve tartalmazza, vagy az ősosztályától azokat eleve örökölte, akkor a kötelezettségeket eleve teljesíti. Az interface implementációja mindössze annyit ír elő, hogy az adott osztály tartalmazza a szóban forgó elemeket, de ez a tartalmazás oly módon is teljesítettnek tekintendő, hogy ezeket az elemeket (vagy azok egy részét) az ős osztálytól öröklés miatt tartalmazza.

Az interface-ben deklarált metódusok, indexelők, property-k az implementáló osztályban

  • kötelezően public védelmi szinttel kell rendelkezzenek
  • hogy virtuális (késői kötésű), vagy egyszerű (korai kötésű) módon kerülnek kidolgozásra, arról az interface nem rendelkezik külön.

Interface-k öröklődése

Az 'interface' tehát típusnak minősül, és ugyanazon típuskompatibilitási szabály vonatkozik rá, mint a teljes értékű osztályok esetén: az interface-t implementáló (az interface gyerekosztályai) osztályok típuskompatibilisek a szóban forgó interface-el. Ennek megfelelően interface-ek esetére is működik az 'is' és 'as' operátorok a megfelelő módokon.

Az 'interface'-ek nem csak ebben hasonlítanak az osztályokra, hanem abban is, hogy az interface-eknek is lehet ősük. Egy interface-nek azonban csak másik interface lehet őse, 'osztály' nem.

interface IRajzolhato
{
  void Kirajzol();
  void Letorol();
  int xKoord
  {
    get;
    set;
  }
}
 
interface IFestheto:IRajzolhato
{
  Color kifestoSzin
  {
    get;
    set;
  }
}

A fenti példában az 'IFestheto' interface őse az 'IRajzolhato' interface. Az öröklődés itt is hasonlóan működik mint osztályok esetén, tehát az az osztály, aki implementálni szeretné az 'IFestheto' metódust, annak ki kell dolgoznia a 'Kirajzol()', a 'Letorol()' metódusokat, az 'Xkoord' property-t, és a 'kifestoSzin' property-t is egy időben. Ugyanakkor ha ennek eleget tesz, akkor egyszerre lesz kompatibilis az 'IFestheto' és az 'IRajzolhato' interface-el is!


Interface és az Object

Mivel az 'interface'-el gyakorlatilag új típust hozunk létre, felmerül ezen új típus kompatibilitási problémája az Object típussal.

A kérdés körbejárása nem egyszerű. Ugyanis az interface őse nem lehet objektumosztály, és az 'Object' az egy 'class', vagyis objektumosztály. E miatt megdőlni látszik az a korábbi kijelentés, hogy a C#-ban minden létező típus valamilyen szinten kompatibilis az Object típussal.

Ez a kijelentés ebben a formában igaz, az interface típusoknak nem őse az 'Object', e miatt nem kompatibilisek vele.

Másrészről azonban ez a kérdés gyakorlatilag nem merül fel semmilyen formában. Ugyanis az 'interface' típusból példányosítani nem lehet, hiszen ő nem tartalmaz konstruktort, és amúgy sem minősül objektumosztálynak. Példányosítani csak osztályokból lehet. Ha ezen osztály kompatibilis egy interface-el (implementálta azt), akkor bekerülhet a példánya egy ilyen interface típusú változóba:

public void Mozgatas_X(int eltolas_X, IRajzolhato g)
{
  g.Letorol();
  g.xKooord += eltolas_X;
  g.Kirajzol();
  if (g is Ellipszis) (g as Ellipszis).Sugar2--;
}

Ezen metódus meghívása olyan példánnyal lehetséges, amely egy olyan objektumosztály példánya, amely implementálta az 'IRajzolhato' interface-t. Ekkor az 'if (g is Ellipszis)' vizsgálat során nem azt teszteljük, hogy az 'IRajzolhato' kompatibilis-e az 'Ellipszis' osztállyal, hanem a 'g' változóban lévő példány dinamikus típusa kompatibilis-e ezzel az osztállyal. Tehát magának az interface-nek mint típusnak a kompatibilitási kérdései nem merülnek fel.

Interface származtatás

Egy interface-nek nem lehet objektum-osztály őse, mivel akkor örökölhetne, s így tartalmazhatna mezőket, kidolgozott metódusokat, stb. Azonban interface őse lehet másik interface, így az interface-k között is értelmezve van a gyermek-ős viszony.

Itt is érvényben van, hogy a gyermek-interface az ősétől örökli az abban deklarált metódusok, property-k, indexelők a gyermek-interface-be is belekerülnek deklarációs szinten. Vagyis ha valamely osztály ezen gyermek-interface-t próbálja implementálni, akkor ezeket a property-ket, indexelőket, metódusokat is implementálnia kell.

Egy interface-nek több más interface is lehet egyszerre őse.

interface IBeallithato
{
    string ertek
    {
        get;
        set;
    }
}
 
interface IKiirhato
{
    void Kiiras();
}
 
interface INovelheto : IBeallithato, IKiirhato
{
    void Noveles();
}

A fenti példának megfelelően amely osztály implementálni szeretné az 'INovelheto' interface-t, annak implementálnia kell az 'ertek' property-t, a 'Kiiras()' és 'Noveles()' metódusokat.

Interface fogalmát nem ismerő OOP nyelvek

Egyetlen objektumosztálynak csak egyetlen másik objektumosztály lehet az őse, de több interface-t is implementálhat egy időben:

class Ellipszis: Kor, IKirajzolhato, IRendezheto, IArchivalhato
{
   ...
}

Más OOP nyelveken az interface fogalma teljes mértékben hiányzik. Helyette azonban ezen nyelveken egyetlen objektumosztálynak több őse is lehet. Itt az interface helyett valós absztrakt osztályokat szoktak készíteni, és az tölti be az interface szerepét - vagyis hogy az amúgy különböző osztályok közötti hasonlóságot leírja. Ilyen nyelv pl. maga a C++.

Hernyák Zoltán
A lap eredeti címe: „http://wiki.ektf.hu/wiki/Mp3/ea19
Nézetek
nincs sb_3.133.87.156 cikk