När man lär sig objektorienterad programmering, och efter att ha lärt sig grunderna i objekt, fält och metoder, ägnar man den mesta tiden åt arv. Arv innebär att vi förvärvar en del av implementationen från en basklass. Man behöver bara skapa en subklass av en basklass för att ärva alla icke-privata fält och metoder.
En bil och ett flygplan är fordon så det är uppenbart att båda dessa klasser bör utökas från sin gemensamma basklass som heter Vehicle. Detta är ett typiskt akademiskt exempel, men när vi beslutar om att binda dessa klasser med arvsrelationen bör vi vara medvetna om vissa konsekvenser.
Fig. 1 Implementering av arvsrelation.
I det här fallet är klasserna nära kopplade till varandra - det innebär att förändringar i beteendet hos varje klass kan uppnås genom att göra ändringar i basklassen kod. Detta kan vara både en fördel och en nackdel - det beror på vilken typ av beteende vi förväntar oss. Om arvet tillämpas vid fel tidpunkt kan processen med att lägga till en ny funktion stöta på vissa implementeringssvårigheter eftersom den inte passar in i den skapade klassmodellen. Vi måste välja mellan att duplicera koden och att omorganisera vår modell - och det kan vara en riktigt tidskrävande process. Vi kan kalla den kod som exekverar arvsrelationen för "öppen-stängd" - det betyder att den är öppen för tillägg men stängd för modifiering. Om vi antar att det i Vehicle-klassen finns en allmän, definierad motordrift för varje fordon, skulle vi behöva göra stora förändringar i våra klasser om vi vill lägga till en modell för ett motorlöst fordon (t.ex. en cykel) i vår klasshierarki.
klass Fordon
def start_motor
slut
def stoppa_motor
slut
slut
klass Flygplan < Fordon
def flytta
starta_motorn
...
stoppa_motor
slut
slut
Sammansättning
Om vi bara är intresserade av en del av beteendet hos den befintliga klassen är ett bra alternativ till arv att använda komposition. Istället för att skapa subklasser som ärver alla beteenden (de vi behöver och de vi inte behöver alls) kan vi isolera de funktioner vi behöver och utrusta våra objekt med referenser till dem. På så sätt ger vi upp tanken på att objektet är en typ av ett basobjekt, till förmån för påståendet att den innehåller endast vissa delar av dess egenskaper.
Fig. 2 Använda kompositionen
På detta sätt kan vi isolera den kod som ansvarar för motordriften till den autonoma klassen Engine och endast referera till den i de klasser som representerar fordon med motorer. Genom att isolera funktionerna med hjälp av komposition blir klassstrukturen för Vehicle enklare och inkapslingen av enskilda klasser stärks. Nu är det enda sättet för fordonen att påverka motorn att använda dess publika interface, eftersom de inte längre kommer att ha information om dess implementation. Dessutom kommer det att göra det möjligt att använda olika typer av motorer i olika fordon och till och med tillåta utbyte av dem medan programmet körs. Att använda komposition är naturligtvis inte felfritt - vi skapar en löst sammanhängande klassuppsättning, som lätt kan utökas och är öppen för modifiering. Men samtidigt är varje klass kopplad till många andra klasser och måste ha information om deras gränssnitt.
klass Fordon
slut
Klass Motor
def start
slut
def stopp
slut
slut
klass Flygplan < Fordon
def initialisera
@motor = Motor.ny
slut
def flytta
@motor.start
@motor.stopp
slut
def change_engine(ny_engine)
@engine = ny_engine
end
slut
Valet
Båda beskrivna tillvägagångssätten har fördelar och nackdelar, så hur man väljer mellan dem? Arv är en specialisering, så det är bäst att tillämpa dem endast för problem, där det finns "is-a" typrelationer - så vi hanterar den verkliga hierarkin av typer. Eftersom arv tätt länkar klasser tillsammans, bör vi först och främst alltid överväga om vi ska använda komposition eller inte. Komposition bör användas för problem där det finns "har-en"-typrelationer - så klassen har många delar men den är något mer än en uppsättning klasser. Ett flygplan består av delar men är i sig självt något mer - det har ytterligare förmågor, t.ex. att flyga. Om man går vidare med detta exempel kan enskilda delar förekomma i olika specialistvarianter, och då är det ett bra tillfälle att använda arv.
Såväl arv som komposition är bara verktyg som programmerarna har till sitt förfogande, så att välja rätt verktyg för ett visst problem kräver erfarenhet. Så låt oss öva och lära av våra misstag 🙂