abstract class vs interface

 ( uid_7086 | 2018. május 4., péntek - 18:37 )

Valamelyik blog posztban merészeltem megkérdezni, hogy miért is jó, ha interface-t használnak abstract class helyett, mire a kedves szerző amolyan mucsai paraszt stílusban elküldött, hogy nyissak neki saját topic-ot.
O.K., itt van (őt meg külön megkérem, hogy tartsa távol magát ettől a topic-tól :D)

Naszóval... nem tudom, mely nyelvekben van mindkettő, melyekben nincs egyik sem, Pythonról tudom egyedül, hogy az utóbbi kategória.
Amelyik nyelvben van mindkettő, azokban miért nem elég csak az abstract class?
Valaki korábban felvetette, hogy nincs minden nyelvben többszörös öröklődés, ezt helyettesítik az interface-ekkel, ez eddig rendben van, ez egy elfogadható indok. Eset kipipálva.

Viszont kérdés, hogy én emlékszem rosszul vagy tényleg van olyan nyelv, amiben van mindkettő és van többszörös öröklődés is? Előbbi esetben a topic értelmét vesztette, a válasz megvan, utóbbiban viszont marad a kérdés, mire jó ilyen esetben az interface?
Az ilyenek, mint https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem - ha jól értem a leírását, ugyanúgy érvényesek akkor is, ha interface-t használunk, ami tudtommal annyiban különbözik az abstract class-tól, hogy egyetlen metódusa sincs implementálva.

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Mert tök másra való a kettő. Az interface gyakorlatilag egy szerződést ír le. Aki megvalósítja az adott interface-t, az garantáltan
bír az interfaceben deklarált metódusokkal. Ez a szép és tiszta dolog.

Az absztrakt osztály egy olyan osztály, amiből nem lehet példányosítani, viszont vannak bizonyos metódusai, amiket a belőle származó
osztályok megvalósítanak.

Bizonyos nyelvekben nincs többszörös öröklődés, viszont lehetőség van több interface-t is megvalósítani.

Mire használjuk? Teljesen másra.

Ha interface helyett konkrét osztályokkal dolgoznál, akkor teljesen hozzá vagy kötve az osztály implementációjához. Az interface az ez ellen
véd. Ha én egy listán végig akarok iterálni, akkor baromira nem érdekel, hogy az most láncolt lista, vagy arraylist, vagy micsoda. Nekem elég
az interface, a többit meg oldja meg az osztály maga belül. Aztán ha az én kódomat használó láncolt lista helyett mégiscsak arraylist-et akar
átadni, a kódom továbbra is működni fog.

Absztrakt osztály esetén már van kód, de azt akarom, hogy egy bizonyos rész cserélhető legyen. Ilyenkor leszármaztatok az ősosztályból, és a
plusz kódot ott fogom implementálni. Igy nem kell duplikálnom az erdeti kódot mindkét osztályba (most más kérdés, hogy ezt máshogy is meg tudtam volna tenni).

De! Ezeket a kérdéseket miért nem a facebook-on teszed fel a programozók csoportban? Mert inkább oda lenne való.

Fészbúkon...

Absztrakt osztály: elvileg nem arra való, de lehet úgy használni, mint egy interface-t, amennyire még emlékszem, ezért a kérdés.

Ha csinálsz egy olyan absztrakt osztályt, aminek csak absztrakt metódusai vannak, akkor az lényegében egy
interface, ha erre gondolsz.

A probléma az, hogy az interface egy alapvetően teljesen jó nyelvi és architekturális elem. Én a magam
részéről az abstract osztályok jelenlétét a kódban code smell kategóriába sorolom (nem mintha nálunk nem
lenne, de attól még nem tetszik).

Hogy miért facebook?

https://www.facebook.com/groups/408156912634809/

Parancsolj, van itt 11 ezer ember. Jóval esélyesebb, hogy találsz olyat, aki a kérdésedre szívesen ír
egy kimerítő választ, mint a hup-on, ahol az olvasók jó része rendszergazda.

Fészbúk... ember, fészbúúk...
(A hup rw és wo közönségének úgy saccolom, jó, ha tíz százaléka rendszergarázda)

Tudod mit, akkor jó szenvedést.

Mi van ebben szenvedés? Nem vagyok fejlesztő, pythonnál mélyebben nem hiszem, hogy valaha is belemegyek a dolgokba.
Láttam valamit, gondoltam, ez lazán kapcsolódik hozzá, rákérdeztem, a poszternek nem tetszett, kiraktam ide.
Ennyi. Ha valódi problémám lenne, ami komoly dologhoz kell, ahhoz nem itt kérnék segítséget.

Viszont feltételezem, hogy már másnak is megfordult a fejében, hogy pl ezek ketten miért vannak egymás mellett akkor is, ha az egyik kiváltható a másikkal.

Jól látod. Szinte mindenkinek ez jut az eszébe és a gyakorlat közben letisztul.

Talán életszerű lesz:
Képzelj el két absztrakció szinten teljesen különálló fogalmat. Legyen az egyik élőlény a másik egy jármű. Nevetséges lenne közös ős, de nem nevetséges közös interface.
Pl
GetCountry()
GetAge()

Nyelvészkedünk? :)
Végül is van benne valami.

"Ezeket a kérdéseket miért nem a facebook-on teszed fel a programozók csoportban?"

Akkor már inkább Instagram. Onnan csak egy tweet, és kikerül a HUP főoldalára.


Bizonyos nyelvekben nincs többszörös öröklődés, viszont lehetőség van több interface-t is megvalósítani.

Javaban biztos ez a megoldás, mivel nincs többszörös öröklődés, mint a C++-ban (én mindig is úgy éreztem, hogy a Java egy C++ akart lenni egyszerűbben), ez lett a workaround.

+1

Ha már be lettem hivatkozva, akkor ez az ominózus mucsai paraszt stílusban elküldött rész:
Ez a téma egyáltalán nem kapcsolódik az adott bloghoz!
Légy szíves nyiss egy fórumot és ott kérdezd meg és beszéld meg a dolgaidat.

Mire a következő finom és úri válaszok érkeztek:
Mivel a "tisztel" poszt-toló leugatott
Lassan itt tényleg csak a bunkó trollok maradnak. :D

--
Ickenham template engine

No, ilyen egy igazi paraszt: ha felkérik, hogy maradjon távol, csakazértis...

tukorbe neztel?

Menj már el egy pszichiáterhez!

Java8 óta interfészben is lehet metódust implementálni, tehát pont úgy be tud segíteni az implementációnak a boilerplate elkerülésében, mint egy absztrakt osztály. Tehát a gyakorlati különbség egyre kevesebb a kettő között. Talán a legnagyobb szintaxisbeli különbség, hogy egy interfész minden metódusa szükségszerűen publikus, míg egy absztrakt osztály metódusait el lehet rejteni. Van, ahol ez fontos lehet.

Szerintem az a tipikus, hogy ha a metódusok nagyobb része absztrakt, és bármit meg akarsz engedni, hogy mi hogyan legyen implementálva, akkor legyen interfész, viszont ha a kisebb része absztrakt, és le akarod korlátozni, hogyan lehet implementálni a működést, akkor meg absztrakt osztály (pl. https://en.wikipedia.org/wiki/Template_method_pattern). Ez sokszor párban jár, ld. List és AbstractList.

Köszi.

"Tehát a gyakorlati különbség egyre kevesebb a kettő között."

By design kurva nagy különbség van a kettő között: az interfésznek nem lehet állapota, az absztrakt osztálynak meg lehet...

Nyilván. Meg egy osztálynak van konstruktora, stb. De ez csak szintaxis különbség. Nem ez volt a kérdés.

A kérdés az volt, hogy egy design során melyiket választod. És ebben irreleváns az, amit te írtál. Egy interfészt osztállyal implementálsz, aminek már van állapota. Egy interfész definícióját én megírhatom úgy, hogy feltételezem, lesz állapota (és pl. tehetek bele setFoo(int value) metódust, ami az aktuális példány állapotát fogja módosítani). Egy interfész egyáltalán nem zárja ki az állapotot.

"De ez csak szintaxis különbség. Nem ez volt a kérdés."

Bocsánat, de lófaszt szintaxis különbség. Teljesen más célra van a kettő. By design. Viszont ugye senkiháziak kezében van a popszakma...

"Egy interfészt osztállyal implementálsz, aminek már van állapota. Egy interfész definícióját én megírhatom úgy, hogy feltételezem, lesz állapota (és pl. tehetek bele setFoo(int value) metódust, ami az aktuális példány állapotát fogja módosítani). Egy interfész egyáltalán nem zárja ki az állapotot."

Facepalm... :/

Bizony, senkiháziak kezében van a popszakma. Régen se volt homogén a színvonal a felsőoktatási intézmények között, most bejöttek ezek a gyorstalpaló programozóképzések, megjelent egy új "szint".

--
Gábriel Ákos

Tudom, hogy másra való, és megfelelően tudom használni. De az, amit te mondtál, hogy van van nincs állapota, az sántít. Pl. ha én azon gondolkodok, hogy implements List vagy extends AbstractList, nem az fog dönteni, hogy van-e állapot, mert minden esetben lesz.

A facepalmon kívül van valami konkrét érved is? Szerinted az interfész kizárja az állapotot vagy mi nem tetszik?

A probléma az, hogy az implements List az nem deklarálja hogy lesz állapota az osztálynak.
Annyit deklarál, hogy x metódusai megvannak.

Az ilyen nem kellően alátámasztott feltételezések miatt jöhetett létre olyan lófasz, hogy az angolok lábban, az amerikaiak meg méterben számoltak, mértékegységet nem küldtek át mert minek, oszt a kütyü leesett az égből, nahát.

--
Gábriel Ákos

"A probléma az, hogy az implements List az nem deklarálja hogy lesz állapota az osztálynak."

Disclaimer: tekintsünk el az emptyList()-től, mert a metódusok nagy része használhatatlan.

https://docs.oracle.com/javase/8/docs/api/java/util/List.html

* "The user of this interface has precise control over where in the list each element is inserted."
* "The List interface provides two methods to efficiently insert and remove multiple elements at an arbitrary point in the list."
* "Sorts this list according to the order induced by the specified Comparator."
* ...

Ha te ezeket tudod állapot nélkül, értelmesen implementálni, akkor kérek rá egy példát.

Tudom, hogy szintaktikailag az inteface csak metódusok létét írja elő. De van ám javadoc is, és van szemantika is.

Egy így felkommentezett interface igencsak előírja, hogy a tipikus implementációnak lesz állapota.

És habár az add, remove stb. a komment szerint opcionális, ez arról szól, hogy léteznek read-only listák (pl. com.google.common.collect.ImmutableList), de egy read-only listának is tipikusan van állapota, ami csak annyiban különleges, hogy csak létrehozáskor lehet módosítani.

Az interface nem mond semmit arról, hogy az implementációs osztály azt hogy valósítja meg. Lehet,
hogy távoli eljárást hív, vagy adatbázisban tárol és onnan vesz adatokat, stb.

Field, adatbázis, RPC: pont ezeket írtam, hogy szerintem ez állapot, legalábbis kívülről megkülönböztethetetlen. (Attól függ, mennyire veszed szigorúan a definícióját. A gyakorlati oldala fontosabb nekem.)

A cél mozgatása nem egy szimpatikus érveléstechnikai eszköz bár bizonyos szituációkban jól használható, főleg védekezésre.
Tapasztalatom az, hogy ezeket a beszélgetéseket nem érdemes folytatnom, nem lesz pozitív kicsengése.

--
Gábriel Ákos

Én azt mondtam, hogy egy interface implikálhatja, hogy az őt implementáló osztálynak lesz állapota. Te azt mondtad, nem. Én mutattam példát rá, hogy de igen, és hogy mire gondoltam. A cél nem változott, legalábbis ilyen célom nem volt. Esetleg valaki nem ért valamit.

"Egy interfész definícióját én megírhatom úgy, hogy feltételezem, lesz állapota"

Ez még viccnek is durva volt...

Miért? Szerinted mennyi értelme van egy állapotmentes List implementációnak? (A Collections.emptyList()-en kívül, amiben ugye a metódusok egy része használhatatlan.)

Lehet hogy fejben érted, de leírva nem az van. Óriási fogalmi keveredést érzek.

Az emptyList() az szerinted nem állapot? De, az: üres.
Két lista implementáció (mondjuk egy egyszeresen láncolt és egy kétszeresen láncolt az szerinted egyforma? Akkor is ha üres? Nem egyforma.

Keveredni látszik a megfogalmazásodban az interface, class, instance (object) fogalma.
Ez sok esetben nem probléma mert kontextus alapján "úgyis értjük" de bizonyos esetben halálos tévedésekhez vezethet.

--
Gábriel Ákos

Disclaimer: ez egy elméleti gondolatkísérlet, hogy egy interface-szel és egy abstract class-szal mit *lehet* megtenni (mert ugye én azt állítottam hogy kb. ugyanaz a kifejező erejük ami nem tetszik egyeseknek). Az pedig, hogy melyik mire való, nem mindig egyértelmű és kissé még szubjektív is. Még akkor is, ha 99%-ban az összes programozó egyetért (még mi ketten is egyetértenénk), akkor is van olyan, amikor nem egyértelmű. Lenne rá példa, hogy nem értenénk egyet, és arra is lenne példa, hogy mindkettő jó (lásd List és AbstractList).

"Az emptyList() az szerinted nem állapot?"

Mi az állapot? Alapvetően a Wikipédián leírt mondattal értek egyet: "Similarly, a computer program stores data in variables, which represent storage locations in the computer's memory. The contents of these memory locations, at any given point in the program's execution, is called the program's state." Ezt lehet kiegészíteni avval, hogy egy osztálynak akkor is van kvázi állapota, ha statikus metódusokon keresztül egy adatbázissal vagy egy bármilyen CRUD API-val beszélget. Ezek alapján az emptyList()-nek nincs állapota, be van drótozva, hogy üresnek tetteti magát, sem konstruktor paraméterrel, sem setFoo() metódusokkal, sem semmivel nem tudod módosítani.

"Két lista implementáció (mondjuk egy egyszeresen láncolt és egy kétszeresen láncolt az szerinted egyforma? Akkor is ha üres? Nem egyforma."

Nem tudom ez miért jön ide, és mi az "egyforma" definíciója. A List.equals() igazat fog adni bármilyen két listára, ha ugyanazok az elemek vannak bennük, akkor is, ha nem ugyanaz az implementáció. De én szerintem semmi olyat nem írtam, ami ezen múlik.

Vegyük ezt a kódot:

interface Value1 {
  void set(int x);
  int get();
}

abstract class Value2 {
  public abstract void set(int x);
  public abstract int get();
}

A dokumentációt képzeld oda. Akármelyiket implementálod, valószínűleg lesz egy private int x benne (vagy bármi, ami állaptot tárol a fenti definíció szerint), ugye? Azon kívül, hogy esetenként változik, hogy interface vagy abstract class a célravezetőbb, ez a két dolog kb. egyforma. Erre gondoltam, hogy az interface-nek direktben tényleg nincs állapota, de az interface kb. kikötheti, hogy az őt implementáló osztálynak már legyen.

Továbbgondolva:

abstract class Value2 {
  protected int x;
  public void set(int x) { this.x = x; }
  public int get() { return x; }
}

Franko erre gondolhatott, hogy egy abstract osztálynak lehet állapota, mert ennek tényleg van. Ha több metódusod van, amik közül némelyik boilerplate, akkor lehet értelme, hogy a boilerplate közös helyen van implementálva (a példában x/set/get). Illetve az abstract osztály lekorlátozza, hogy az x mező mellett már nem kell más állapot, pl. nem fogod adatbázisban tárolni a cuccot (jó, lehet ott is, cache-eléssel, stb.).

És most jön az, amiről előre látom, hogy bele fogtok kötni, de képzeljük el az alábbi képzeletbeli szintaxist a fentebbi helyett:

interface Value1 {
  int x;
}

abstract class Value2 {
  public int x;
}

Ha az előző példákban láttad a párhuzamot Value1 és Value2 között, akkor itt is látod. Igen igen, publikus mezőt kiajánlani nem szép dolog. Igen igen, az előbbit a javac és a JVM egyáltalán nem is támogatja. Ez csak egy gondolatkísérlet. Lehetne írni olyan javac-t és JVM-et, ami ezt támogatja? Lehetne. Az, hogy most ez nem működik, az szintaxis kérdése (azon felül, hogy csúnya, és ez miatt valószínűleg soha nem is lesz támogatva).

Hogy legyen min csámcsogni, bedobom ezt is: a javac simán megengedhetné, hogy ha egy interface-ben az összes metódusnak van default-ja, akkor a new Interface() jelentse ugyanazt, mint a new Interface() {}, és hoppá, sikerült példányosítani az interface-t. *Elsősorban* azért nem lehet példányosítani abstract dolgokat, mert hiányzik belőlük néhány implementáció. Az kb. önkényesség/szépség/értelmesség kérdése, hogy még egy hiányzó metódusok nélküli interface-t sem lehet példányosítani.

+1 ez a lényegi különbség

még ha létrehozható is egy getter/setter páros az interface-ben, amögött nincs valódi mező ami állapotot tárolna, és ezt az implementor vagy aláteszi ha akarja, vagy nem

> Naszóval... nem tudom, mely nyelvekben van mindkettő, melyekben nincs egyik sem, Pythonról tudom egyedül, hogy az utóbbi kategória.

Nem jól tudod, abstract class van pythonban is, a 2.6-os verziótól kezdve. Igaz, némi metaclass-os tákolással, de van. Lásd: https://docs.python.org/2.7/library/abc.html

Jogos, bár szintaktikailag... kissé, khm... formabontó... :)

Fontos filozófiai és gyakorlati különbségek vannak.
Abstract class (AC):

- előbb van az AC, utána jön a valódi class.

Interface:

- előbb van a class, aztán ennek lesz esetleg több interface-e.

IF-t ugye különösebb következmények nélkül kb. bármikor "rá lehet húzni" egy class-ra, a class attól nem lesz beteg.
Viszont ha újabb AC-t teszel be a hierarchiába utólag, attól a leszármazott class-ok bizony lehetnek betegek.

A realitás azt mutatja, hogy az AC "túl sok" az átlag programozónak, az interfész az még elmegy (ld. előbbi ok), azt kell jól megérteni, a legtöbb keretrendszer ezt használja (pl. Spring).

--
Gábriel Ákos

C#-ban van interface és abstract class, egyszeres öröklődés.
Az interface-t arra használom, hogy egymással kompatibilis osztályokat hozzak létre.
Az abstract classt közös ősnek szoktam használni, ahol vannak metódusok, amik minden származtatott osztályban ugyanúgy kell működjenek, de vannak, amik különböznek. Utóbbiakat a származtatott osztályban fejtem ki. Az abstract garantálja, hogy a metódusok benne lesznek.
-----------
Akkor beszélsz igazán jól angolul, ha egy angol viccen őszintén tudsz nevetni