소프트웨어 아키텍처/디자인패턴

추상 팩토리 패턴 (Abstract Factory Pattern)

파란막대 2022. 7. 7. 01:19

추상 팩토리 패턴은 객체를 그룹화 시켜 생성해야 할 경우 사용할 수 있는 패턴이다. 

패턴 적용시 객체간 의존성이 제거되어 객체 그룹이 추가되거나 변경될때도 사용부(Client)에서는 변경이 없게한다.

 

추상 팩토리 클래스 다이어그램

상황

RPG 게임을 만들기로 하고 아래와 같이 특징을 정의하였다.

 

[진영]

인간진영

오크진영

 

[종족]
▶ 인간
말하다 - "안녕"
공격하다 - "칼로 공격"

▶ 오크
말하다 - "orrr...."
공격하다 - "도끼로 공격"


[탈 것]
▷ 말
짖다 - "히히잉"
먹다 - "풀을 먹다"

▷ 늑대
짖다 - "아우~"
먹다 - "고기를 먹다"

 

캐릭터를 생성할 때 인간진영을 선택하면 인간과 말을,  오크진영을 선택하면 오크와 늑대를  함께 지급하기로 한다.

 

이제  코드를 작성해보자.

패턴 적용

    //종족 인터페이스
    public interface Tribe {
        public String say(); //말하다
        public String attack();  //공격하다
    }

    //인간 클래스
    public class Human implements Tribe {
        @Override
        public String say() {
            return "안녕";
        }

        @Override
        public String attack() {
            return "칼로 공격";
        }
    }

    //오크 클래스
    public class Oak implements Tribe {

        @Override
        public String say() {
            return "orrrrrr....";
        }

        @Override
        public String attack() {
            return "도끼로 공격";
        }

    }
    //탈 것 
    public interface Animal {
        public String bark(); //짖다
        public String eat();  //먹다
    }


    //말
    public class Horse implements Animal {

        @Override
        public String bark() {
            return "히이잉";
        }

        @Override
        public String eat() {
            return "풀을 먹다";
        }

    }

    //늑대
    public class wolf implements Animal {

        @Override
        public String bark() {
            return "아우";
        }

        @Override
        public String eat() {
            return "고기를 먹다";
        }

    }

 

팩토리 메서드 페턴을 사용하여 종족과 탈것을 담당하는 클래스를 각 각 만들어보자.

    //종족 생성을 담당하는 팩토리 클래스
    public class TribeFactory {

        public Tribe createTribe(TRIBE_TYPE tribeType) {
            Tribe tribe = null;

            if(tribeType == TRIBE_TYPE.HUMAN) {
                tribe = new Human();
            } else if (tribeType == TRIBE_TYPE.OAK) {
                tribe = new Oak();
            }

            return tribe;
        }
    }


    //탈 것 생성을 담당하는 팩토리 클래스
    public class AnimalFactory {

        public Animal createAnimal(ANIMAL_TYPE animalType) {

            Animal animal = null;

            if (animalType == ANIMAL_TYPE.HORSE) {
                animal = new Horse();
            } else if (animalType == ANIMAL_TYPE.WOLF) {
                animal = new Wolf();
            }

            return animal;
        }
    }

 

이제 캐릭터를 생성하는 클래스를 만들자.

    //사용자 캐릭터 정보를 출력하는 클래스
    public class Character {
        Tribe tribe = null;
        Animal animal = null;

        public Character(String race) {

            TribeFactory tribeFactory = new TribeFactory();
            AnimalFactory animalFactory = new AnimalFactory();

            if(race == "인간진영") {
                tribe = tribeFactory.createTribe(TRIBE_TYPE.HUMAN); 
                animal = animalFactory.createAnimal(ANIMAL_TYPE.HORSE);
            } else if (race == "오크진영") {
                tribe = tribeFactory.createTribe(TRIBE_TYPE.OAK); 
                animal = animalFactory.createAnimal(ANIMAL_TYPE.WOLF);			
            }
        }

        public void action() {
            System.out.println("말하다 : " + tribe.say());
            System.out.println("공격하다 : " + tribe.attack());

            System.out.println("짖다 : " + animal.bark());
            System.out.println("먹다 : " + animal.eat());
        }
    }

Character 생성자에서 인간진영과 오크진영 정보를 입력받아 종족과 탈것을 생성한다. 

그리고 action 메서드를 호출하면 종족과 탈것이 행동을 하게 한다.

 

이제 캐릭터를 생성하고 액션을 지시한다.

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Character character = new Character("오크진영");
        character.action();
    }

결과는 아래와 같다.

    말하다 : orrrrrr....
    공격하다 : 도끼로 공격
    짖다 : 아우
    먹다 : 고기를 먹다

여기까지만 해도 코드가 많이 간결해졌으나, Character 생성자에서 종족과 탈것을 생성하는 부분은 수정이 필요하다.

이것이 왜 문제가 되는가?

 

먼저 Character 클래스는 캐릭터의 기능을 출력하기 위해 만들어졌으나, 내부에서 TribeFactory와 AnimalFactory 를 생성함으로 의존관계가 형성되었다. 이로써 생성에도 관여하게 되어, 종족과 탈것의 변경시 Character 클래스도 수정이 필요하게 된 것이다.

이 의존관계를 제거해보자

 

추상 팩토리 클래스 생성

먼저 생성되는 객체 그룹을 정의할 추상 클래스(AbstractRaceFactory)를 선언하고 추상클래스를 상속받아 실체화할 클래스(HumanRaceFactory, OakRaceFactory)를 작성한다.

    public interface AbstractRaceFactory {
        public Tribe createTribe();
        public Animal createAnimal();
    }

    //인간 진영 캐릭터 세트 생성 
    public class HumanRaceFactory implements AbstractRaceFactory{

        @Override
        public Tribe createTribe() {
            return new Human();
        }

        @Override
        public Animal createAnimal() {
            return new Horse();
        }

    }

    //오크 진영 캐릭터 세트 생성
    public class OakRaceFactory implements AbstractRaceFactory {

        @Override
        public Tribe createTribe() {
            return new Oak();
        }

        @Override
        public Animal createAnimal() {
            return new Wolf();
        }

    }

    //진영에 따라 TribeFactory와 AnimalFactory 객체가 담긴 RaceFactory를 생성
    public class RaceCreateFactory {

        public AbstractRaceFactory createRace(String race) {
            AbstractRaceFactory raceFactory = null;

            if(race == "인간진영") {
                raceFactory =  new HumanRaceFactory();
            } else if (race == "오크진영") {
                raceFactory =  new OakRaceFactory();
            }

            return raceFactory;
        }
    }

 

그리고 Character 클래스 내부에서 객체를 생성하던 부분을 다른 객체에서 호출하는 방식으로 변경한다. (의존성 제거)

    //사용자 캐릭터 정보를 출력하는 클래스
    public class Character {
        Tribe tribe = null;
        Animal animal = null;

        public Character(String race) {
            RaceCreateFactory raceCreateFactory = new RaceCreateFactory();
            AbstractRaceFactory raceFactory = raceCreateFactory.createRace(race);
            tribe = raceFactory.createTribe();
            animal = raceFactory.createAnimal();

        }

        public void action() {
            System.out.println("말하다 : " + tribe.say());
            System.out.println("공격하다 : " + tribe.attack());

            System.out.println("짖다 : " + animal.bark());
            System.out.println("먹다 : " + animal.eat());
        }
    }

이제 종족과 탈것이 추가된다고 하더라도, Characer 클래스의 수정없이 Factory 이름의 클래스들만 수정하면 된다.