Abstract Factory (Abstrakt Fabrik)

Introduktion

Abstract Factory är ett skapande mönster som låter dig skapa familjer av relaterade objekt utan att specificera deras konkreta klasser.


Problem

Du bygger ett UI-ramverk som ska stödja både ljust och mörkt tema. Varje tema har egna varianter av Button, Checkbox och TextField. Om du skapar komponenterna direkt med new LightButton() och new DarkCheckbox() riskerar du att blanda komponenter från olika teman — ett ljust tema-fönster med en mörk knapp.

Lösning

Abstract Factory definierar ett gränssnitt med en fabriksmetod per komponenttyp. Varje konkret fabrik implementerar hela familjen — LightThemeFactory skapar bara ljusa komponenter, DarkThemeFactory bara mörka. Klientkoden väljer fabrik en gång och skapar sedan alla komponenter via den.

När ska Abstract Factory användas?

  • När din kod ska fungera med flera familjer av relaterade produkter.
  • När du vill garantera att produkter från samma familj används tillsammans.
  • När du vill dölja implementationsdetaljerna för produktfamiljer.

Struktur

RollAnsvar
Abstract FactoryDeklarerar fabriksmetoder för varje produkttyp.
Concrete FactoryImplementerar fabriksmetoderna för en specifik produktfamilj.
Abstract ProductGränssnitt för en produkttyp.
Concrete ProductEn specifik implementation inom en familj.
KlientArbetar mot abstrakta fabriker och produkter.

Exempel — UI-teman (Java)

Scenariot: En applikation som kan rendera antingen ett ljust eller mörkt tema. Alla komponenter inom ett tema skapas av samma fabrik.

// ── Abstract Products ─────────────────────────────────────

public interface Button {
    void render();
    void onClick();
}

public interface Checkbox {
    void render();
    void toggle();
}

public interface TextField {
    void render();
    void onInput(String text);
}

// ── Light Theme — Concrete Products ──────────────────────

public class LightButton implements Button {
    @Override public void render()  { System.out.println("[ Ljus knapp  ]"); }
    @Override public void onClick() { System.out.println("Ljus knapp klickad"); }
}

public class LightCheckbox implements Checkbox {
    @Override public void render()         { System.out.println("☐ Ljus checkbox"); }
    @Override public void toggle()         { System.out.println("Ljus checkbox togglas"); }
}

public class LightTextField implements TextField {
    @Override public void render()              { System.out.println("[____________] Ljust textfält"); }
    @Override public void onInput(String text)  { System.out.println("Ljust textfält input: " + text); }
}

// ── Dark Theme — Concrete Products ───────────────────────

public class DarkButton implements Button {
    @Override public void render()  { System.out.println("█ Mörk knapp  █"); }
    @Override public void onClick() { System.out.println("Mörk knapp klickad"); }
}

public class DarkCheckbox implements Checkbox {
    @Override public void render()  { System.out.println("▣ Mörk checkbox"); }
    @Override public void toggle()  { System.out.println("Mörk checkbox togglas"); }
}

public class DarkTextField implements TextField {
    @Override public void render()             { System.out.println("[████████████] Mörkt textfält"); }
    @Override public void onInput(String text) { System.out.println("Mörkt textfält input: " + text); }
}

// ── Abstract Factory ──────────────────────────────────────

public interface UiFactory {
    Button    createButton();
    Checkbox  createCheckbox();
    TextField createTextField();
}

// ── Concrete Factories ────────────────────────────────────

public class LightThemeFactory implements UiFactory {
    @Override public Button    createButton()    { return new LightButton(); }
    @Override public Checkbox  createCheckbox()  { return new LightCheckbox(); }
    @Override public TextField createTextField() { return new LightTextField(); }
}

public class DarkThemeFactory implements UiFactory {
    @Override public Button    createButton()    { return new DarkButton(); }
    @Override public Checkbox  createCheckbox()  { return new DarkCheckbox(); }
    @Override public TextField createTextField() { return new DarkTextField(); }
}

// ── Klient ────────────────────────────────────────────────

public class LoginForm {
    private final Button    submitButton;
    private final Checkbox  rememberMe;
    private final TextField emailField;

    public LoginForm(UiFactory factory) {
        this.submitButton = factory.createButton();
        this.rememberMe   = factory.createCheckbox();
        this.emailField   = factory.createTextField();
    }

    public void render() {
        System.out.println("=== Inloggningsformulär ===");
        emailField.render();
        rememberMe.render();
        submitButton.render();
    }

    public void submit() {
        emailField.onInput("user@bytebase.se");
        submitButton.onClick();
    }
}

// ── Klientkod ─────────────────────────────────────────────

public class Application {
    public static void main(String[] args) {
        // Välj tema — kan komma från konfiguration eller användarinställning
        UiFactory factory = isDarkMode() ? new DarkThemeFactory() : new LightThemeFactory();

        LoginForm form = new LoginForm(factory);
        form.render();
        System.out.println();
        form.submit();
    }

    private static boolean isDarkMode() {
        return true; // Simulerar systeminställning
    }
}

Output:

=== Inloggningsformulär ===
[████████████] Mörkt textfält
▣ Mörk checkbox
█ Mörk knapp  █

Mörkt textfält input: user@bytebase.se
Mörk knapp klickad

Fördelar

  • Garanterar att produkter från samma familj används tillsammans.
  • Single Responsibility Principle — Produktskapandet är samlat i fabrikerna.
  • Open/Closed Principle — Lägg till en ny produktfamilj utan att ändra klientkoden.

Nackdelar

  • Att lägga till en ny produkttyp (t.ex. Tooltip) kräver ändringar i alla fabriker och alla fabriksgränssnitt.

Av Victor Hernandez från Bytebase.se