Arv (Inheritance)

Arv låter en subklass (barn) ärva fält och metoder från en superklass (förälder). Detta skapar en är-en-relation och främjar återanvändning.

// Superklass (förälder)
public class Fordon {
    protected String registeringsnummer;

    public Fordon(String registeringsnummer) {
        this.registeringsnummer = registeringsnummer;
    }

    public void starta() {
        System.out.println("Fordonet startar");
    }

    public void stanna() {
        System.out.println("Fordonet stannar");
    }
}

// Subklass (barn) — ärver från Fordon
public class Bil extends Fordon {
    private int antalDorrar;

    public Bil(String regNr, int antalDorrar) {
        super(regNr);          // anropar förälderns konstruktor
        this.antalDorrar = antalDorrar;
    }

    public void tuta() {
        System.out.println("Tut tut!");
    }
}

// Användning
Bil b = new Bil("ABC123", 4);
b.starta();   // ärvd från Fordon
b.tuta();     // egen metod
b.stanna();   // ärvd från Fordon

Nyckelordet super används för att referera till föräldraklassen — både för konstruktorer och överskuggade metoder.


Arvshierarkier

En subklass kan också bli superklass för andra klasser:

public class Fordon { /* ... */ }
public class Bil extends Fordon { /* ... */ }
public class Elbil extends Bil { /* ... */ }
public class Sportbil extends Bil { /* ... */ }

De flesta språk (Java, C#, PHP) tillåter enkelarv — en klass kan bara ha en direkt förälder. C++ och Python tillåter flerarv (flera föräldrar), vilket är kraftfullt men kan leda till Diamond-problemet.


Överskuggning av metoder (Overriding)

En subklass kan ge en egen implementation av en metod från superklassen:

public class Fordon {
    public void beskrivning() {
        System.out.println("Ett fordon");
    }
}

public class Bil extends Fordon {
    @Override
    public void beskrivning() {
        System.out.println("En bil med " + antalDorrar + " dörrar");
    }
}

Fordon f = new Bil();
f.beskrivning(); // "En bil med 4 dörrar" — polymorfism i praktiken

@Override-annotationen är valfri men rekommenderas — kompilatorn varnar då om du stavat fel eller signaturen inte matchar.


När ska man använda arv?

Arv passar bra när:

  • Det finns en tydlig är-en-relation (Förälder-Barn)
  • Subklassen behöver det mesta av superklassens beteende
  • Du vill utnyttja polymorfism
public class Anstalld { /* ... */ }
public class Chef extends Anstalld { /* ... */ }         // Chef ÄR-EN Anställd
public class Utvecklare extends Anstalld { /* ... */ }    // Utvecklare ÄR-EN Anställd

Arv passar dåligt när:

  • Relationen är mer har-en än är-en
  • Du bara vill återanvända en liten del av superklassen
  • Subklassen behöver override:a många metoder

Komposition (Composition)

Komposition innebär att en klass innehåller andra objekt som fält — en har-en-relation. Detta är ofta flexiblare än arv.

// KOMPOSITION — föredra detta framför arv när det är möjligt

public class Motor {
    public void starta() {
        System.out.println("Motorn startar");
    }
}

public class Hjul {
    public void rulla() {
        System.out.println("Hjulen rullar");
    }
}

public class Bil {
    private Motor motor = new Motor(); // Bil HAR-EN motor
    private Hjul[] hjul = new Hjul[4]; // Bil HAR-FYRA hjul

    public Bil() {
        for (int i = 0; i < 4; i++) {
            hjul[i] = new Hjul();
        }
    }

    public void starta() {
        motor.starta();
        for (Hjul h : hjul) h.rulla();
    }
}

Fördelar med komposition framför arv

AspektArvKomposition
KopplingStark (tight coupling)Lös (loose coupling)
FlexibilitetLåst vid kompileringKan ändras vid runtime
ÅteranvändningÄrver allt (även oönskat)Väljer bara vad som behövs
TestbarhetSvårare (beroende av förälder)Lätt (beroenden kan mockas)
ÄndringarÄndring i superklass påverkar allaIsolerad

Favor Composition over Inheritance

Detta är en välkändprincip inom OOP. Exempel där komposition vinner:

// DÅLIGT med arv — en klass per kombination
public class Anstalld { /* ... */ }
public class ChefMedBonus extends Anstalld { /* ... */ }
public class ChefUtanBonus extends Anstalld { /* ... */ }
public class KonsultMedBonus extends Anstalld { /* ... */ }
public class KonsultUtanBonus extends Anstalld { /* ... */ }
// Exploderar snabbt!

// BRA med komposition
public interface BonusPolicy { double beraknaBonus(double lon); }

public class Anstalld {
    private BonusPolicy bonusPolicy; // injiceras

    public double getTotalErsattning() {
        return lon + bonusPolicy.beraknaBonus(lon);
    }
}

Många designmönster (Strategy, Decorator, Bridge) bygger på komposition istället för arv.


protected — en medelväg

protected låter subklasser komma åt förälderns fält och metoder, men håller dem dolda för omvärlden:

public class Fordon {
    protected String registeringsnummer;  // åtkomligt i subklasser
    private double serviceKostnad;        // bara i Fordon
}

Var dock försiktig med protected — det skapar ändå en stark koppling mellan förälder och barn.


final / sealed — förhindra arv

Ibland vill du förhindra att en klass ärvs:

// Java — final
public final class String { /* ... */ }

// C# — sealed
public sealed class String { /* ... */ }

// PHP — final
final class Database { /* ... */ }

Varför? För att garantera oföränderlighet (immutability), säkerhet eller att kontraktet inte kan brytas.


Fortsätt till Polymorfism.

Av Victor Hernandez från Bytebase.se