Fabryka jest wzorcem kreacyjnym, pozwalającym w uporządkowany sposób tworzyć różne obiekty implementujące ten sam interfejs.

Problem:

Wyobraźmy sobie, że tworzymy grę w której nasza postać musi walczyć z przeróżnymi potworami, żeby zdobywać doświadczenie.

Z doświadczenia wiemy, że takie gry mogą posiadać setki rodzajów takich przeciwników.

Jak może wyglądać kod bez użycia wzorca projektowego fabryka?

class Monster:

package monsters;

public abstract class Monster {

    private String name;
    private int experience;
    private int hp;
    private int damage;
    private int mana;
    private int speed;


    public Monster(String name, int experience, int hp, int damage, int mana, int speed) {
        this.name = name;
        this.experience = experience;
        this.hp = hp;
        this.damage = damage;
        this.mana = mana;
        this.speed = speed;
    }

    public abstract void attack();

    public abstract void walk();


    public String getName() {
        return name;
    }

    public int getExperience() {
        return experience;
    }

    public int getHp() {
        return hp;
    }

    public int getDamage() {
        return damage;
    }

    public int getMana() {
        return mana;
    }

    public int getSpeed() {
        return speed;
    }
}

class Cyclop:

package monsters;

public class Cyclop extends Monster {

    public Cyclop(String name, int experience, int hp, int damage, int mana, int speed) {
        super(name, experience, hp, damage, mana, speed);
    }

    @Override
    public void attack() {
        System.out.println("Atakuję swoją maczugą");
    }

    @Override
    public void walk() {
        System.out.println("Poruszam się z prędkością: " + getSpeed());
    }
}

class Demon:

package monsters;

public class Demon extends Monster{

    public Demon(String name, int experience, int hp, int damage, int mana, int speed) {
        super(name, experience, hp, damage, mana, speed);
    }

    @Override
    public void attack() {
        System.out.println("Atakuje swoimi szponami");
    }

    @Override
    public void walk() {
        System.out.println("Poruszam się z prędkością: " + getSpeed());
    }
}

class Dragon:

package monsters;

public class Dragon extends Monster {

    public Dragon(String name, int experience, int hp, int damage, int mana, int speed) {
        super(name, experience, hp, damage, mana, speed);
    }

    @Override
    public void attack() {
        System.out.println("Zieje ogniem");
    }

    @Override
    public void walk() {
        System.out.println("Poruszam się z prędkością: " + getSpeed());
    }
}

class Minotaur:

package monsters;

public class Minotaur extends Monster{

    public Minotaur(String name, int experience, int hp, int damage, int mana, int speed) {
        super(name, experience, hp, damage, mana, speed);
    }

    @Override
    public void attack() {
        System.out.println("Atakuje swoimi rogami");
    }

    @Override
    public void walk() {
        System.out.println("Poruszam się z prędkością: " + getSpeed());
    }
}

class Skeleton:

package monsters;

public class Skeleton extends Monster{

    public Skeleton(String name, int experience, int hp, int damage, int mana, int speed) {
        super(name, experience, hp, damage, mana, speed);
    }

    @Override
    public void attack() {
        System.out.println("Atakuje swoimi czarami");
    }

    @Override
    public void walk() {
        System.out.println("Poruszam się z prędkością: " + getSpeed());
    }
}

class Zombie:

package monsters;

public class Zombie extends Monster{

    public Zombie(String name, int experience, int hp, int damage, int mana, int speed) {
        super(name, experience, hp, damage, mana, speed);
    }

    @Override
    public void attack() {
        System.out.println("Zjadam mózgi");
    }

    @Override
    public void walk() {
        System.out.println("Poruszam się z prędkością: " + getSpeed());
    }
}

class Main:

import monsters.*;

public class Main {

    public static void main(String[] args) {

        Monster skeleton = new Skeleton("Szkieletor", 0, 100, 20,10,5);
        Monster demon = new Demon("Demon", 0, 300, 50,30,15);
        Monster dragon = new Dragon("Smok", 0, 200, 40,20,15);
        Monster zombie = new Zombie("Zombie", 0, 80, 20,0,5);
        Monster minotaur = new Minotaur("Minotaur", 0, 90, 30,0,10);
        Monster cyclop = new Cyclop("Cyklop", 0, 120, 30,20,20);

    }
}

 

A teraz wyobraź sobie, że musisz tworzyć w dalszej części kodu jeszcze kilkanaście takich potworów, pamiętając za każdym razem jakie statystki posiada każdy z nich. Problematyczne prawda? Z pomocą przychodzi nam wzorzec Fabryka,
który pomoże nam w prosty sposób utworzyć nowe obiekty przez nas bez użycia operatora „new”. Nie będziemy musieli martwić się na przykład o to czy stworzony przez nas kolejny „Szkieletor” nie okaże się siliniejszy od „Demona”.

Rozwiązanie (pominę tutaj kod który nie został zmieniony przy implementacji wzorca):

interface Factory:

package monsters;

public interface Factory {

    Monster createMonster(MonsterType monsterType);
}

enum MonsterType:

package monsters;

public enum MonsterType {
    CYCLOP,
    DEMON,
    DRAGON,
    MINOTAUR,
    SKELETON,
    ZOMBIE
}

class MonsterFactory:

package monsters;

public class MonsterFactory implements Factory{

    @Override
    public Monster createMonster(MonsterType monsterType) {
        switch(monsterType){
            case CYCLOP:
                return new Cyclop("Cyklop", 0, 120, 30,20,20);
            case DEMON:
                return new Demon("Demon", 0, 300, 50,30,15);
            case DRAGON:
                return new Dragon("Smok", 0, 200, 40,20,15);
            case MINOTAUR:
                return new Minotaur("Minotaur", 0, 90, 30,0,10);
            case SKELETON:
                return new Skeleton("Szkieletor", 0, 100, 20,10,5);
            case ZOMBIE:
                return new Zombie("Zombie", 0, 80, 20,0,5);
            default:
                throw new UnsupportedOperationException("Wybrałeś nieprawidłową klasę potwora");
        }
    }
}

class Main:

import monsters.*;

public class Main {

    public static void main(String[] args) {

        Factory monsterFactory = new MonsterFactory();

        Monster skeleton = monsterFactory.createMonster(MonsterType.SKELETON);

        Monster cyclop = monsterFactory.createMonster(MonsterType.CYCLOP);

        Monster demon = monsterFactory.createMonster(MonsterType.DEMON);
        

    }
}

Jak widzisz wzorzec Fabryka a dokładniej metoda wytwórcza należy raczej do prostszych w implementacji wzorców. U nas widoczne zmiany to dodanie interfejsu „Factory” w którym tworzymy metodę „createMonster”, która przyjmuję enum „MonsterType”. Dodajemy również klasę właściwej fabryki „MonsterFactory” która implementuje nasz interfejs „Factory” i nadpisuje jej metodę. Jeśli chodzi o samo tworzenie obiektu w metodzie „createMonster” to w naszym przykładzie używamy warunku wielokrotnego wyboru, ale równie dobrze mogłaby być to instrukcja warunkowa if.

Mam nadzieję, że pomogłem Ci zrozumieć wzorzec projektowy Fabryka.