
Factory 1. Factory Method
소프트웨어 개발에서는 객체를 생성하는 방식이 점점 복잡해질 때가 많습니다. 초기에는 단순히 static method를 사용하여 객체를 생성할 수 있지만, 복잡한 로직이 필요하거나 다양한 객체 타입을 생성해야 하는 경우가 생기면 Factory Method 패턴이 매우 유용해집니다.
이번 글에서는 Factory Method 패턴이 필요한 이유와 이를 사용해야 하는 Code Smell 상황을 설명하고, 구현 예시를 통해 패턴을 적용하는 방법을 살펴보겠습니다.
Static Method를 이용한 간단한 객체 생성
객체를 생성할 때 가장 간단한 방법은 static method를 사용하는 것입니다. 객체 생성을 위한 정적 메서드를 클래스 내부에 두고, 해당 메서드로만 객체를 생성하도록 제한할 수 있습니다. 예를 들어, 아래 코드는 BattleCard를 생성하는 가장 간단한 방법을 보여줍니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BattleCard : ICard
{
readonly int value;
readonly string name;
public string Name => name;
private BattleCard(string name, int value)
{
this.name = name;
this.value = value;
}
public static BattleCard Create(string name, int value)
{
return new BattleCard(name, value);
}
}
여기서 생성자는 private
으로 설정되어, 외부에서 객체를 직접 생성하지 못하게 하고 static method인 Create()
메서드를 통해서만 객체를 생성하도록 강제합니다. 이는 간단한 객체 생성의 경우 매우 유용합니다. 하지만 객체 생성 로직이 복잡해지거나 다양한 종류의 객체를 생성해야 할 때는 이러한 접근 방식이 한계에 봉착할 수 있습니다.
Factory Method 패턴의 필요성
Factory Method 패턴은 객체 생성을 특정 클래스에 위임하여 객체 생성 로직을 확장하거나 유연하게 처리할 수 있도록 도와주는 디자인 패턴입니다. 이 패턴을 통해 객체 생성 방식을 캡슐화할 수 있으며, 객체 생성 과정에 추가적인 로직이나 다양한 조건을 포함할 수 있습니다.
Code Smell: Factory Method 패턴이 필요한 순간
1. 긴 생성자 목록 (Long Constructor List)
- 상황: 생성자에 너무 많은 매개변수가 있어 객체 생성이 복잡해지는 경우.
- 해결: Factory Method를 사용하여 생성 로직을 간소화하고, 매개변수 관리를 일관되게 할 수 있습니다.
2. 서로 다른 하위 클래스의 인스턴스화 필요 (Need to Instantiate Different Subclasses)
- 상황: 서로 다른 하위 클래스 객체를 생성해야 할 때, 클라이언트 코드가 이를 관리하는 것이 복잡해질 수 있습니다.
- 해결: Factory Method를 사용하여 클라이언트 코드에서 하위 클래스를 직접 다루지 않고, 팩토리가 적절한 객체를 생성하게 할 수 있습니다.
3. 객체 생성 로직의 복잡성 (Complex Object Creation Logic)
- 상황: 객체 생성 과정에서 복잡한 설정이나 초기화가 필요한 경우.
- 해결: Factory Method를 통해 객체 생성과 설정을 캡슐화하고, 클라이언트 코드에서는 간단하게 객체를 생성할 수 있습니다.
4. 조건에 따라 다른 객체를 생성해야 하는 경우 (Creating Different Objects Based on Conditions)
- 상황: 조건에 따라 서로 다른 객체를 생성해야 할 때.
- 해결: Factory Method는 조건에 따라 적절한 객체를 생성하는 로직을 포함하여, 클라이언트 코드의 복잡성을 줄일 수 있습니다.
Factory Method 패턴 구현 예시
아래 코드는 Factory Method 패턴을 통해 BattleCard 객체를 생성하는 과정을 보여줍니다. 팩토리 클래스는 객체 생성과 등록을 한 번에 처리하여, 객체 생성의 복잡한 과정을 감추고 더 유연하게 처리합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public interface ICard
{
public string Name {get;}
public int Play();
}
public class BattleCard : ICard
{
readonly int value;
readonly string name;
public string Name => name;
public int Play()
{
// 카드의 능력치 계산 로직
}
public BattleCard(string name, int value)
{
this.name = name;
this.value = value;
}
}
public abstract class CardFactory
{
public abstract ICard CreateCard();
}
public class BattleCardFactory : CardFactory
{
public override ICard CreateCard(string name, int value)
{
BattleCard battleCard = new BattleCard(string, value);
CardManager.RegisterCard(battleCard);
return battleCard;
}
}
코드 설명
- CardFactory는 추상 클래스로, 다양한 카드를 생성할 수 있는 팩토리 메서드를 정의합니다. 이를 통해 새로운 카드를 추가할 때마다 팩토리 메서드만 수정하면 됩니다.
- BattleCardFactory는 구체적인 팩토리로, BattleCard 객체를 생성하고 CardManager에 등록하는 역할을 합니다.
- CardManager.RegisterCard()는 생성된 카드를 중앙에서 관리하기 위한 로직으로, 생성뿐만 아니라 카드 등록이나 기타 부가 작업을 처리합니다.
Factory Method 패턴의 장점
-
객체 생성과 로직 분리
팩토리 메서드를 사용하면 객체 생성과 로직이 분리되기 때문에 클라이언트 코드가 더 간결해집니다. 클라이언트는 단순히 팩토리를 호출하여 객체를 생성하고, 복잡한 로직은 팩토리 클래스가 처리하게 됩니다. -
유연한 객체 생성
팩토리 메서드를 사용하면 다양한 하위 클래스 객체를 유연하게 생성할 수 있습니다. 새로운 하위 클래스를 추가할 때 팩토리 메서드만 수정하면 클라이언트 코드를 변경하지 않고도 확장할 수 있습니다. -
코드의 확장성과 유지보수성 향상
객체 생성 로직을 캡슐화하므로, 유지보수가 용이하고 코드의 확장성이 높아집니다. 클라이언트 코드에서 객체 생성 방식을 변경하지 않고도 새로운 기능을 추가할 수 있습니다.
결론
Factory Method 패턴은 객체 생성 로직을 캡슐화하고 확장 가능하게 만드는 강력한 도구입니다. 단순한 객체 생성에서는 static method가 충분할 수 있지만, 복잡한 객체 생성이나 다양한 하위 클래스의 인스턴스화가 필요할 때는 Factory Method 패턴이 코드의 유지보수성과 유연성을 크게 향상시킵니다.