Abstract Factory (추상팩토리) 에 대해 설명을 기재해 봤습니다.
Abstract Factory (추상팩토리) 는 구체적인 클래스를 지정하지 않고 전체 제품군을 생성하는 문제를 해결하는 크리에이티브 디자인 패턴입니다.
Abstract Factory 는 모든 개별 제품을 생성하기 위한 인터페이스를 정의하지만 실제 제품 생성은 구체적인 팩토리 클래스에 맡깁니다. 각 팩토리 유형은 특정 제품 종류에 해당합니다.
클라이언트 코드는 생성자 호출(새 연산자)을 사용하여 제품을 직접 생성하는 대신 팩토리 객체의 생성 메서드를 호출합니다. 팩토리는 단일 제품 이형 상품에 해당하므로 모든 제품이 호환됩니다.
클라이언트 코드는 팩토리 및 제품의 추상 인터페이스를 통해서만 작동합니다. 따라서 클라이언트 코드는 공장 개체에 의해 생성된 모든 제품 이형 상품과 함께 작동할 수 있습니다. 구체적인 팩토리 클래스를 새로 생성하여 클라이언트 코드에 전달하기만 하면 됩니다.
사용 예제: 추상 팩토리 패턴은 C# 코드에서 매우 일반적입니다. 많은 프레임워크와 라이브러리에서 표준 컴포넌트를 확장하고 사용자 지정하는 방법을 제공하기 위해 이 패턴을 사용합니다.
식별: 이 패턴은 팩토리 객체를 반환하는 메서드로 쉽게 알아볼 수 있습니다. 그런 다음 팩토리는 특정 하위 컴포넌트를 만드는 데 사용됩니다.
* 집중해야 할 부분 살펴보기
어떤 클래스로 구성되어 있는가?
이 클래스들은 어떤 역할을 수행하나요?
패턴의 요소들은 어떤 방식으로 관련되어 있는가?
아래는 C#에서 Abstract Factory 패턴을 활용하여 다른 종족 간에 싸우고 이기고 지는 게임을 만들기 위한 예제 코드입니다:
using System;
// Abstract Factory 인터페이스
interface IUnitFactory
{
Unit CreateUnit();
}
// 유닛 모델
abstract class Unit
{
public abstract string Name { get; }
public int Health { get; set; }
public void Attack(Unit target)
{
Console.WriteLine($"{Name}이(가) {target.Name}을(를) 공격합니다.");
target.Health -= 10;
if (target.Health <= 0)
{
Console.WriteLine($"{target.Name}이(가) 파괴되었습니다.");
}
}
}
// 테란 종족 유닛 Factory
class TerranUnitFactory : IUnitFactory
{
public Unit CreateUnit()
{
Console.WriteLine("테란 종족 유닛 생성 중...");
return new TerranUnit();
}
}
// 저그 종족 유닛 Factory
class ZergUnitFactory : IUnitFactory
{
public Unit CreateUnit()
{
Console.WriteLine("저그 종족 유닛 생성 중...");
return new ZergUnit();
}
}
// 프로토스 종족 유닛 Factory
class ProtossUnitFactory : IUnitFactory
{
public Unit CreateUnit()
{
Console.WriteLine("프로토스 종족 유닛 생성 중...");
return new ProtossUnit();
}
}
// 테란 종족 유닛
class TerranUnit : Unit
{
public override string Name => "테란 마린";
public TerranUnit()
{
Health = 100;
}
}
// 저그 종족 유닛
class ZergUnit : Unit
{
public override string Name => "저그 저글링";
public ZergUnit()
{
Health = 80;
}
}
// 프로토스 종족 유닛
class ProtossUnit : Unit
{
public override string Name => "프로토스 질럿";
public ProtossUnit()
{
Health = 120;
}
}
// 클라이언트 코드
class GameClient
{
private IUnitFactory playerFactory;
private IUnitFactory enemyFactory;
public GameClient(IUnitFactory playerFactory, IUnitFactory enemyFactory)
{
this.playerFactory = playerFactory;
this.enemyFactory = enemyFactory;
}
public void PlayGame()
{
Unit playerUnit = playerFactory.CreateUnit();
Unit enemyUnit = enemyFactory.CreateUnit();
Console.WriteLine($"플레이어 유닛: {playerUnit.Name} (체력: {playerUnit.Health})");
Console.WriteLine($"적 유닛: {enemyUnit.Name} (체력: {enemyUnit.Health})");
Console.WriteLine();
// 플레이어 유닛이 적 유닛을 공격
playerUnit.Attack(enemyUnit);
Console.WriteLine($"플레이어 유닛: {playerUnit.Name} (체력: {playerUnit.Health})");
Console.WriteLine($"적 유닛유닛: {enemyUnit.Name} (체력: {enemyUnit.Health})");
Console.WriteLine();
// 적 유닛이 플레이어 유닛을 공격
enemyUnit.Attack(playerUnit);
Console.WriteLine($"플레이어 유닛: {playerUnit.Name} (체력: {playerUnit.Health})");
Console.WriteLine($"적 유닛: {enemyUnit.Name} (체력: {enemyUnit.Health})");
Console.WriteLine();
// 게임 결과 출력
if (playerUnit.Health <= 0 && enemyUnit.Health <= 0)
{
Console.WriteLine("무승부!");
}
else if (playerUnit.Health <= 0)
{
Console.WriteLine("적 유닛이 승리했습니다!");
}
else if (enemyUnit.Health <= 0)
{
Console.WriteLine("플레이어 유닛이 승리했습니다!");
}
}
}
class Program
{
static void Main()
{
Console.WriteLine("StarCraft 게임을 시작합니다.");
IUnitFactory terranFactory = new TerranUnitFactory();
IUnitFactory zergFactory = new ZergUnitFactory();
IUnitFactory protossFactory = new ProtossUnitFactory();
// 테란 vs 저그
GameClient terranVsZergClient = new GameClient(terranFactory, zergFactory);
terranVsZergClient.PlayGame();
// 저그 vs 프로토스
GameClient zergVsProtossClient = new GameClient(zergFactory, protossFactory);
zergVsProtossClient.PlayGame();
// 프로토스 vs 테란
GameClient protossVsTerranClient = new GameClient(protossFactory, terranFactory);
protossVsTerranClient.PlayGame();
}
}
위의 코드에서는 GameClient 클래스에서 플레이어 Factory와 적 Factory를 각각 주입받아 다른 종족 간의 전투를 구현합니다. PlayGame() 메서드에서는 플레이어 유닛과 적 유닛을 생성하고 초기 체력을 출력한 후, 플레이어 유닛이 적 유닛을 공격하고, 적 유닛이 플레이어 유닛을 공격합니다. 마지막으로 게임의 승리 조건에 따라 결과를 출력합니다.
Main() 메서드에서는 테란 vs 저그, 저그 vs 프로토스, 프로토스 vs 테란의 세 가지 전투를 각각 생성하고 GameClient를 생성하여 게임을 플레이합니다.
장점:
단점:
Abstract Factory 패턴은 관련된 객체들을 일관성 있게 생성하고, 클라이언트 코드와의 결합도를 낮추며, 확장성을 갖출 수 있는 장점을 가지고 있습니다. 그러나 복잡성과 유연성 제한을 고려하여 패턴을 적용해야 합니다. 프로젝트의 요구사항과 구조에 맞게 적절한 패턴을 선택하고 사용하는 것이 중요합니다.
Abstract Factory 패턴의 사용에 따른 장점과 단점을 비교하기 위해 좋은(Good) 코드와 나쁜(Bad) 코드를 비교해보겠습니다.
Bad Code (Abstract Factory를 사용하지 않은 경우):
class GameClient
{
public void PlayGame(string faction)
{
if (faction == "Terran")
{
TerranUnit terranUnit = new TerranUnit();
terranUnit.Attack();
}
else if (faction == "Zerg")
{
ZergUnit zergUnit = new ZergUnit();
zergUnit.Attack();
}
else if (faction == "Protoss")
{
ProtossUnit protossUnit = new ProtossUnit();
protossUnit.Attack();
}
}
}
위의 코드는 게임 클라이언트에서 Abstract Factory 패턴을 사용하지 않고 유닛을 생성하는 예시입니다. 코드에서는 플레이어의 선택에 따라 테란, 저그, 프로토스 종족의 유닛을 생성하고 공격하는 로직이 구현되어 있습니다. 이러한 방식은 다음과 같은 문제점을 가지고 있습니다:
Good Code (Abstract Factory 패턴을 사용한 경우):
interface IUnitFactory
{
Unit CreateUnit();
}
class TerranUnitFactory : IUnitFactory
{
public Unit CreateUnit()
{
return new TerranUnit();
}
}
class ZergUnitFactory : IUnitFactory
{
public Unit CreateUnit()
{
return new ZergUnit();
}
}
class ProtossUnitFactory : IUnitFactory
{
public Unit CreateUnit()
{
return new ProtossUnit();
}
}
class GameClient
{
private IUnitFactory unitFactory;
public GameClient(IUnitFactory factory)
{
unitFactory = factory;
}
public void PlayGame()
{
Unit unit = unitFactory.CreateUnit();
unit.Attack();
}
}
위의 코드는 Abstract Factory 패턴을 사용하여 유닛을 생성하는 예시입니다. 클라이언트 코드는 추상화된 IUnitFactory 인터페이스에 의존하고 있으며, 특정 종족에 대한 유닛 생성 로직은 해당Factory 클래스에서 구현되어 있습니다. 이러한 방식은 다음과 같은 장점을 가지고 있습니다:
Abstract Factory 패턴을 사용하면 클라이언트 코드와 유닛 생성 로직 사이의 결합도를 낮출 수 있으며, 유지보수성과 확장성을 개선할 수 있습니다. 클라이언트 코드는 추상화된 인터페이스에 의존하여 구체적인 클래스에 대한 의존성을 제거하고, 유닛 생성 로직은 적절한 Factory 클래스 내에서 캡슐화됩니다.
좀 더 복잡하게 구현해 볼까요?
서로 다른 종족끼리 서로 싸우고 에너지가 0이 되면 먼저 지는 게임을 만들었습니다.
마린과 질럿으로 싸움을 붙여서 결국은 마린이 졌다고 나올 겁니다. 🤗
using System;
public interface IUnit
{
string Name { get; }
int HP { get; }
int AttackPower { get; }
void Attack(IUnit enemy);
void ReceiveDamage(int damage);
}
public interface IUnitFactory
{
IUnit CreateUnit(string unitType);
}
public class Terran : IUnit
{
public Terran(string name, int hp, int attackPower)
{
Name = name;
HP = hp;
AttackPower = attackPower;
}
public string Name { get; }
public int HP { get; private set; }
public int AttackPower { get; }
public void Attack(IUnit enemy)
{
enemy.ReceiveDamage(AttackPower);
}
public void ReceiveDamage(int damage)
{
HP -= damage;
}
}
public class Protoss : IUnit
{
public Protoss(string name, int hp, int attackPower)
{
Name = name;
HP = hp;
AttackPower = attackPower;
}
public string Name { get; }
public int HP { get; private set; }
public int AttackPower { get; }
public void Attack(IUnit enemy)
{
enemy.ReceiveDamage(AttackPower);
}
public void ReceiveDamage(int damage)
{
HP -= damage;
}
}
public class TerranFactory : IUnitFactory
{
public IUnit CreateUnit(string unitType)
{
switch (unitType)
{
case "Marine":
return new Terran("Marine", 40, 5);
case "Marauder":
return new Terran("Marauder", 100, 10);
default:
throw new ArgumentException("Invalid unit type specified");
}
}
}
public class ProtossFactory : IUnitFactory
{
public IUnit CreateUnit(string unitType)
{
switch (unitType)
{
case "Zealot":
return new Protoss("Zealot", 60, 8);
case "Stalker":
return new Protoss("Stalker", 80, 12);
default:
throw new ArgumentException("Invalid unit type specified");
}
}
}
public class Game
{
public static void Main(string[] args)
{
IUnitFactory terranFactory = new TerranFactory();
IUnitFactory protossFactory = new ProtossFactory();
IUnit terranUnit = terranFactory.CreateUnit("Marine");
IUnit protossUnit = protossFactory.CreateUnit("Zealot");
// 유닛들이 서로 싸우기 시작합니다.
while (true)
{
terranUnit.Attack(protossUnit);
if (protossUnit.HP <= 0)
{
Console.WriteLine($"{protossUnit.Name} is defeated!");
break;
}
protossUnit.Attack(terranUnit);
if (terranUnit.HP <= 0)
{
Console.WriteLine($"{terranUnit.Name} is defeated!");
break;
}
Console.WriteLine($"{protossUnit.HP} is protossUnit.HP!");
Console.WriteLine($"{terranUnit.HP} is terranUnit.HP!");
}
}
}
Factory Method 패턴과 Abstract Factory 패턴은 서로 매우 유사한 패턴이지만, 각 패턴의 목적과 적용 방법에 약간의 차이가 있습니다. 이 차이점을 설명드리겠습니다.
* Factory Method 패턴:
객체 생성에 관련된 코드를 별도의 클래스로 분리하여, 생성할 객체와 해당 객체를 생성하기 위한 팩토리 메서드를 동일한 인터페이스에 정의합니다.
팩토리 메서드는 일반적으로 하나의 생성자를 가지고 있어, 추상화된 인터페이스를 구현하는 구체 클래스들 중 하나를 선택하여 생성합니다.
이 패턴은 새로운 종류의 객체를 추가하기 쉽게 만들어 기존의 코드에 최소한의 수정으로 새로운 유형의 객체를 지원할 수 있게 하려는 것이 목표입니다.
* Abstract Factory 패턴:
추상 팩토리는 서로 관련된 객체 집합을 생성하기 위한 인터페이스를 제공합니다. 각 객체 집합은 일반적으로 동일한 주제나 용도로 연결되어 있습니다.
구체 팩토리 클래스는 이 인터페이스를 구현하여 해당 주제 또는 용도에 대한 객체 집합을 생성하는 로직을 제공합니다.
* 결론
이 패턴은 서로 다른 객체 패밀리를 자유롭게 대체할 수 있는 유연성을 제공하려는 것이 목표입니다.
패턴에 어떤 클래스 구조와 코드가 포함되냐에 따라 Factory Method와 Abstract Factory를 구별할 수 있습니다. 많은 경우에서 이 두 패턴은 유사한 목적과 구조를 지니고 있기 때문에 상황에 맞게 선택하여 사용하시면 됩니다. 중요한 것은 패턴 자체의 이름이나 디자인보다는 클린하고 유연한 코드를 작성하는 데 도움이 되는 소프트웨어 디자인 원칙을 이해하는 것입니다.
StarCraft 게임 - C# Builder Pattern (2) | 2023.07.06 |
---|---|
StarCraft 게임 - C# Singleton Pattern (1) | 2023.07.06 |
StarCraft 게임 - C# Factory Method Pattern (0) | 2023.07.06 |
StarCraft 게임 - C# Repository Pattern (0) | 2023.07.06 |
factory pattern c# (0) | 2016.02.05 |