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.