Fabryka

Fabryka

Wzorzec Fabryka (ang. Factory) to jeden z najpopularniejszych wzorców kreacyjnych, który służy do tworzenia obiektów bez potrzeby jawnego określania ich konkretnego typu w kodzie. Fabryka ukrywa szczegóły tworzenia obiektów, zwracając produkty o wspólnym interfejsie, dzięki czemu klient nie musi znać dokładnej klasy tworzonego obiektu.

Kluczowe aspekty wzorca Fabryka:

  • Abstrakcja procesu tworzenia obiektów – Umożliwia tworzenie obiektów poprzez delegowanie tego zadania do specjalnej klasy fabryki.
  • Elastyczność i rozszerzalność – Można łatwo dodać nowe typy obiektów, bez modyfikowania kodu, który korzysta z fabryki.
  • Ukrywanie szczegółów implementacji – Klient (użytkownik fabryki) nie musi znać szczegółów tworzenia obiektów, co zmniejsza zależność od konkretnych klas.

Typy fabryk:

  • Prosta Fabryka – jedna metoda statyczna tworzy obiekty na podstawie przekazanego parametru.
  • Fabryka Abstrakcyjna – pozwala na tworzenie rodzin powiązanych obiektów (kilka klas fabryk).
  • Fabryka Metoda Wytwórcza – definiuje metodę, która może być nadpisywana przez klasy potomne, aby tworzyć różne obiekty.

Przykład bez wzorca Fabryka

Przykład gry, w której tworzymy różne jednostki (wojownika, łucznika, maga i rycerza) bez użycia wzorca fabryka

// Interfejs Jednostka
interface Unit {
    String getName();
    int getAttack();
    int getDefense();
    int getHealth();
}

// Klasa z implementacjami różnych jednostek
class Character implements Unit {
    private final String name;
    private final int attack;
    private final int defense;
    private final int health;

    public Character(String name, int attack, int defense, int health) {
        this.name = name;
        this.attack = attack;
        this.defense = defense;
        this.health = health;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getAttack() {
        return attack;
    }

    @Override
    public int getDefense() {
        return defense;
    }

    @Override
    public int getHealth() {
        return health;
    }
}

public class Game {
    public static void main(String[] args) {
        // Tworzenie jednostek z unikalnymi statystykami
        Unit warrior = new Character("Warrior", 12, 6, 120);
        System.out.println(warrior.getName() + " | Atak: " + warrior.getAttack() + " | Obrona: " + warrior.getDefense() + " | Zdrowie: " + warrior.getHealth());

        Unit archer = new Character("Archer", 8, 4, 90);
        System.out.println(archer.getName() + " | Atak: " + archer.getAttack() + " | Obrona: " + archer.getDefense() + " | Zdrowie: " + archer.getHealth());

        Unit mage = new Character("Mage", 15, 2, 70);
        System.out.println(mage.getName() + " | Atak: " + mage.getAttack() + " | Obrona: " + mage.getDefense() + " | Zdrowie: " + mage.getHealth());

        Unit knight = new Character("Knight", 20, 10, 180);
        System.out.println(knight.getName() + " | Atak: " + knight.getAttack() + " | Obrona: " + knight.getDefense() + " | Zdrowie: " + knight.getHealth());
    }
}

Wady z takiego podejścia:

  • Brak centralizacji: Logika tworzenia obiektów rozproszona w kodzie, co utrudnia zarządzanie.
  • Trudności w rozbudowie: Dodawanie nowych jednostek wymaga modyfikacji w klasie głównej, co zwiększa ryzyko błędów.
  • Mniejsza czytelność: Jawne podawanie statystyk w klasie głównej komplikuje kod.
  • Ograniczona elastyczność: Zmiany w sposobie tworzenia jednostek wymagają modyfikacji kodu głównego.
  • Powtarzalność kodu: Modyfikacje w statystykach wymagają zmiany w wielu miejscach, co zwiększa ryzyko błędów.
  • Zwiększone ryzyko błędów: Zmiany w klasie Character mogą wpływać na inne części kodu.
  • Trudność w testowaniu: Testowanie jednostkowe staje się bardziej skomplikowane.

Przykład z użyciem wzorca Fabryka

// Interfejs Jednostka
interface Unit {
    String getName();
    int getAttack();
    int getDefense();
    int getHealth();
}

// Klasa fabryki jednostek
class UnitFactory {
    public static Unit createWarrior() {
        return new Character("Warrior", 12, 6, 120);
    }

    public static Unit createArcher() {
        return new Character("Archer", 8, 4, 90);
    }

    public static Unit createMage() {
        return new Character("Mage", 15, 2, 70);
    }

    public static Unit createKnight() {
        return new Character("Knight", 20, 10, 180);
    }
}

// Klasa z implementacjami różnych jednostek
class Character implements Unit {
    private final String name;
    private final int attack;
    private final int defense;
    private final int health;

    public Character(String name, int attack, int defense, int health) {
        this.name = name;
        this.attack = attack;
        this.defense = defense;
        this.health = health;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getAttack() {
        return attack;
    }

    @Override
    public int getDefense() {
        return defense;
    }

    @Override
    public int getHealth() {
        return health;
    }
}

public class Game {
    public static void main(String[] args) {
        // Tworzenie jednostek przy użyciu fabryki, bez jawnego podawania statystyk
        Unit warrior = UnitFactory.createWarrior();
        System.out.println(warrior.getName() + " | Atak: " + warrior.getAttack() + " | Obrona: " + warrior.getDefense() + " | Zdrowie: " + warrior.getHealth());

        Unit archer = UnitFactory.createArcher();
        System.out.println(archer.getName() + " | Atak: " + archer.getAttack() + " | Obrona: " + archer.getDefense() + " | Zdrowie: " + archer.getHealth());

        Unit mage = UnitFactory.createMage();
        System.out.println(mage.getName() + " | Atak: " + mage.getAttack() + " | Obrona: " + mage.getDefense() + " | Zdrowie: " + mage.getHealth());

        Unit knight = UnitFactory.createKnight();
        System.out.println(knight.getName() + " | Atak: " + knight.getAttack() + " | Obrona: " + knight.getDefense() + " | Zdrowie: " + knight.getHealth());
    }
}

Podsumowanie

Użycie wzorca fabryka zwiększa elastyczność, organizację i jakość kodu, ułatwia rozwój oraz testowanie aplikacji, co prowadzi do lepszej efektywności zespołu programistycznego.

Scroll to Top