Factory Pattern
이번엔 Factory Pattern을 사용해봤다.
사진을 보면 알다시피 itemFactory가 다양한 아이템들을 만들고 있다.
왜 이런 방식이 필요한 걸까?
이론
Design principle 2
"Program to an interface, not to an implementation"
- by 대학교 4학년 강의 자료.
디자인 2 원칙이 뭐냐면 코딩할 때 new 연산자를 사용하지 말라는 거다.
엥? 이게 무슨 소릴까???
이해하기 위해 아래 예제 코드를 보자.
Item Buy(string name)
{
if(name == "WoodenSword")
return new WoodenSword();
else if(name == "StoneSword")
return new StoneSword();
else if(name == "StoneArmor")
return new StoneArmor();
return null;
}
플레이어가 아이템을 구매하는 코드고, new를 이용해 아이템을 리턴해주고 있다.
그리고 우리는 여기에 아이템을 50개 추가해야 한다. 벌써부터 머리가 아프다...
디자인 원칙 2는 이런 상황을 피하기 위해선 Implementation(e.g. new 연산자)를 멀리 하고 Interface를 사용해야 한다고 말하고 있다.
원칙 2대로 Interface를 사용하면 어떻게 될까?
ItemFactory itemFactory;
Item Buy(string name)
{
return itemFactory.Create(name);
}
이제 우리는 아이템을 50개 추가하든 1,000개 추가하든 맘 놓고 있을 수 있다.
근데 한 가지 의문이 든다.
어차피 itemFactory 안에서 new 연산자 쓰는 건 마찬가지 아닌가???
Design principle 1
"identify the aspects that vary and separate them from what stays the same."
- by 대학교 4 학년 강의 자료
디자인 1 원칙은 이렇게 말하고 있다. "반복되는 건 분리해!"
대충 코딩할 때 똑같은 구조가 세 번 이상 반복된다면 따로 class로 만들라는 얘기다.
반복되는 구조는 앞으로 확장될 가능성도 있다.
만약 반복되는 구조를 따로 분리하지 않는다면, 스크립트 하나가 몇 천 줄은 되지 않을까?
유지보수와 재사용성을 위해 반복되는 구조는 분리하자.
The Factory Method patteren
defines an abstract method for creatinging objects,
but lets subclasses decide which object to create.
Factory Method lets a class defer instantiation to subclasses.
팩토리 패턴은 서브 클래스가 어떤 객체를 생성할지 결정한다. 자 이게 무슨 얘기일까. 다음 코드를 보자.
public abstract class ItemFactory
{
public abstract Item CreateItem();
}
public class WeaponFactory : ItemFactory
{
public override Item CreateItem()
{
return new Weapon();
}
}
public class PortionFactory : ItemFactory
{
public override Item CreateItem()
{
return new Portion();
}
}
class Program
{
static void Main()
{
ItemFactory itemFactory;
itemFactory = new WeaponFactory();
Item weapon = itemFactory.CreateItem();
weapon.Use();
itemFactory = new PortionFactory();
Item portion = itemFactory.CreateItem();
portion.Use();
}
}
Main 부분을 보면 item 종류에 따라 다른 Factory로 아이템을 생성하고 있다.
어떤 장점이 있을까?
1. 객체 생성은 Factory 안에 캡슐화 되어 있다는 점
2. Factory를 무한정 확장할 수 있고 유지보수가 용이하다는 점
3. 결합도가 낮아진다는 점
4. Object Pooling 등과 결합할 수 있다는 점
개인적으로 결합도가 낮아진다는 점이 체감이 많이 된다.
한 번 팩토리 패턴 써봤다
근데 코드 작성하고 보니 위에서 말한 팩토리 패턴이랑 다르다.
내가 팩토리 패턴을 잘 몰랐어서 생겨버린 일이다.
생성자에서 각 아이템이 잘 생성되는지 검사한다.
Create Method는 제너릭(T)에 대응하는 인스턴스를 생성하고 반환한다.
인스턴스를 생성하기 위한 준비물로 Loader가 있다. Loader에서 Data를 가져와 Instance를 생성하는 구조다.
이를 그림으로 나타내면 다음과 같다.
ItemData는 원본 값을 가지고 있고, ItemInstance가 이를 토대로 이것저것 한다고 보면 된다.
한 가지 아쉬운 점은 이 코드에 큰 허점이 있다는 거다.
Loader가 Instance한테 데이터를 전달하는데, 타입이 Interface라 어떤 Data도 가리지 않고 받아들인다.
때문에 WeaponInstance가 WeaponData가 아닌 ArmorData를 들고 있을 수도 있다.
지금 당장은 Loader와 Instance를 잘 매칭시켜주는 것만으로 문제는 없지만, 후에 타입이 약 1,000가지가 넘으면 제어할 수 없게 될 거다.
그럼에도 프로젝트 규모가 작고 더 이상 타입이 늘어날 일도 없어 일단 구조를 유지하려고 한다.
이것말고도 제너릭을 남용한 느낌이 들기도 한다.
위 사진과 같이 사용법은 무척 간단하다.
원하는 타입을 제너릭으로 전달하고, 아이템 ID를 변수로 전달하면 그에 맞는 아이템이 생성된다.
마무리
이번 포스팅에서는 팩토리 패턴의 이론을 얕게 공부해보고 직접 구현해봤다. 디자인 패턴에 정해진 것은 없고 상황에 맞춰서 변형하면 된다고 들은 적이 있는데, 내가 잘 한 건지 모르겠다. 솔직히 이론에서 서브 클래스가 객채 생성을 정의한다는 부분이 아직 와닿진 않는다. 앞으로 몇 번 더 팩토리 패턴을 사용해 보면서 그 쓰임새를 알아보려고 한다.