Wzorzec Fabryka

(ang. Factory)

Ten wzorzec kreacyjny wykorzystujemy do ułatwienia tworzenia nowych obiektów.  Używając go, oddelegowujemy tworzenie nowych obiektów do innych klas tak, by użytkownik musiał wywołać tylko jedną metodę i otrzymał gotowy obiekt.

Możemy zastosować dwa podejścia: metoda fabrykująca i fabryka abstrakcyjna.

</> Lepszy przykład niż wykład

1. Metoda fabrykująca

Będziemy produkować pojazdy np. samochody i samoloty.
Utworzymy abstrakcyjną klasę Vehicle z prywatnymi polami reprezentującymi moc silników i maksymalną prędkość naszych pojazdów. Dodamy konstruktor i gettery.

✘ Błędny kod
public abstract class Vehicle {
    private int enginePower;
    private int maximumVelocity;

    public Vehicle(int enginePower, int maximumVelocity) {
        this.enginePower = enginePower;
        this.maximumVelocity = maximumVelocity;
    }

    public int getEnginePower() {
        return enginePower;
    }

    public int getMaximumVelocity() {
        return maximumVelocity;
    }    
}

W klasach dziedziczących Car i Plane utworzymy konstruktory.

public class Car extends Vehicle {
    public Car(int enginePower, int maximumVelocity) {
        super(enginePower, maximumVelocity);
    }
}

public class Plane extends Vehicle {
    public Plane(int enginePower, int maximumVelocity) {
        super(enginePower, maximumVelocity);
    }
}

W main utworzymy sobie konkretne pojazdy. Jednak takie rozwiązanie niesie za sobą ryzyko pomyłek przy wpisywaniu konkretnych wartości pól przekazywanych do konstruktora.

public class Main {
    public static void main(String[] args) {
        Vehicle car = new Car(150, 190);
        Vehicle plane = new Plane(150000, 850);
    }
}
✔ Prawidłowy kod

Aby użytkownik nie miał możliwości tworzenia nowych pojazdów przy użyciu konstruktorów, wszystkie klasy oprócz Main umieścimy w osobnej paczce vehicles, a następnie zmienimy widoczność konstruktorów poprzez odpowiednie modyfikatory dostępu: w klasie Vehicle – protected (widoczność tylko w klasach rozszerzających), a w klasach dziedziczących – domyślny (widoczność tylko w klasach w obrębie paczki).

Klasa abstrakcyjna Vehicle

public abstract class Vehicle {
    private int enginePower;
    private int maximumVelocity;

    protected Vehicle(int enginePower, int maximumVelocity) {
        this.enginePower = enginePower;
        this.maximumVelocity = maximumVelocity;
    }

    public int getEnginePower() {
        return enginePower;
    }

    public int getMaximumVelocity() {
        return maximumVelocity;
    }

    @Override
    public String toString() {
        return "Vehicle{" +
                "enginePower=" + enginePower +
                ", maximumVelocity=" + maximumVelocity +
                '}';
    }
}

Klasy dziedziczące Car i Plane

public class Car extends Vehicle {
    Car(int enginePower, int maximumVelocity) {
        super(enginePower, maximumVelocity);
    }
}

public class Plane extends Vehicle {
    Plane(int enginePower, int maximumVelocity) {
        super(enginePower, maximumVelocity);
    }
}

Teraz możemy rozpocząć implementację metody fabrykującej.

Enum VehicleType

Ponieważ mamy dwa typu obiektów, które będziemy tworzyć w naszej fabryce, użyjemy enuma VehicleType (będzie określał nam, jaki typ pojazdu chcemy wyprodukować).

public enum VehicleType {
    CAR, PLANE;
}

Klasa abstrakcyjna Factory

Będzie ona posiadała jedną metodę publiczną abstrakcyjną createVehicle(VehicleType vehicleType) przyjmującą jeden argument typu VehicleType i zwracającą typ Vehicle.

public abstract class Factory {
    abstract public Vehicle createVehicle(VehicleType vehicleType);
}

Klasa VehicleFactory – konkretna fabryka

Rozszerza ona klasę Factory, implementuje metodę fabrykującą createVehicle(VehicleType vehicleType). 
Switch na podstawie otrzymanego typu pojazdu zwróci nam nowy obiekt odpowiedniego typu pojazdu. Domyślnie rzucimy wyjątek.

public class VehicleFactory extends Factory{
    @Override
    public Vehicle createVehicle(VehicleType vehicleType) {
        switch(vehicleType) {
            case CAR:
                return new Car(150, 190);
            case PLANE:
                return new Plane(150000, 850);
            default:
                throw new UnsupportedOperationException("Nie ma takiego pojazdu!");
        }
    }
}

Klasa Main

Teraz przy tworzeniu nowych pojazdów nie korzystamy już z konstruktorów.
Tworzymy nową fabrykę, a następnie tworzymy konkretne pojazdy korzystając z metody fabrykującej, w której podajemy typ pojazdu.

public class Main {
    public static void main(String[] args) {
        Factory factory = new VehicleFactory();
        Vehicle car = factory.createVehicle(VehicleType.CAR);
        Vehicle plane = factory.createVehicle(VehicleType.PLANE);

        System.out.println("Samochód: " + car);
        System.out.println("Samolot: " + plane);
    }
}

Metoda toString() wypisze nam wyprodukowane pojazdy.

Samochód: Vehicle{enginePower=150, maximumVelocity=190}
Samolot: Vehicle{enginePower=150000, maximumVelocity=850}
2. Fabryka abstrakcyjna

Fabryki abstrakcyjnej używamy, gdy potrzebujemy tworzyć spójne rodziny produktów (np. pojazdy lądowe, pojazdy powietrzne), a chcemy odizolować klienta od konkretnych klas tych rodzin, co ułatwia rozszerzenie aplikacji o nowe rodziny produktów (np. pojazdy morskie). 

✘ Błędny kod

Produkcja pojazdów się rozwija i dochodzimy do etapu, na którym musimy zastąpić naszą klasę abstrakcyjną Vehicle dwoma bardziej wyspecjalizowanymi klasami LandVehicle i AirVehicle.

public abstract class LandVehicle {
    private int enginePower;
    private int maximumVelocity;

    protected LandVehicle(int enginePower, int maximumVelocity) {
        this.enginePower = enginePower;
        this.maximumVelocity = maximumVelocity;
    }

    public int getEnginePower() {
        return enginePower;
    }

    public int getMaximumVelocity() {
        return maximumVelocity;
    }

    @Override
    public String toString() {
        return "Vehicle{" +
                "enginePower=" + enginePower +
                ", maximumVelocity=" + maximumVelocity +
                '}';
    }
}

public abstract class AirVehicle {
    private int enginePower;
    private int maximumVelocity;

    protected AirVehicle(int enginePower, int maximumVelocity) {
        this.enginePower = enginePower;
        this.maximumVelocity = maximumVelocity;
    }

    public int getEnginePower() {
        return enginePower;
    }

    public int getMaximumVelocity() {
        return maximumVelocity;
    }

    @Override
    public String toString() {
        return "Vehicle{" +
                "enginePower=" + enginePower +
                ", maximumVelocity=" + maximumVelocity +
                '}';
    }
}

Teraz moglibyśmy produkować różne pojazdy lądowe (np. ciężarówki, osobowe itp.) oraz powietrzne (np. samoloty, helikoptery itp.) tworząc ich klasy dziedziczące odpowiednio po LandVehicle i AirVehicle oraz dopisując do enuma.
Dla uproszczenia przykładu pozostaniemy przy jednym rodzaju pojazdu z każdej grupy.

public class Car extends LandVehicle {
    public Car(int enginePower, int maximumVelocity) {
        super(enginePower, maximumVelocity);
    }
}

public class Plane extends AirVehicle {
    public Plane(int enginePower, int maximumVelocity) {
        super(enginePower, maximumVelocity);
    }
}

Skoro mamy już dwie klasy abstrakcyjne naszego produktu, potrzebujemy zamiast jednej fabryki Factory zastosować dwie fabryki abstrakcyjne (LandFactory i AirFactory) 

public abstract class LandFactory {
    abstract public LandVehicle createVehicle(VehicleType vehicleType);
}

public abstract class AirFactory {
    abstract public AirVehicle createVehicle(VehicleType vehicleType);
}

oraz dwie różne implementacje tych fabryk (LandVehicle Factory i AirVehicle Factory).

public class LandVehicleFactory extends LandFactory {
    @Override
    public LandVehicle createVehicle(VehicleType vehicleType) {
        switch(vehicleType) {
            case CAR:
                return new Car(150, 190);
            //case TRUCK: ... etc.
            default:
                throw new UnsupportedOperationException("Nie ma takiego pojazdu!");
        }
    }
}

public class AirVehicleFactory extends AirFactory {
    @Override
    public AirVehicle createVehicle(VehicleType vehicleType) {
        switch (vehicleType) {
            case PLANE:
                return new Plane(150000, 850);
            //case HELICOPTER: ... etc.
            default:
                throw new UnsupportedOperationException("Nie ma takiego pojazdu!");
        }
    }
}

Czyli musieliśmy dodać kilka klas, a oprócz tego chcemy w naszej fabryce tworzyć dwa typy każdego rodzaju pojazdów: cywilne i wojskowe. Więc będziemy musieli nasze konkretne implementacje (LandVehicleFactory i AirVehicleFactory) rozbić na CivilLandVehicleFactory i MilitaryLandVehicleFactory oraz CivilAirVehicleFactory i MilitaryAirVehicleFactory.

Gdybyśmy postanowili poszerzyć produkcję o kolejny rodzaj pojazdów np. statki, musielibyśmy dorzucić:

  • jedną klasę abstrakcyjna pojazdów wodnych WaterVehicle
  • klasy konkretnych pojazdów wodnych: statek, łódź podwodna itp. dziedziczące po niej
  • jedną abstrakcyjną fabrykę WaterFactory
  • oraz dwie konkretne implementacje fabryki tworzące statki cywilne i wojskowe (CivilWaterVehicleFactory i MilitaryWaterVehicleFactory).
✔ Prawidłowy kod

Klasy abstrakcyjne naszych produktów LandVehicle i AirVehicle oraz ich konkretne implementacje Car i Plane zostają bez zmian.

public abstract class LandVehicle {
    private int enginePower;
    private int maximumVelocity;

    protected LandVehicle(int enginePower, int maximumVelocity) {
        this.enginePower = enginePower;
        this.maximumVelocity = maximumVelocity;
    }

    public int getEnginePower() {
        return enginePower;
    }

    public int getMaximumVelocity() {
        return maximumVelocity;
    }

    @Override
    public String toString() {
        return "Vehicle{" +
                "enginePower=" + enginePower +
                ", maximumVelocity=" + maximumVelocity +
                '}';
    }
}

public abstract class AirVehicle {
    private int enginePower;
    private int maximumVelocity;

    protected AirVehicle(int enginePower, int maximumVelocity) {
        this.enginePower = enginePower;
        this.maximumVelocity = maximumVelocity;
    }

    public int getEnginePower() {
        return enginePower;
    }

    public int getMaximumVelocity() {
        return maximumVelocity;
    }

    @Override
    public String toString() {
        return "Vehicle{" +
                "enginePower=" + enginePower +
                ", maximumVelocity=" + maximumVelocity +
                '}';
    }
}
public class Car extends LandVehicle {
    public Car(int enginePower, int maximumVelocity) {
        super(enginePower, maximumVelocity);
    }
}

public class Plane extends AirVehicle {
    public Plane(int enginePower, int maximumVelocity) {
        super(enginePower, maximumVelocity);
    }
}

Fabryka abstrakcyjna.

Zamiast dwóch osobnych klas abstrakcyjnych LandFactory i AirFactory tworzymy jedną klasę abstrakcyjną Factory. Posiada ona dwie metody abstrakcyjne zwracające odpowiednio typ LandVehicle bądź  AirVehicle.

public abstract class Factory {
    abstract public LandVehicle createLandVehicle(VehicleType vehicleType);
    abstract public AirVehicle createAirVehicle(VehicleType vehicleType);
}

Implementacje abstrakcyjnej fabryki

Tworzymy dwie implementacje abstrakcyjnej fabryki: CivilFactory i MilitaryFactory.

Cywilna fabryka implementuje metody tworząc samochód lub samolot z parametrami pojazdów cywilnych, a wojskowa – z parametrami odpowiednimi do celów wojskowych czyli samochody z silnikami o niższych osiągach ale o wyższej niezawodności i trwałości, natomiast samoloty np. myśliwce o niższej mocy i znacznie szybsze (bo dużo lżejsze od np. airbusów).

public class CivilFactory extends Factory {
    @Override
    public LandVehicle createLandVehicle(VehicleType vehicleType) {
        switch (vehicleType) {
            case CAR:
                return new Car(150, 190);
            //case TRUCK: ... etc.
            default:
                throw new UnsupportedOperationException("Nie ma takiego pojazdu!");
        }
    }

    @Override
    public AirVehicle createAirVehicle(VehicleType vehicleType) {
        switch (vehicleType) {
            case PLANE:
                return new Plane(150000, 850);
            //case HELICOPTER: ... etc.
            default:
                throw new UnsupportedOperationException("Nie ma takiego pojazdu!");
        }
    }
}

public class MilitaryFactory extends Factory {
    @Override
    public LandVehicle createLandVehicle(VehicleType vehicleType) {
        switch (vehicleType) {
            case CAR:
                return new Car(100, 120);
            //case TRUCK: ... etc.
            default:
                throw new UnsupportedOperationException("Nie ma takiego pojazdu!");
        }
    }

    @Override
    public AirVehicle createAirVehicle(VehicleType vehicleType) {
        switch (vehicleType) {
            case PLANE:
                return new Plane(55000, 2000);
            //case HELICOPTER: ... etc.
            default:
                throw new UnsupportedOperationException("Nie ma takiego pojazdu!");
        }
    }
}

Klasa Main

Używamy typów abstrakcyjnych, tworzymy dwie fabryki: CivilFactory i MilitaryFactory.
Teraz możemy już tworzyć pojazdy lądowe lub powietrzne używając odpowiednich fabryk cywilnych lub militarnych i przekazując w każdej z nich konkretny typ pojazdu (samochód, ciężarówka lub samolot, helikopter).

public class Main {
    public static void main(String[] args) {
        Factory civilFactory = new CivilFactory();
        Factory militaryFactory = new MilitaryFactory();

        LandVehicle civilCar = civilFactory.createLandVehicle(VehicleType.CAR);
        AirVehicle civilPlane = civilFactory.createAirVehicle(VehicleType.PLANE);

        LandVehicle militaryCar = militaryFactory.createLandVehicle(VehicleType.CAR);
        AirVehicle militaryPlane = militaryFactory.createAirVehicle(VehicleType.PLANE);

        System.out.println("Samochód cywilny: " + civilCar);
        System.out.println("Samolot cywilny: " + civilPlane);

        System.out.println("\nSamochód wojskowy: " + militaryCar);
        System.out.println("Samolot wojskowy: " + militaryPlane);
    }
}

Metoda toString() wypisze nam wyprodukowane pojazdy cywilne oraz wojskowe.

Samochód cywilny: Vehicle{enginePower=150, maximumVelocity=190}
Samolot cywilny: Vehicle{enginePower=150000, maximumVelocity=850}

Samochód wojskowy: Vehicle{enginePower=100, maximumVelocity=120}
Samolot wojskowy: Vehicle{enginePower=55000, maximumVelocity=2000}

Kolejny typ produktów

Firma nadal się rozwija i będziemy chcieli uruchomić produkcję kolejnego typu pojazdów np. wodnych.
Przy zastosowanym wzorcu fabryki abstrakcyjnej to dosyć proste i czytelne rozwiązanie.
Tworzymy klasę abstrakcyjną nowego typu produktu WaterVehicle.

public abstract class WaterVehicle {
    private int enginePower;
    private int maximumVelocity;

    protected WaterVehicle(int enginePower, int maximumVelocity) {
        this.enginePower = enginePower;
        this.maximumVelocity = maximumVelocity;
    }

    public int getEnginePower() {
        return enginePower;
    }

    public int getMaximumVelocity() {
        return maximumVelocity;
    }

    @Override
    public String toString() {
        return "Vehicle{" +
                "enginePower=" + enginePower +
                ", maximumVelocity=" + maximumVelocity +
                '}';
    }
}

Potrzebujemy konkretną implementację pojazdu tej klasy np. statek.

public class Ship extends WaterVehicle {
    protected Ship(int enginePower, int maximumVelocity) {
        super(enginePower, maximumVelocity);
    }
}

W enumie dodajemy typ SHIP.

public enum VehicleType {
    CAR, PLANE, SHIP;
}

Aby rozpocząć produkcję statków, w fabryce abstrakcyjnej dodajemy metodę abstrakcyjną tworzącą  pojazdy wodne.

public abstract class Factory {

    abstract public LandVehicle createLandVehicle(VehicleType vehicleType);

    abstract public AirVehicle createAirVehicle(VehicleType vehicleType);

    abstract public WaterVehicle createWaterVehicle(VehicleType vehicleType);

}

A następnie tworzymy tą metodę w naszych implementacjach konkretnych fabryk: cywilnej i wojskowej.

public class CivilFactory extends Factory {
    @Override
    public LandVehicle createLandVehicle(VehicleType vehicleType) {…}

    @Override
    public AirVehicle createAirVehicle(VehicleType vehicleType) {…}


    @Override
    public WaterVehicle createWaterVehicle(VehicleType vehicleType) {
        switch (vehicleType) {
            case SHIP:
                return new Ship(80000, 45);
            //case MOTORBOAT: ... etc.
            default:
                throw new UnsupportedOperationException("Nie ma takiego pojazdu!");
        }
    }
}

W Main możemy tworzyć pojazdy morskie korzystając z fabryk cywilnej lub wojskowej.

public class Main {
    public static void main(String[] args) {
        Factory civilFactory = new CivilFactory();
        Factory militaryFactory = new MilitaryFactory();

        LandVehicle civilCar = civilFactory.createLandVehicle(VehicleType.CAR);
        AirVehicle civilPlane = civilFactory.createAirVehicle(VehicleType.PLANE);

        LandVehicle militaryCar = militaryFactory.createLandVehicle(VehicleType.CAR);
        AirVehicle militaryPlane = militaryFactory.createAirVehicle(VehicleType.PLANE);

        WaterVehicle civilShip = civilFactory.createWaterVehicle(VehicleType.SHIP);
        WaterVehicle militaryShip = militaryFactory.createWaterVehicle(VehicleType.SHIP);

        System.out.println("Samochód cywilny: " + civilCar);
        System.out.println("Samolot cywilny: " + civilPlane);
        System.out.println("Statek cywilny: " + civilShip);

        System.out.println("\nSamochód wojskowy: " + militaryCar);
        System.out.println("Samolot wojskowy: " + militaryPlane);
        System.out.println("Statek wojskowy: " + militaryShip);
    }
}

Metoda toString() wypisze nam wyprodukowane pojazdy cywilne oraz wojskowe wraz z dodanym do każdej z tych grup nowym typem (pojazdem morskim).

Samochód cywilny: Vehicle{enginePower=150, maximumVelocity=190}
Samolot cywilny: Vehicle{enginePower=150000, maximumVelocity=850}
Statek cywilny: Vehicle{enginePower=80000, maximumVelocity=45}

Samochód wojskowy: Vehicle{enginePower=100, maximumVelocity=120}
Samolot wojskowy: Vehicle{enginePower=55000, maximumVelocity=2000}
Statek wojskowy: Vehicle{enginePower=54000, maximumVelocity=70}

Podsumowując, obie fabryki są wzorcami spotykanymi bardzo często (metoda fabrykująca częściej niż abstrakcyjne fabryki, które są bardziej domeną frameworków).

Fabryka abstrakcyjna ma dwie bardzo ważne cechy:

  • enkapsuluje tworzenie nowych obiektów – użytkownik, który korzysta z tej fabryki, nie ma pojęcia, jak pod spodem jest tworzony np. nasz samochód, jakie ma konkretne wartości pól
  • używamy abstrakcji

– gdy tworzymy fabrykę, to używamy typu Factory a nie konkretnej CivilFactory czy MilitaryFactory

Factory civilFactory = new CivilFactory();
Factory militaryFactory = new MilitaryFactory();

– gdy tworzymy pojazdy lądowe, nie zwracamy konkretnych samochodów tylko typ LandVehicle

LandVehicle militaryCar = militaryFactory.createLandVehicle(VehicleType.CAR);

– gdybyśmy tworzyli np. ciężarówkę, to też odwoływalibyśmy się do niego poprzez LandVehicle

LandVehicle militaryTruck = militaryFactory.createLandVehicle(VehicleType.TRUCK);

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *