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
| Aspekt | Abstrakt klass | Interface |
|---|---|---|
| Instansieras | Nej | Nej |
| Kan ha fält/state | Ja (protected int x) | Nej |
| Kan ha konstruktor | Ja | Nej |
| Kan ha implementation | Ja (konkreta metoder) | default-metoder |
| Multiple inheritance | Nej (enda superklass) | Ja (flera interface) |
| Synlighet | Alla nivåer | Alltid public |
| Använd när | Delad struktur + kontrakt | Endast 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åk | Interface-nyckelord | Default-metoder | Arv |
|---|---|---|---|
| Java | interface | default | extends (interface-arv) |
| TypeScript | interface | Nej (men kan ha implementering) | extends (flera) |
| PHP | interface | static metoder | extends (interface-arv) |
| C# | interface | Default | : (flera interface) |
| Python | ABC | Via ABC / konkreta metoder | Multiple inheritance |
| Go | interface | Nej | Implicit (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