Kezdőoldal » Számítástechnika » Programozás » Mi a különbség az absztrakt...

Mi a különbség az absztrakt osztály és az interfész között?

Figyelt kérdés

Már több leírást/hozzászólást elolvastam, de még mindig nem tiszta a különbség.

Azt értem, hogy:


1. Interfészt többet is lehet implementálni, még osztályt csak 1-et

absztrakt osztályban


2. Absztrakt osztálynál: A is B, interfésznél: A can do B kapcsolat van


3. Valamint, hogy interfésznél csak dekrarálni lehet a függvényeket, absztrakt osztálynál lehet definiálisni is (de nem muszáj).


4. A közös ősosztállyal rendelkező osztályok között sokkal "szorosabb" kapcsolat van, mint az interfészt implementáló osztályok között.


De még mindig nem világos, hogy mikor érdemes inkább osztályt és mikor interfészt használni?


Ha a fentiek szerint öröklődésnél hasonlítanak az osztályok, interfésznél kevésbé, akkor pl az ArrayList mért interfész és mért nem a.osztály? Értem, hogy egy láncolt listánál más az implementáció mint egy hashsetnél, (vagy sima listánál)

de Overrideolni ugyan úgy lehetne, (ha már úgyis virtuálisak a metódusok absz.o esetén), szóval mért előnyősebb ebben az esetben egy interfész, mint egy osztály?


2022. jún. 24. 20:02
 1/4 anonim ***** válasza:
77%
Az interfész előnyösebb olyan helyzetekben, mikor több osztálynak kell valamit megvalósítania. Azokban a nyelvekben, ahol nem támogatott a többszörös öröklődés, az absztrakt osztállyal nem valósítható meg. Ráadásul azáltal, hogy objektumpéldány jön létre, függőségek jelennek meg a kódban, ami nehezebben karbantartható kódot eredményez nagy kódbázis esetén.
2022. jún. 24. 23:25
Hasznos számodra ez a válasz?
 2/4 anonim ***** válasza:
80%

Az interfésszel egy képességet írsz le. Az osztály, ami implementálja azt az interfészt, annak képesnek kell lennie annak a dolognak az elvégzésére, amelyet az interfész leír. Egyfajta szerződésként is felfogható két fél között:

- Aki az interfészt implementálja, vállalja azt, hogy az interfészben leírt módon nyújt egy szolgáltatást.

- Aki az interfész által leírt szolgáltatást igénybe veszi, tudni fogja azt, hogy miképp tudja használni azt az eszközt, amely a kért szolgáltatást megvalósítja.


Példaként képzeld el, hogy valami kétszemélyes játékot fejlesztesz. Mondjuk legyen valami körökre osztott táblajáték, pl. sakk. Egy ilyen játéknak három fő összetevője van:

- a UI (de ez most nem lényeg)

- a két játékos

- a játéklogika


Szeretnéd megcsinálni azt, hogy a játékot

- lehessen számítógép ellen játszani

- lehessen egy számítógépen két emberrel játszani

- lehessen két számítógépen két emberrel játszani hálózaton keresztül


Ha azt tippelted, hogy a játékost fogjuk interfészként leírni, amelynek több implementációja is lesz, akkor gratulálok, nagyon érzed a dolgot. Egy játékosnak ugyanis tudnia kell:

- megadni a következő lépést

- értesülnie arról, hogy az ellenfél köre jön

- értesülnie arról, hogy vesztett

- értesülnie arról, hogy győzött

- értesülnie arról, hogy a játék döntetlen lett


Vagyis lesz valami ilyesmi:

interface IPlayer {

Step GetNextStep();

void NotifyOtherPlayersTurn();

void NotifyWin();

void NotifyLose();

void NotifyDraw();

}


A játéklogikának aztán abszolút mindegy, hogy milyen játékos játszik a valóságban. Neki csak az a lényeg, hogy ezt az interfészt felhasználva kommunikálhat a játékossal. Tehát:

class GameLogic {

.. private IPlayer playerA;

.. private IPlayer playerB;

.. //konstruktorban értéket kap a playerA és a playerB

.. void PlayGame() {

.. .. IPlayer currentPlayer = this.playerA;

.. .. IPlayer opponentPlayer = this.playerB;

.. .. while (!this.IsGameOver()) {

.. .. .. opponentPlayer.NotifyOtherPlayersTurn();

.. .. .. Step playerMove = currentPlayer.GetNextStep();

.. .. .. //validáljuk a lépést, frissítjük a játékállapotot és szólunk a UI-nak


.. .. .. //felcseréljük a játékosokat

.. .. .. IPlayer temp = currentPlayer;

.. .. .. currentPlayer = opponentPlayer;

.. .. .. opponentPlayer = temp;

.. .. }

.. .. if (this.IsDraw()) {

.. .. .. this.playerA.NotifyDraw();

.. .. .. this.playerB.NotifyDraw();

.. .. }

.. .. else {

.. .. .. this.GetWinner().NotifyWin();

.. .. .. this.GetLoser().NotifyLose();

.. .. }

.. }

.. //...

}


És lesznek a következő implementációi a játékosoknak:

class AIPlayer : IPlayer {...}

class LocalPlayer : IPlayer {...}

class NetworkPlayer : IPlayer {...}


Az absztrakt osztályt már nehezebb megfogni. Vannak nyelvek, mint pl. a C++, ahol az interfész, mint fogalom nem is létezik, helyette az interfészt absztrakt osztályokkal oldják meg - de az absztrakt osztály minden nyelvben használható ugyanarra a célra, amire az interfész is (maximum az egyszeres öröklődés okozhat gondot azoknál a nyelveknél, ahol az interfész külön fogalomként létezik), mivel semmi sem tiltja meg neked, hogy egy absztrakt osztály minden metódusa absztrakt legyen.


Az interfésszel ellentétben - amely szerint "X dolog Y dologra képes" - az absztrakt osztály több fogalom közös képességeinek megvalósítását emeli ki, és mint absztrakt fogalmat vezeti be.


Tegyük fel, hogy egy zenelejátszó szoftvert fejlesztesz. Sokféle hangfájlformátum létezik, mint pl. MP3, FLAC, WAV és társai. A zenelejátszáshoz az kell, hogy az ezekben a formátumban tárolt adatokból visszaállítsd a nyers PCM hangmintát, amit leküldhetsz a hangkártyára. Szóval csinálsz valami ilyesmit:


interface IAudioPlayer {

void Play();

void Stop();

void Pause();

Time GetLength();

void Seek(Time time);

int GetVolume();

void SetVolume(int value);

}


És ezt implementálod is szépen:

class MP3Player : IAudioPlayer {

.. private IAudioOutputDevice outputDevice;

.. private IFile inputFile; //ezek ctor-ban értéket kapnak

.. private PcmWaveStream DecodeAudio() {

.. .. //dekódoljuk az MP3 formátumot

.. }

.. public void Play() { //ez most egy nagyon-nagyon leegyszerűsített verzió

.. .. PcmWaveStream rawStream = DecodeAudio();

.. .. while (!rawStream.EndOfStream()) {

.. .. .. byte[] segment = rawStream.Read(rawStream.Bitrate); //beolvasunk 1 másodpercnyi anyagot

.. .. .. outputDevice.Write(segment);

.. .. }

.. }

.. //...

}


class FlacPlayer : IAudioPlayer {

.. private IAudioOutputDevice outputDevice;

.. private IFile inputFile; //ezek ctor-ban értéket kapnak

.. private PcmWaveStream DecodeAudio() {

.. .. //dekódoljuk a FLAC formátumot

.. }

.. public void Play() { //ez most egy nagyon-nagyon leegyszerűsített verzió

.. .. PcmWaveStream rawStream = DecodeAudio();

.. .. while (!rawStream.EndOfStream()) {

.. .. .. byte[] segment = rawStream.Read(rawStream.Bitrate); //beolvasunk 1 másodpercnyi anyagot

.. .. .. outputDevice.Write(segment);

.. .. }

.. }

.. //...

}


Ami így első körben feltűnhet az az, hogy a Play metódus gyakorlatilag ugyanazt az implementációt tartalmazza mindkét osztályban, csak a DecodeAudio működése tér el. Ez azért van, mert a különböző fájltípusok által reprezentált adatot egységes formára hoztuk, amely feldolgozása már egységes módon történik. Itt jön a képbe az absztrakt osztály, ugyanis a megegyező részeket ki tudjuk emelni:


abstract class AbstractAudioPlayer : IAudioPlayer {

.. private IAudioOutputDevice outputDevice;

.. private IFile inputFile; //ezek ctor-ban értéket kapnak

.. protected abstract PcmWaveStream DecodeAudio();

.. public void Play() { //ez most egy nagyon-nagyon leegyszerűsített verzió

.. .. PcmWaveStream rawStream = DecodeAudio();

.. .. while (!rawStream.EndOfStream()) {

.. .. .. byte[] segment = rawStream.Read(rawStream.Bitrate); //beolvasunk 1 másodpercnyi anyagot

.. .. .. outputDevice.Write(segment);

.. .. }

.. }

.. //...

}


Így már elég csak az eltérő részekkel foglalkoznod:

class MP3Player : AbstractAudioPlayer {

.. //csinálunk egy ctor-t, amely az örökölt ctor-t meghívja

.. private PcmWaveStream DecodeAudio() {

.. .. //dekódoljuk az MP3 formátumot

.. }

}


class FlacPlayer : AbstractAudioPlayer {

.. //csinálunk egy ctor-t, amely az örökölt ctor-t meghívja

.. private PcmWaveStream DecodeAudio() {

.. .. //dekódoljuk a FLAC formátumot

.. }

}


Látható, hogy a kódunk jelentősen egyszerűsödött, jobban áttekinthető, és ami a legfontosabb, hogyha hibát vétettünk a közös részek valamelyikében, elég csak egy helyen átírnunk.


Remélem, ez a gyakorlati példa segített megérteni a lényeget, de ha nem, kérdezz bátran ^^

2022. jún. 25. 01:35
Hasznos számodra ez a válasz?
 3/4 anonim ***** válasza:
80%

Bocsi, volt némi copy-paste issue. Tehát helyesen:

class MP3Player : AbstractAudioPlayer {

.. //csinálunk egy ctor-t, amely az örökölt ctor-t meghívja

.. protected override PcmWaveStream DecodeAudio() {

.. .. //dekódoljuk az MP3 formátumot

.. }

}


class FlacPlayer : AbstractAudioPlayer {

.. //csinálunk egy ctor-t, amely az örökölt ctor-t meghívja

.. protected override PcmWaveStream DecodeAudio() {

.. .. //dekódoljuk a FLAC formátumot

.. }

}

2022. jún. 25. 01:38
Hasznos számodra ez a válasz?
 4/4 anonim ***** válasza:
67%
Csak zárójelben, az 1-es és a 3-as pontok nyelvfüggőek. Pl. Java újabb verzióiban interface-ben is lehet függvénydefiníció.
2022. jún. 25. 06:58
Hasznos számodra ez a válasz?

További kérdések:




Minden jog fenntartva © 2024, www.gyakorikerdesek.hu
GYIK | Szabályzat | Jogi nyilatkozat | Adatvédelem | Cookie beállítások | WebMinute Kft. | Facebook | Kapcsolat: info(kukac)gyakorikerdesek.hu

A weboldalon megjelenő anyagok nem minősülnek szerkesztői tartalomnak, előzetes ellenőrzésen nem esnek át, az üzemeltető véleményét nem tükrözik.
Ha kifogással szeretne élni valamely tartalommal kapcsolatban, kérjük jelezze e-mailes elérhetőségünkön!