추상 팩토리 패턴 (Abstract Factory Pattern)
추상 팩토리 패턴은 객체를 그룹화 시켜 생성해야 할 경우 사용할 수 있는 패턴이다.
패턴 적용시 객체간 의존성이 제거되어 객체 그룹이 추가되거나 변경될때도 사용부(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 이름의 클래스들만 수정하면 된다.