Przeznaczenie

Jest to wzorzec kontrukcyjny. Daje klientowi interfejs do tworzenia rodzin powiązanych ze sobą lub od siebie zależnych obiektów bez okreslania ich klas konkretnych.

Dlaczego jej potrzebujemy?

Powiedzmy, że jestesmy producentem drukarek. Każda z nich posiada różne parametry i dodatkowo możemy wybrać, czy dana drukarka ma drukować w kolorze, czy tylko monochromatycznie.

Poniżej zaprezentuję przykładowy kod:

abstract public class Printer {
    private Type type;
    private boolean wiFi;
    private boolean twoSidedPrinting;
    private PrintingColor color;

    public Printer(Type type, PrintingColor color, boolean wiFi, boolean twoSidedPrinting) {
        this.type = type;
        this.color = color;
        this.wiFi = wiFi;
        this.twoSidedPrinting = twoSidedPrinting;
    }

    public Type getType() {
        return type;
    }

    public PrintingColor getColor() {
        return color;
    }

    public boolean isWiFi() {
        return wiFi;
    }

    public boolean isTwoSidedPrinting() {
        return twoSidedPrinting;
    }

    @Override
    public String toString() {
        return "Printer{" +
                "type=" + type +
                ", wiFi=" + wiFi +
                ", twoSidedPrinting=" + twoSidedPrinting +
                ", color=" + color +
                '}';
    }
} 

Abstrakcyjna klasa Printer, która zawiera niektóre (na potrzeby naszego programu) parametry drukarek

public enum PrintingColor {
    MONO,
    COLOR
} 

Klasa typu Enum ustalająca konkretne kolory druku

public enum Type {
    LASER,
    INK
} 

Klasa typu Enum ustalająca konkretne typu drukarek

import Printer;
import PrintingColor;
import Type;

public class HP extends Printer {
    public HP(Type type, PrintingColor color, boolean wiFi, boolean twoSidedPrinting) {
        super(type, color, wiFi, twoSidedPrinting);
    }
} 

Konkretna implementacja klasy Printer

import Printer;
import PrintingColor;
import Type;

public class Epson extends Printer {
    public Epson(Type type, PrintingColor color, boolean wiFi, boolean twoSidedPrinting) {
        super(type, color, wiFi, twoSidedPrinting);
    }
} 

Kolejna implementacja klasy Printer

import Epson;
import HP;
import PrintingColor;
import Type;

public class Main {
    public static void main(String[] args) {
        HP Laser107W = new HP(Type.LASER, PrintingColor.COLOR, true, false);
        Epson ET2810 = new Epson(Type.INK, PrintingColor.MONO, false, false);
    }
} 

Stworzenie konkretnych obiektów klasy HP i Epson

W powyższym przykładzie klient musi wiedzieć jaka drukarka ma jakie parametry, żeby ją stworzyć. Przez co ma mozliwość zmieniania tych danych na wstępie, a tego nie chcemy tworząc konkretny model drukarki.

Wykorzystanie

Celem tego wzorca jest stworzenie abstrakcyjnej klasy Fabryka, która będzie deklarować metody tworzące porządane obiekty, a w konktretnych jej implementacjach będziemy tworzyć porządane obiekty. Klient będzie mógł wywołać jedynie metodę tworzącą konkretny model drukarki, a nie ustalać jej cechy.

Struktura

Implementacja

Powyższe klasy zamykamy w jednym package Printers. Dodatkowo tworzymy w nich kolejne dwie klasy typu enum określające nam nazwy modeli drukarek.

package Printers;

public enum HPModel {
    Laser107W,
    DeskJet3750
} 

Klasa typu Enum okreslająca modele drukarek klasy HP.

package Printers;

public enum EpsonModel {
    L3210,
    ET2810
}
 

Klasa typu Enum okreslająca modele drukarek klasy Epson.

Następnie tworzymy nowy package Factories zawierający naszą fabrykę abstrakcyjną i jej dwie konkretne implementacje.

package Factories;

import Printers.*;

abstract public class Factory {
    protected PrintingColor color;

    public Factory(){};

    public HP buildHP(HPModel model) {
        switch (model){
            case Laser107W -> {return new HP(Type.LASER, color, true, false);}
            case DeskJet3750 -> {return new HP(Type.INK, color, true, true);}
            default -> throw new UnsupportedOperationException("Nieznany model");
        }
    }


    public Epson buildEpson(EpsonModel model) {
        switch (model){
            case L3210 -> {return new Epson(Type.LASER, color, true, false);}
            case ET2810 -> {return new Epson(Type.INK, color, false, false);}
            default -> throw new UnsupportedOperationException("Nieznany model");
        }
    }
}
 

Fabryka abstrakcyjna zawierająca metody tworzące konkretne modele drukarek.

package Factories;

import Printers.*;

public class ColorPrinterFactory extends Factory {
    public ColorPrinterFactory() {
        this.color = PrintingColor.COLOR;
    }
}
 

Implementacja fabryki tworząca drukarki z kolorowym tuszem.

package Factories;

import Printers.*;

public class MonoPrinterFactory extends Factory{
    public MonoPrinterFactory(){
        this.color = PrintingColor.MONO;
    }
}
 

Implementacja fabryki tworząca drukarki z tuszem tylko czarno-białym.

import Factories.MonoPrinterFactory;
import Factories.ColorPrinterFactory;
import Factories.Factory;
import Printers.*;

public class Main {
    public static void main(String[] args) {

        Factory colorPrinterFactory = new ColorPrinterFactory();
        Factory monoFactory = new MonoPrinterFactory();

        Printer hp = colorPrinterFactory.buildHP(HPModel.DeskJet3750);
        System.out.println(hp.getColor());

        Printer epson = monoFactory.buildEpson(EpsonModel.ET2810);
        System.out.println(epson.getColor());
    }
} 

Wywołanie programu i stworzenie konkretnych obiektów.

Output

COLOR
MONO 

Podsumowanie

Zalety

  •  ukrywane są szczegóły implementacyjne klas reprezentujących konkretny produkt
  • ukryte są nazwy klas, przez co nie wymuszane jest ich zapamiętywanie
  • odizolowuje klienta od problemu określenia do której klasy należy obiekt
  • możliwość całkowitego ukrycia implementacji obiektów

Wady

  • tworzenie nowych podobiektów wymusza modyfikację klasy fabryki abstrakcyjnej i wszsytkich obiektów, które są przez nią tworzone