Nasljeđivanje i sastav (odnos Is-a vs Has-a) u Javi

1. Pregled

Nasljeđivanje i sastav - zajedno s apstrakcijom, inkapsulacijom i polimorfizmom - temelji su objektno orijentiranog programiranja (OOP).

U ovom ćemo uputstvu pokriti osnove nasljeđivanja i sastava te ćemo se snažno usredotočiti na uočavanje razlika između dvije vrste odnosa.

2. Osnove nasljeđivanja

Nasljeđivanje je moćan, a opet previše korišten i zloupotrijebljen mehanizam.

Jednostavno rečeno, s nasljeđivanjem, osnovna klasa (poznata kao osnovni tip) definira stanje i ponašanje uobičajeno za određeni tip i dopušta potklase (tzv. Podtipove) da pružaju specijalizirane verzije tog stanja i ponašanja.

Da bismo imali jasnu ideju kako raditi s nasljeđivanjem, stvorimo naivan primjer: osnovnu klasu Osoba koji definira uobičajena polja i metode za osobu, dok su podrazredi Konobarica i Glumica pružaju dodatne, fino zrnaste implementacije metoda.

Evo Osoba razred:

javni razred Osoba {privatni konačni naziv niza; // ostala polja, standardni konstruktori, getteri}

I ovo su potklase:

konobarica javne klase proširuje Person {public String serveStarter (String starter) {return "Posluživanje" + starter; } // dodatne metode / konstruktori} 
javna klasa Glumica proširuje Person {javni niz readScript (String film) {return "Čitanje skripte" + filma; } // dodatne metode / konstruktori}

Uz to, izradimo jedinstveni test kako bismo provjerili jesu li primjeri Konobarica i Glumica klase su također primjeri Osoba, čime se pokazuje da je uvjet "je-a" zadovoljen na razini tipa:

@Test javna praznina givenWaitressInstance_whenCheckedType_thenIsInstanceOfPerson () {assertThat (nova konobarica ("Mary", "[email zaštićena]", 22)) .isInstanceOf (Person.class); } @Test javna praznina givenActressInstance_whenCheckedType_thenIsInstanceOfPerson () {assertThat (nova glumica ("Susan", "[e-pošta zaštićena]", 30)) .isInstanceOf (Person.class); }

Ovdje je važno naglasiti semantički aspekt nasljeđivanja. Osim ponovne upotrebe provedbe Razred osoba, stvorili smo dobro definiran odnos "je-a" između osnovnog tipa Osoba i podvrste Konobarica i Glumica. Konobarice i glumice zapravo su osobe.

To bi nas moglo natjerati da pitamo: u kojim je slučajevima nasljeđivanje pravi pristup?

Ako podtipovi ispunjavaju uvjet "je-a" i uglavnom pružaju aditivnu funkcionalnost niže po hijerarhiji klasa,onda je nasljedstvo put kojim treba ići.

Naravno, nadjačavanje metoda dopušteno je sve dok nadjačane metode zadržavaju zamjenjivost osnovnog tipa / podtipa promičenog Liskovim principom zamjene.

Uz to, to bismo trebali imati na umu podtipovi nasljeđuju API osnovnog tipa, što u nekim slučajevima može biti pretjerano ili jednostavno nepoželjno.

Inače bismo umjesto toga trebali koristiti sastav.

3. Nasljeđivanje u uzorcima dizajna

Iako je konsenzus da bismo trebali favorizirati sastav nego nasljedstvo kad god je to moguće, postoji nekoliko tipičnih slučajeva upotrebe u kojima nasljeđe ima svoje mjesto.

3.1. Uzorak nadtipa sloja

U ovom slučaju, mi koristite nasljedstvo za premještanje zajedničkog koda u osnovnu klasu (supertip), na osnovi po slojevima.

Evo osnovne implementacije ovog uzorka u sloju domene:

entitet javne klase {zaštićen dugi ID; // postavljači} 
javni razred Korisnik proširuje entitet {// dodatna polja i metode} 

Isti pristup možemo primijeniti i na ostale slojeve u sustavu, poput slojeva usluge i postojanosti.

3.2. Uzorak metode predloška

U uzorku metode predloška možemo koristite osnovnu klasu za definiranje invarijantnih dijelova algoritma, a zatim implementirajte varijantne dijelove u podrazrede:

javna apstraktna klasa ComputerBuilder {javno završno računalo buildComputer () {addProcessor (); addMemory (); } javna sažetak void addProcessor (); javna sažetak void addMemory (); } 
javna klasa StandardComputerBuilder proširuje ComputerBuilder {@Override public void addProcessor () {// implementacija metode} @Override public void addMemory () {// implementacija metode}}

4. Osnove kompozicije

Sastav je još jedan mehanizam koji pruža OOP za ponovnu upotrebu provedbe.

U suštini, kompozicija omogućuje nam modeliranje objekata koji su sačinjeni od drugih predmeta, definirajući tako odnos "ima-a" između njih.

Nadalje, sastav je najjači oblik udruživanja, što znači da objekti koji čine ili ih sadrži jedan objekt također se uništavaju kada je taj objekt uništen.

Da bismo bolje razumjeli kako kompozicija djeluje, pretpostavimo da moramo raditi s objektima koji predstavljaju računala.

Računalo se sastoji od različitih dijelova, uključujući mikroprocesor, memoriju, zvučnu karticu i tako dalje, tako da računalo i svaki njegov dio možemo modelirati kao pojedinačne razrede.

Evo kako jednostavna implementacija Računalo klasa može izgledati:

računalo javne klase {privatni procesor procesora; privatna memorija memorije; privatna SoundCard zvučna kartica; // standardni getteri / postavljači / konstruktori public Izborni getSoundCard () {return Optional.ofNullable (soundCard); }}

Sljedeće klase modeliraju mikroprocesor, memoriju i zvučnu karticu (sučelja su izostavljena radi kratkoće):

javna klasa StandardProcessor implementira Processor {model privatnog niza; // standardni getteri / postavljači}
javna klasa StandardMemory implementira memoriju {marka private String; privatna veličina niza; // standardni konstruktori, getteri, toString} 
javna klasa StandardSoundCard implementira SoundCard {private String brand; // standardni konstruktori, getteri, toString} 

Lako je razumjeti motive koji potiskuju sastav nad nasljeđivanjem. U svakom scenariju u kojem je moguće uspostaviti semantički ispravan odnos "ima-a" između određene klase i drugih, sastav je pravi izbor.

U gornjem primjeru, Računalo zadovoljava uvjet "ima-a" s klasama koje modeliraju njegove dijelove.

Također je vrijedno napomenuti da u ovom slučaju, sadrži Računalo objekt ima vlasništvo nad sadržanim objektima ako i samo ako predmeti se ne mogu ponovno upotrijebiti u drugom Računalo objekt. Ako oni mogu, mi bismo koristili agregaciju, a ne sastav, tamo gdje se vlasništvo ne podrazumijeva.

5. Sastav bez apstrakcije

Alternativno, mogli smo definirati odnos kompozicije tvrdo kodirajući ovisnosti Računalo klase, umjesto da ih deklariramo u konstruktoru:

računalo javne klase {privatni procesor StandardProcessor = novi StandardProcessor ("Intel I3"); privatna memorija StandardMemory = nova StandardMemory ("Kingston", "1TB"); // dodatna polja / metode}

Naravno, ovo bi bio krut, čvrsto povezan dizajn, kakav bismo mi izrađivali Računalo snažno ovisi o specifičnim implementacijama sustava Windows Procesor i Memorija.

Ne bismo iskoristili razinu apstrakcije koju pružaju sučelja i ubrizgavanje ovisnosti.

Početnim dizajnom koji se temelji na sučeljima dobivamo labavo povezan dizajn, što je također lakše testirati.

6. Zaključak

U ovom smo članku naučili osnove nasljeđivanja i sastava na Javi te smo dubinski istražili razlike između dvije vrste odnosa („je-a“ nasuprot „ima-a“).

Kao i uvijek, svi uzorci koda prikazani u ovom vodiču dostupni su na GitHub-u.