Composite Pattern
컴포지트 패턴은 부분-전체 계층 구조를 표현한다.
인터페이스를 통일하여 개별 객체와 복합 객체를 동일한 방식으로 다룰 수 있게 한다.
보통 트리 구조로 구성되며 재귀적은 구조를 가진다.
스킬을 예로 들어보면 다음과 같다.
- Component (ISkill)
- 모든 객체의 공통 인터페이스 정의
- 단일 객체와 복합 객체가 구현해야 할 메서드 선언
- Leaf (SingleSkill)
- 가장 기본이 되는 객체
- 다른 객체를 포함할 수 없는 말단 객체
- Composite (ComboSkill)
- 자식 컴포넌트를 가질 수 있는 복합 객체
- 하위 컴포넌트들을 관리하는 메서드 포함
- 실제 작업은 자식 컴포넌트에 위임
ComboSkill (Root)
├── SingleSkill (Fireball)
├── SingleSkill (IceSpear)
└── ComboSkill (ElementalBurst)
├── SingleSkill (Lightning)
└── SingleSkill (Wind)
// 스킬 시스템을 위한 기본 인터페이스
public interface ISkill
{
void Execute();
float GetCooldown();
float GetManaCost();
}
// 단일 스킬 구현
public class SingleSkill : ISkill
{
private string skillName;
private float cooldown;
private float manaCost;
public SingleSkill(string name, float cd, float mana)
{
skillName = name;
cooldown = cd;
manaCost = mana;
}
public void Execute()
{
Debug.Log($"Executing {skillName}");
// 실제 스킬 실행 로직
}
public float GetCooldown() => cooldown;
public float GetManaCost() => manaCost;
}
// 복합 스킬(여러 스킬을 조합)
public class ComboSkill : ISkill
{
private List<ISkill> skills = new List<ISkill>();
private string comboName;
public ComboSkill(string name)
{
comboName = name;
}
public void AddSkill(ISkill skill)
{
skills.Add(skill);
}
public void RemoveSkill(ISkill skill)
{
skills.Remove(skill);
}
public void Execute()
{
Debug.Log($"Executing Combo: {comboName}");
foreach (var skill in skills)
{
skill.Execute();
}
}
public float GetCooldown()
{
return skills.Max(s => s.GetCooldown());
}
public float GetManaCost()
{
return skills.Sum(s => s.GetManaCost());
}
}
// 사용 예시
public class Player : MonoBehaviour
{
void Start()
{
// 기본 스킬 생성
var fireball = new SingleSkill("Fireball", 5f, 30f);
var iceSpear = new SingleSkill("Ice Spear", 3f, 20f);
// 콤보 스킬 생성
var elementalCombo = new ComboSkill("Elemental Combo");
elementalCombo.AddSkill(fireball);
elementalCombo.AddSkill(iceSpear);
// 실행
elementalCombo.Execute();
}
}
Decorator Pattern
데코레이터 패턴은 객체에 동적으로 새로운 기능을 추가할 수 있게 하는 패턴이다.
상속을 통해 기능을 확장하는 것이 아니라 소유 및 참조를 통해 기능을 확장할 수 있다.
무기 시스템을 예로 들면 다음과 같다.
- Component (IWeapon)
- 기본 인터페이스 정의
- 데코레이터와 실제 구현체가 따르는 공통 구조
- ConcreteComponent (BasicSword)
- 실제 기본 기능을 구현하는 클래스
- 추가 기능 없이 핵심 기능만 구현
- Decorator (WeaponDecorator)
- Component를 감싸는 래퍼 클래스
- Component와 동일한 인터페이스를 구현
- ConcreteDecorator (FireEnchantment, SharpnessEnchantment)
- 실제 추가 기능을 구현하는 클래스
- 기존 기능을 확장하거나 변경
SharpnessEnchantment
└── FireEnchantment
└── BasicSword
// 기본 무기 인터페이스
public interface IWeapon
{
float GetDamage();
string GetDescription();
}
// 기본 무기 구현
public class BasicSword : IWeapon
{
public float GetDamage() => 10f;
public string GetDescription() => "Basic Sword";
}
// 무기 데코레이터 기본 클래스
public abstract class WeaponDecorator : IWeapon
{
protected IWeapon decoratedWeapon;
public WeaponDecorator(IWeapon weapon)
{
decoratedWeapon = weapon;
}
public virtual float GetDamage()
{
return decoratedWeapon.GetDamage();
}
public virtual string GetDescription()
{
return decoratedWeapon.GetDescription();
}
}
// 화염 강화 데코레이터
public class FireEnchantment : WeaponDecorator
{
public FireEnchantment(IWeapon weapon) : base(weapon) { }
public override float GetDamage()
{
return base.GetDamage() + 5f;
}
public override string GetDescription()
{
return $"{base.GetDescription()} (Fire Enchanted)";
}
}
// 날카로움 강화 데코레이터
public class SharpnessEnchantment : WeaponDecorator
{
public SharpnessEnchantment(IWeapon weapon) : base(weapon) { }
public override float GetDamage()
{
return base.GetDamage() * 1.2f;
}
public override string GetDescription()
{
return $"{base.GetDescription()} (Sharpened)";
}
}
// 사용 예시
public class WeaponSystem : MonoBehaviour
{
void Start()
{
// 기본 무기 생성
IWeapon sword = new BasicSword();
// 강화 적용
sword = new FireEnchantment(sword);
sword = new SharpnessEnchantment(sword);
Debug.Log($"Final Weapon: {sword.GetDescription()}");
Debug.Log($"Final Damage: {sword.GetDamage()}");
}
}
Facade Pattern
파사드 패턴은 복잡한 서브시스템들을 단순화된 인터페이스로 제공하는 패턴이다.
사용하는 전면부를 제공하고 실제 동작은 숨길 수 있다.
또한, 서브시스템 간의 결합도를 낮출 수 있으며 클라이언트에서도 서브시스템을 직접 참조하지 않아도 되기 때문에 의존성을 줄일 수 있다.
- Facade (GameEventFacade)
- 단순화된 통합 인터페이스 제공
- 서브시스템들의 복잡한 상호작용을 캡슐화
- Subsystems (AudioSystem, ParticleSystem, AnimationSystem)
- 실제 기능을 구현하는 복잡한 시스템들
- 각각 독립적으로 동작
Client -> GameEventFacade
├── AudioSystem
│ ├── InitializeAudio()
│ ├── PlayMusic()
│ └── PlaySoundEffect()
├── ParticleSystem
│ ├── InitializeParticles()
│ └── SpawnParticles()
└── AnimationSystem
├── InitializeAnimator()
└── PlayAnimation()
// 복잡한 서브시스템들
public class AudioSystem
{
public void InitializeAudio() => Debug.Log("Audio System Initialized");
public void PlayMusic(string track) => Debug.Log($"Playing music: {track}");
public void PlaySoundEffect(string sfx) => Debug.Log($"Playing SFX: {sfx}");
}
public class ParticleSystem
{
public void InitializeParticles() => Debug.Log("Particle System Initialized");
public void SpawnParticles(Vector3 position) => Debug.Log($"Spawning particles at {position}");
}
public class AnimationSystem
{
public void InitializeAnimator() => Debug.Log("Animation System Initialized");
public void PlayAnimation(string animName) => Debug.Log($"Playing animation: {animName}");
}
// 퍼사드 클래스
public class GameEventFacade
{
private AudioSystem audioSystem;
private ParticleSystem particleSystem;
private AnimationSystem animationSystem;
public GameEventFacade()
{
audioSystem = new AudioSystem();
particleSystem = new ParticleSystem();
animationSystem = new AnimationSystem();
Initialize();
}
private void Initialize()
{
audioSystem.InitializeAudio();
particleSystem.InitializeParticles();
animationSystem.InitializeAnimator();
}
// 간단한 인터페이스 제공
public void PlayVictorySequence(Vector3 position)
{
audioSystem.PlayMusic("victory_theme");
audioSystem.PlaySoundEffect("victory_fanfare");
particleSystem.SpawnParticles(position);
animationSystem.PlayAnimation("victory_dance");
}
public void PlayDefeatSequence(Vector3 position)
{
audioSystem.PlayMusic("defeat_theme");
audioSystem.PlaySoundEffect("defeat_sound");
particleSystem.SpawnParticles(position);
animationSystem.PlayAnimation("defeat_animation");
}
}
// 사용 예시
public class GameManager : MonoBehaviour
{
private GameEventFacade gameEvents;
void Start()
{
gameEvents = new GameEventFacade();
}
public void OnPlayerWin()
{
gameEvents.PlayVictorySequence(transform.position);
}
public void OnPlayerLose()
{
gameEvents.PlayDefeatSequence(transform.position);
}
}
Proxy Pattern
프록시 패턴은 다른 객체에 대한 접근을 제어하는 대리자를 두는 패턴이다.
실제 객체의 생성과 초기화를 지연시킬 수 있으며 접근 제어, 캐싱, 로깅 등 부가 기능 제공할 수 있다.
게임 내의 리소스를 관리하는 로직을 예로 들어 보자.
- Subject (IGameResource)
- 프록시와 실제 객체가 구현하는 공통 인터페이스
- 클라이언트가 사용할 메서드 정의
- RealSubject (HeavyGameResource)
- 실제 작업을 수행하는 객체
- 리소스를 많이 사용하거나 생성 비용이 큰 객체
- Proxy (GameResourceProxy, CachingResourceProxy)
- RealSubject에 대한 레퍼런스 관리
- 접근 제어 및 부가 기능 제공
- 필요할 때만 실제 객체 생성
Client -> GameResourceProxy
├── Check if initialized
├── Create RealSubject if needed
└── Delegate to RealSubject
// 게임 리소스 인터페이스
public interface IGameResource
{
void Load();
void Display();
}
// 실제 게임 리소스
public class HeavyGameResource : IGameResource
{
private string resourcePath;
private bool isLoaded;
public HeavyGameResource(string path)
{
resourcePath = path;
isLoaded = false;
}
public void Load()
{
Debug.Log($"Loading heavy resource from {resourcePath}...");
// 실제 리소스 로딩 작업
isLoaded = true;
Debug.Log("Resource loaded successfully");
}
public void Display()
{
if (isLoaded)
{
Debug.Log("Displaying heavy resource");
}
else
{
Debug.LogError("Resource not loaded!");
}
}
}
// 프록시 클래스
public class GameResourceProxy : IGameResource
{
private HeavyGameResource realResource;
private string resourcePath;
private bool isInitialized = false;
public GameResourceProxy(string path)
{
resourcePath = path;
}
public void Load()
{
if (!isInitialized)
{
Debug.Log("Proxy: Initializing resource");
realResource = new HeavyGameResource(resourcePath);
isInitialized = true;
}
realResource.Load();
}
public void Display()
{
if (!isInitialized)
{
Load();
}
realResource.Display();
}
}
// 캐싱 프록시
public class CachingResourceProxy : IGameResource
{
private static Dictionary<string, HeavyGameResource> resourceCache
= new Dictionary<string, HeavyGameResource>();
private string resourcePath;
private HeavyGameResource resource;
public CachingResourceProxy(string path)
{
resourcePath = path;
}
public void Load()
{
if (!resourceCache.ContainsKey(resourcePath))
{
Debug.Log($"Cache miss for {resourcePath}");
resource = new HeavyGameResource(resourcePath);
resource.Load();
resourceCache[resourcePath] = resource;
}
else
{
Debug.Log($"Cache hit for {resourcePath}");
resource = resourceCache[resourcePath];
}
}
public void Display()
{
if (resource == null)
{
Load();
}
resource.Display();
}
}
// 사용 예시
public class ResourceManager : MonoBehaviour
{
void Start()
{
// 일반 프록시 사용
IGameResource proxy = new GameResourceProxy("heavy_model.fbx");
proxy.Display(); // 자동으로 로드 후 표시
// 캐싱 프록시 사용
IGameResource cachingProxy1 = new CachingResourceProxy("texture.png");
IGameResource cachingProxy2 = new CachingResourceProxy("texture.png");
cachingProxy1.Load(); // 실제 로드 발생
cachingProxy2.Load(); // 캐시에서 로드
}
}