Mi a különbség az absztrakt osztály és az interfész között?
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?
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 ^^
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
.. }
}
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
Ha kifogással szeretne élni valamely tartalommal kapcsolatban, kérjük jelezze e-mailes elérhetőségünkön!