Wzorce Projektowe: Fabryka Abstrakcyjna (Abstract Factory)

Definicja

Fabryka abstrakcyjna jest kreacyjnym wzorcem projektowym, który pozwala tworzyć rodziny powiązanych ze sobą obiektów bez definiowania ich konkretnych klas. Wzorzec ten jest bardzo podobnych do wzorca metody fabrykującej,  a jego implementacja skupia się na super-klasie, która tworzy inne fabryki.

Przykład zastosowania wzorca Fabryki Abstrakcyjnej

Załóżmy, że chcemy zbudować globalną fabrykę rowerów. Moglibyśmy użyć do tego celu wzorca metody fabrykującej, jednak aplikacja taka byłaby odpowiednia tylko dla jednej lokalizacji. Ponieważ fabryka ma produkować rowery na skalę światową oprócz warstwy abstrakcyjnej, definiującej typ roweru, będziemy potrzebować kolejnej warstwy, która zidentyfikuje lokalizację i zastosuje odpowiednią implementację fabryki (bez ujawniania informacji jaka fabryka została użyta – jest to swego rodzaju zabezpieczenie przed wywołaniem niewłaściwej fabryki dla określonej lokalizacji). Wykorzystamy do tego celu wzorzec Fabryki Abstrakcyjnej.

Implementacja wzorca Fabryki Abstrakcyjnej

Zaczniemy od utworzenia typów wyliczeniowych dla rowerów oraz lokalizacji:

enum Location {
    DEFAULT, POLAND
}

enum BikeType {
    ROAD, MOUNTAIN, FOLDING
} 

Następnie zajmiemy się utworzeniem klasy abstrakcyjnej Bike oraz jej klasami rzeczywistymi:

abstract class Bike {
    Bike(BikeType type, Location location) {
        this.type = type;
        this.location = location;
    }

    abstract void construct();

    BikeType type = null;
    Location location = null;

    BikeType getType() {
        return type;
    }

    void setType(BikeType type) {
        this.type = type;
    }

    Location getLocation() {
        return location;
    }

    void setLocation(Location location) {
        this.location = location;
    }

    @Override
    public String toString() {
        return "Bike: " + type + " , Location: " + location;
    }
}

class RoadBike extends Bike {
    RoadBike(Location location) {
        super(BikeType.ROAD, location);
        construct();
    }
    @Override
    protected void construct() {
        System.out.println("Creating a road bike…");
    }
}

class MountainBike extends Bike {
    MountainBike(Location location) {
        super(BikeType.MOUNTAIN, location);
        construct();
    }
    @Override
    protected void construct() {
        System.out.println("Creating a mountain bike…");
    }
}

class FoldingBike extends Bike {
    FoldingBike(Location location) {
        super(BikeType.FOLDING, location);
        construct();
    }

    @Override
    void construct() {
        System.out.println("Creating a folding bike…");
    }
} 

Kolejnym krokiem będzie utworzenie klasy nadrzędnej dla naszych fabryk oraz zaimplementowanie klas rzeczywistych:

public abstract class BikeFactory {
    abstract Bike buildBike(BikeType type);
}

public class DefaultBikeFactory extends BikeFactory {
    @Override
    public Bike buildBike(BikeType type) {

        Bike bike = null;
        switch (type) {
            case ROAD:
                bike = new RoadBike(Location.DEFAULT);
                break;

            case MOUNTAIN:
                bike = new MountainBike(Location.DEFAULT);
                break;

            case FOLDING:
                bike = new FoldingBike(Location.DEFAULT);
                break;

            default:
                break;

        }
        return bike;
    }
}

public class PolandBikeFactory extends BikeFactory {
    @Override
    public Bike buildBike(BikeType type) {

        Bike bike = null;
        switch (type) {
            case ROAD:
                bike = new RoadBike(Location.POLAND);
                break;

            case MOUNTAIN:
                bike = new MountainBike(Location.POLAND);
                break;

            case FOLDING:
                bike = new FoldingBike(Location.POLAND);
                break;

            default:
                break;

        }
        return bike;
    }
} 

Ostatnim krokiem jest utworzenie generatora fabryk:

public class FactoryProducer {
    public static AbstractFactory getFactory(boolean
        default) {
        if (
            default) {
            return new DefaultBikeFactory();
        } else {
            return new PolandBikeFactory();
        }
    }
} 

Wzorzec Fabryki Abstrakcyjnej w akcji

Zobaczmy działanie aplikacji z użyciem wzorca fabryki abstrakcyjnej:

class BikeFactoryDemo {
    public static void main(String[] args) {
        BikeFactory bikeFactory = FactoryProducer.getFactory(false);
        System.out.println(bikeFactory.buildBike(BikeType.MOUNTAIN));
        bikeFactory = FactoryProducer.getFactory(true);
        System.out.println(bikeFactory.buildBike(BikeType.ROAD));
        System.out.println(bikeFactory.buildBike(BikeType.FOLDING));
    }
}

Wynik:
"Creating a mountain bike…"
"Bike: MOUNTAIN , Location: POLAND"
"Creating a road bike…"
"Bike: ROAD , Location: DEFAULT"
"Creating a folding bike…"
"Bike: FOLDING , Location: DEFAULT" 

Wzorzec Fabryka Abstrakcyjna całkowicie hermetyzuje proces tworzenia obiektów oraz odpowiedzialność za ich tworzenie – dzięki temu kod klienta nie ma pojęcia jakie klasy rzeczywiste są tworzone oraz jaka fabryka została do tego celu wykorzystana. Spójrzmy na diagram klas wzorca.

Diagram klas UML wzorca Fabryki Abstrakcyjnej

Różnice między wzorcami Metoda Fabrykująca oraz Fabryka Abstrakcyjna

Główna różnica między dwoma rodzajami fabryk polega na tym, że metoda fabryczna jest pojedynczą metodą, podczas gdy fabryka jest obiektem. Metodę wytwórczą można przesłonić w podklasie, zaś fabryka abstrakcyjna ma wiele metod fabrycznych. Ponadto metoda fabryczna wykorzystuje dziedziczenie, w fabryce abstrakcyjnej stosowana jest kompozycja.

Podsumowanie

Fabryka Abstrakcyjna umożliwia wykorzystanie interfejsu abstrakcyjnego do tworzenia zestawu spokrewnionych produktów bez znajomości szczegółów dotyczących tworzonych obiektów. Dzięki takiemu rozwiązaniu kod klienta jest całkowicie pozbawiony sprzężenia od jakichkolwiek szczegółów implementacji produktów rzeczywistych.

Leave a Comment

Your email address will not be published. Required fields are marked *