Factory (Fabryka)

Załóżmy, że chcemy napisać prosty program, który wypisuje jaką standardowo mamy pogodę w zależności od pory roku. W najprostszym podejściu tworzymy obiekty pór roku za pomocą operatora new bezpośrednio w kodzie. Takie rozwiązanie działa, ale ma istotne wady:

  • klasa korzystająca z obiektów musi znać szczegóły ich tworzenia,

  • jeśli chcemy zmienić logikę inicjalizacji obiektów, musimy modyfikować wiele fragmentów kodu,

  • kod staje się trudniejszy do utrzymania i mniej elastyczny.

Poniżej przedstawiam przykład kodu bez zastosowania wzorca projektowego Factory:

Napierw tworzymy interfejs Season:

interface Season {
    void showWeather();
}

Następnie tworzymy klasy odpowiadające porom roku i implementujemy interfejs. Np. klasa dla jesieni:

public class Autumn implements Season{
    public void showWeather(){
        System.out.println("Autumn - it's rainy and cloudy");
    }
}

Podobnie tworzymy klasy dla pozostałych pór roku. Na koniec, wywołujemy obiekty tych klas z użyciem wyżej wspomnianego operator new:

public class Demo {
    public static void main(String[] args){
        Season spring = new Spring();
        spring.showWeather();

        Season winter = new Winter();
        winter.showWeather();

        Season summer = new Summer();
        summer.showWeather();

        Season autumn = new Autumn();
        autumn.showWeather();
    }
}

Wzorzec Fabryka można podzielić na dwa rodzaje.

Jednym z nich jest metoda fabrykująca. Poniżej pokazuję jak ona działa, modyfikując przykład z porami roku:

Najpierw tworzymy klasę abstrakcyjną Factory:

package season;

public abstract class Factory {
    abstract public Season getSeason(SeasonType type);
}

Następnie enum SeasonType:

package season;

public enum SeasonType {
    AUTUMN, SUMMER, WINTER, SPRING;
}

Następnie implementujemy fabrykę konkretną:

package season;

public class WeatherFactory extends Factory{
    @Override
    public Season getSeason(SeasonType type) {
        switch(type){
            case SPRING:
                return new Spring();
            case AUTUMN:
                return new Autumn();
            case SUMMER:
                return new Summer();
            case WINTER:
                return new Winter();
            default:
                throw new UnsupportedOperationException("No such type");
        }
    }
}

W klasie Demo:

import season.*;

public class Demo {
    public static void main(String[] args){
        Factory factory = new WeatherFactory();

        Season spring = factory.getSeason(SeasonType.SPRING);
        spring.showWeather();
        Season winter = factory.getSeason(SeasonType.WINTER);
        winter.showWeather();
        Season summer = factory.getSeason(SeasonType.SUMMER);
        summer.showWeather();
        Season autumn = factory.getSeason(SeasonType.AUTUMN);
        autumn.showWeather();
    }
}

Drugi rodzaj fabryki, to fabryka abstrakcyjna. Tutaj mamy interfejs fabryki, który tworzy rodziny powiązanych obietków (w naszym przykładzie związane z porami roku), zamiast pojedynczego obiektu. Załóżmy, że dana pora roku może mieć cechy charakterystyczne, jak krajobraz, czy typ ubrań. Poniżej zmodyfikuję kod tak, aby uwzględnić ten wzorzec, dla uproszczenia i przejrzystości odwołam się tylko do jesieni i lata, ale oczywiście podobnie można zrobić dla pozostałych pór.

Najpierw tworzę interfejs dla krajobrazu:

package season;

public interface Landscape {
    public String description();
}

Podobnie robię dla typu ubrań:

package season;

public interface ClothesType {
    public String description();
}

Teraz dla jesieni, tworzę klasę, która implementuje klasę krajobrazu:

package season;

class AutumnLandscape implements Landscape {
    public String description() {
        return "Falling leaves, fog.";
    }
}

Teraz klasa implementująca typ ubrań dla jesieni:

package season;

public class AutumnClothesType implements ClothesType{
    public String description() {
        return "Wellies and a raincoat.";
    }
}

Dla pory roku lato robię analogicznie, oczywiście zmieniając jej opisy.

Następnie tworzę fabrykę abstrakcyjną:

package season;

public interface FactorySeasons {
    public Landscape createLandscape();
    public ClothesType createClothes();
}

Teraz należy utworzyć konkretne fabryki.

package season;

public class AutumnFactory implements FactorySeasons{
    public Landscape createLandscape() {
        return new AutumnLandscape();
    }

    public ClothesType createClothes() {
        return new AutumnClothesType();
    }
}
package season;

public class SummerFactory implements FactorySeasons {
    public Landscape createLandscape() {
        return new SummerLandscape();
    }

    public ClothesType createClothes() {
        return new SummerClothesType();
    }
}

Na koniec implementuję klasę Demo.

import season.*;

public class Demo {
    public static void showSeasonDescription(FactorySeasons factory) {
        Landscape landscape = factory.createLandscape();
        ClothesType clothes = factory.createClothes();

        System.out.println("Landscape: " + landscape.description());
        System.out.println("Clothes type: " + clothes.description());
    }

    public static void main(String[] args) {
        System.out.println("Summer: ");
        showSeasonDescription(new SummerFactory());
        System.out.println();
        System.out.println("Autumn: ");
        showSeasonDescription(new AutumnFactory());
    }
}

Przewijanie do góry