Abstraktion i OOP

Abstraktion handlar om att fokusera på vad något gör, inte hur det gör det. De två viktigaste verktygen för abstraktion är abstrakta klasser och interface.


Abstrakta klasser

En abstrakt klass kan inte instansieras direkt. Den fungerar som en basklass som definierar delvis implementation och tvingar subklasser att implementera vissa metoder.

public abstract class Betalning {
    protected double belopp;

    public Betalning(double belopp) {
        this.belopp = belopp;
    }

    // Konkret metod — delad implementation för alla betalningar
    public void skrivKvitto() {
        System.out.println("Kvitto: " + belopp + " kr");
    }

    // Abstrakt metod — måste implementeras av subklasser
    public abstract void genomfor();
}

En abstrakt metod har ingen kropp ({}) — bara signatur. Alla konkreta subklasser måste implementera den.

public class Kortbetalning extends Betalning {
    public Kortbetalning(double belopp) {
        super(belopp);
    }

    @Override
    public void genomfor() {
        System.out.println("Dra kort för " + belopp + " kr");
    }
}

public class SwishBetalning extends Betalning {
    public SwishBetalning(double belopp) {
        super(belopp);
    }

    @Override
    public void genomfor() {
        System.out.println("Skicka Swish-begäran för " + belopp + " kr");
    }
}

// Betalning b = new Betalning(100); // GÅR INTE — abstrakt klass

Betalning b = new Kortbetalning(500);
b.genomfor();  // "Dra kort för 500.0 kr"
b.skrivKvitto(); // "Kvitto: 500.0 kr"

Interface

Ett interface definierar vad en klass ska kunna göra, utan någon implementation. Alla metoder i ett interface är implicit publika och abstrakta.

public interface loggbar {
    void logga(String meddelande);  // implicit public abstract
}

public interface Skrivbar {
    void sparaTillFil(String sökväg);
}

En klass kan implementera flera interface (till skillnad från arv där en klass bara kan ha en förälder):

public class Logger implements loggbar, Skrivbar {
    @Override
    public void logga(String meddelande) {
        System.out.println("[LOG] " + meddelande);
    }

    @Override
    public void sparaTillFil(String sökväg) {
        // skriv till fil...
    }
}

Interface som kontrakt

Interface är kraftfulla för att skapa lös koppling:

// Dåligt — beroende av konkret klass
public class OrderService {
    private MySQLDatabase db = new MySQLDatabase(); // Hård koppling!
}

// Bra — beroende av interface
public class OrderService {
    private final Database db; // Abstraktion

    public OrderService(Database db) { // Injiceras utifrån
        this.db = db;
    }
}

Detta är Dependency Inversion (D i SOLID) — högnivåmoduler ska inte bero på lågnivåmoduler, båda ska bero på abstraktioner.


Abstrakt klass vs Interface

AspektAbstrakt klassInterface
InstansierasNejNej
Kan ha fält/stateJa (protected int x)Nej
Kan ha konstruktorJaNej
Kan ha implementationJa (konkreta metoder)default-metoder
Multiple inheritanceNej (enda superklass)Ja (flera interface)
SynlighetAlla nivåerAlltid public
Använd närDelad struktur + kontraktEndast kontrakt

När välja vad?

Välj abstrakt klass när:

  • Subklasser delar gemensam implementation (fält, konstruktorer, hjälpmetoder)
  • Relationen är tydligt är-en
  • Du vill kontrollera hur subklasserna byggs
public abstract class Rapport {
    protected String titel;

    public Rapport(String titel) {
        this.titel = titel;
    }

    public void skrivUtRubrik() {
        System.out.println("=== " + titel + " ===");
    }

    public abstract String genereraInnehall();
    public abstract String formatera();
}

Välj interface när:

  • Du vill definiera en förmåga som många olika klasser kan ha
  • Olika klasser som inte är relaterade behöver samma kontrakt
  • Du vill ha lös koppling och Dependency Injection
public interface Flygbar {
    void flyg();
}

public class Fagel implements Flygbar { /* ... */ }
public class Flygplan implements Flygbar { /* ... */ }
public class Drake implements Flygbar { /* ... */ }
// Helt olika saker — men alla kan flyga

Tumregel: Interface = “vad den kan göra”. Abstrakt klass = “vad den är”.


Default-metoder i Interface

Moderna språk tillåter default-implementation i interface:

public interface Flygbar {
    void flyg();

    // Default-metod — valfri att överskugga
    default void landa() {
        System.out.println("Landar...");
    }
}

// Behöver bara implementera flyg()
public class Fagel implements Flygbar {
    public void flyg() { System.out.println("Flax flax"); }
    // landa() fungerar med default-implementationen
}

Detta möjliggör att lägga till nya metoder i interface utan att bryta befintliga implementationer.


Interface i olika språk

SpråkInterface-nyckelordDefault-metoderArv
Javainterfacedefaultextends (interface-arv)
TypeScriptinterfaceNej (men kan ha implementering)extends (flera)
PHPinterfacestatic metoderextends (interface-arv)
C#interfaceDefault: (flera interface)
PythonABCVia ABC / konkreta metoderMultiple inheritance
GointerfaceNejImplicit (duck typing)

Exempel — Interfaces i praktiken

Ett modernt exempel med Repository Pattern:

// Interface = kontrakt för datalagret
public interface UserRepository {
    User findById(int id);
    List<User> findAll();
    void save(User user);
    void delete(int id);
}

// Implementation för MySQL
public class MySQLUserRepository implements UserRepository {
    public User findById(int id) {
        // SQL: SELECT * FROM users WHERE id = ?
    }
    // ...
}

// Implementation för testning
public class InMemoryUserRepository implements UserRepository {
    private final Map<Integer, User> users = new HashMap<>();

    public User findById(int id) {
        return users.get(id);
    }
    // ...
}

// Applikationen bryr sig inte om vilken databas
UserRepository repo = new MySQLUserRepository(); // eller ny InMemory...()
User user = repo.findById(42);

Detta gör koden testbar, utbytbar och oberoende av infrastruktur.


Av Victor Hernandez från Bytebase.se