1. 방문자 패턴이란? ✨

방문자 패턴은 객체 구조를 변경하지 않고도 새 기능을 추가할 수 있는 디자인 패턴입니다. 특히, 다양한 타입의 객체에 대한 연산을 분리할 때 유용합니다.

예를 들어, 복잡한 객체 구조(트리, 그래프 등)에 서로 다른 연산을 수행해야 한다면, 방문자 패턴을 통해 구조와 연산의 분리를 실현할 수 있습니다.

2. 방문자 패턴의 구성 요소 📦

방문자 패턴은 다음과 같은 구성 요소로 이루어져 있습니다:

  1. Visitor (방문자): 객체 구조의 각 요소를 방문하여 연산을 수행하는 인터페이스입니다.
  2. ConcreteVisitor (구체 방문자): Visitor를 구현하며, 실제 연산을 정의합니다.
  3. Element (요소): Visitor를 수용(accept)하는 인터페이스를 정의합니다.
  4. ConcreteElement (구체 요소): Element를 구현하며, Visitor를 받아들이는 로직을 포함합니다.
  5. ObjectStructure (객체 구조): Element들의 집합으로, Visitor가 이 구조를 순회하며 연산을 수행합니다.

    출처: 위키

3. 방문자 패턴 예제 🌟

아래는 C#으로 작성한 방문자 패턴 예제입니다. 다양한 캐릭터 객체가 있고, 방문자가 이들을 방문하여 연산을 수행하는 상황을 가정합니다.

using System;
using System.Collections.Generic;

// Visitor 인터페이스
public interface IVisitor
{
    void Visit(Warrior warrior);
    void Visit(Mage mage);
}

// Element 인터페이스
public interface ICharacter
{
    void Accept(IVisitor visitor);
}

// 구체 요소들
public class Warrior : ICharacter
{
    public string Name { get; set; } = "Warrior";
    public void Accept(IVisitor visitor) => visitor.Visit(this);
}

public class Mage : ICharacter
{
    public string Name { get; set; } = "Mage";
    public void Accept(IVisitor visitor) => visitor.Visit(this);
}

// 구체 방문자
public class DamageCalculator : IVisitor
{
    public void Visit(Warrior warrior)
    {
        Console.WriteLine($"{warrior.Name} takes 50 damage!");
    }

    public void Visit(Mage mage)
    {
        Console.WriteLine($"{mage.Name} takes 70 damage!");
    }
}

// 객체 구조
public class CharacterGroup
{
    private readonly List<ICharacter> _characters = new();

    public void AddCharacter(ICharacter character) => _characters.Add(character);

    public void Accept(IVisitor visitor)
    {
        foreach (var character in _characters)
        {
            character.Accept(visitor);
        }
    }
}

// 사용 예시
public class Program
{
    public static void Main()
    {
        var group = new CharacterGroup();
        group.AddCharacter(new Warrior());
        group.AddCharacter(new Mage());

        var damageCalculator = new DamageCalculator();
        group.Accept(damageCalculator);
    }
}

출력 결과

Warrior takes 50 damage!
Mage takes 70 damage!

4. 방문자 패턴 사용 시 주의할 점 ⚠️

  1. 요소 클래스의 변경에 취약: 새로운 Element를 추가하면 모든 Visitor를 수정해야 합니다.
  2. 객체 구조의 복잡성 증가: 객체 구조가 클수록 관리가 어려워질 수 있습니다.
  3. 사용 적합성 고려: 연산이 자주 변경되거나 추가되는 경우에 적합하지만, 요소의 변화가 잦다면 부적합할 수 있습니다.

5. 결론 ✨

방문자 패턴은 객체 구조와 연산의 분리를 통해 확장성과 유연성을 제공합니다. 특히, 여러 객체에 대해 다양한 연산을 수행할 때 강력한 도구가 됩니다. 다만, 요소의 변경이 많을 경우 관리 비용이 증가할 수 있으므로 상황에 따라 신중히 선택해야 합니다.

6. 관련 링크 🔗

반응형

1. 책임 연쇄 패턴이란?

책임 연쇄 패턴은 요청을 처리할 수 있는 객체들의 체인을 구성하고, 각 객체가 요청을 처리할 기회를 갖도록 하는 디자인 패턴입니다. 이 패턴을 사용하면 요청을 보낸 코드와 처리하는 코드 간의 결합도를 낮추고, 새로운 처리 로직을 쉽게 추가할 수 있습니다. ✨

2. 책임 연쇄 패턴의 구성 요소

책임 연쇄 패턴은 다음과 같은 구성 요소로 이루어져 있습니다:

  • Handler (처리자): 요청을 처리하거나 다음 처리자에게 전달합니다.
  • ConcreteHandler (구체 처리자): 요청을 실제로 처리하거나 처리할 수 없는 경우 체인의 다음 처리자에게 넘깁니다.
  • Client (클라이언트): 요청을 체인의 첫 번째 처리자에게 전달합니다.

출처: 위키

3. 책임 연쇄 패턴 예제

문제 상황

사용자 요청을 다양한 수준에서 검증해야 합니다. 예를 들어, 사용자 입력에 대해 입력 값 검증, 권한 확인, 로그 기록 등의 처리를 해야 할 때 책임 연쇄 패턴을 사용할 수 있습니다.

C# 코드 예제

using System;

// Handler 인터페이스
abstract class Handler
{
    protected Handler next;

    public void SetNext(Handler nextHandler)
    {
        next = nextHandler;
    }

    public abstract void Handle(string request);
}

// ConcreteHandler: 입력 검증
class ValidationHandler : Handler
{
    public override void Handle(string request)
    {
        if (string.IsNullOrWhiteSpace(request))
        {
            Console.WriteLine("Validation failed: Input is empty.");
            return;
        }

        Console.WriteLine("Validation passed.");
        next?.Handle(request);
    }
}

// ConcreteHandler: 권한 확인
class AuthorizationHandler : Handler
{
    public override void Handle(string request)
    {
        if (request != "AuthorizedUser")
        {
            Console.WriteLine("Authorization failed: User is not authorized.");
            return;
        }

        Console.WriteLine("Authorization passed.");
        next?.Handle(request);
    }
}

// ConcreteHandler: 로그 기록
class LoggingHandler : Handler
{
    public override void Handle(string request)
    {
        Console.WriteLine($"Logging request: {request}");
        next?.Handle(request);
    }
}

// 클라이언트 코드
class Program
{
    static void Main()
    {
        // 체인 구성
        Handler validation = new ValidationHandler();
        Handler authorization = new AuthorizationHandler();
        Handler logging = new LoggingHandler();

        validation.SetNext(authorization);
        authorization.SetNext(logging);

        // 요청 처리
        string userInput = "AuthorizedUser";
        validation.Handle(userInput);
    }
}

출력 결과

Validation passed.
Authorization passed.
Logging request: AuthorizedUser

4. 책임 연쇄 패턴의 특이점 및 고려할 점

  • 유연성: 각 처리자는 독립적으로 동작하며, 체인의 구조를 동적으로 변경할 수 있습니다.
  • 결합도 감소: 요청 처리 로직이 처리자 객체들에 분리되어 코드의 재사용성을 높입니다.
  • 주의점: 요청이 체인의 끝까지 전달되었는데도 처리되지 않을 수 있으므로, 기본 처리자를 추가하거나 요청이 처리되지 않았을 경우에 대한 대책을 마련해야 합니다.

5. 결론

책임 연쇄 패턴은 요청 처리의 유연성과 확장성을 높이는 데 유용한 패턴입니다. 체인의 각 구성 요소를 독립적으로 관리할 수 있어, 유지보수가 용이하며 동적으로 처리 로직을 변경할 수 있습니다. 단, 체인의 끝에서 요청이 처리되지 않는 경우를 염두에 두고 설계해야 합니다. 🔗

6. 관련 링크

반응형

1. 브리지 패턴이란?

브리지 패턴은 두 개의 독립적인 개념을 연결(Bridge)해 서로의 변화에 영향을 덜 받게 만드는 구조적 디자인 패턴입니다. 일반적으로 추상화구현을 분리해 독립적으로 확장할 수 있도록 설계합니다. 이 패턴은 코드의 확장성과 유지보수성을 높이는 데 유용합니다. 🎯

특징

  • 추상화와 구현의 독립성 유지
  • 런타임에 구현을 바꿀 수 있는 유연성 제공
  • 다형성을 활용한 깔끔한 구조

2. 브리지 패턴의 구성 요소

브리지 패턴은 크게 4가지 구성 요소로 이루어집니다:

  1. Abstraction (추상화)
    • 기능의 상위 레벨을 정의한 인터페이스 또는 추상 클래스
  2. Refined Abstraction (구체화된 추상화)
    • 추상화를 확장한 구체적인 클래스
  3. Implementor (구현자)
    • 실제 기능을 수행하는 인터페이스
  4. Concrete Implementor (구체 구현자)
    • 구현자 인터페이스를 실제로 구현한 클래스

구조 다이어그램

출처: 위키

3. 브리지 패턴 예제

문제 상황

게임 아이템을 화면에 표시할 때, 무기 종류효과(예: 불, 얼음 등)를 조합해야 한다고 가정해봅시다.

브리지 패턴을 활용해 조합 가능한 클래스 수를 줄이고 유연성을 높이는 구조를 설계해 보겠습니다. 💡

코드 예제

// Implementor
public interface IEffect
{
    void ApplyEffect(string weapon);
}

// Concrete Implementors
public class FireEffect : IEffect
{
    public void ApplyEffect(string weapon)
    {
        Debug.Log($"{weapon}에 불 효과를 적용합니다.");
    }
}

public class IceEffect : IEffect
{
    public void ApplyEffect(string weapon)
    {
        Debug.Log($"{weapon}에 얼음 효과를 적용합니다.");
    }
}

// Abstraction
public abstract class Weapon
{
    protected IEffect effect;

    public Weapon(IEffect effect)
    {
        this.effect = effect;
    }

    public abstract void Use();
}

// Refined Abstraction
public class Sword : Weapon
{
    public Sword(IEffect effect) : base(effect) { }

    public override void Use()
    {
        Debug.Log("검을 휘두릅니다.");
        effect.ApplyEffect("검");
    }
}

public class Bow : Weapon
{
    public Bow(IEffect effect) : base(effect) { }

    public override void Use()
    {
        Debug.Log("활을 쏩니다.");
        effect.ApplyEffect("활");
    }
}

// 사용 예시
public class Game
{
    public static void Main()
    {
        IEffect fireEffect = new FireEffect();
        IEffect iceEffect = new IceEffect();

        Weapon sword = new Sword(fireEffect);
        sword.Use(); // 출력: 검을 휘두릅니다. 검에 불 효과를 적용합니다.

        Weapon bow = new Bow(iceEffect);
        bow.Use(); // 출력: 활을 쏩니다. 활에 얼음 효과를 적용합니다.
    }
}

4. 브리지 패턴의 사용 시 고려할 점

장점 ✨

  1. 확장성 증가: 추상화와 구현을 독립적으로 확장할 수 있습니다.
  2. 코드 중복 감소: 조합 가능한 클래스 수를 줄입니다.
  3. 유연성 제공: 런타임에 구현을 바꿀 수 있습니다.

주의점 ⚠️

  • 설계가 복잡해질 수 있으므로, 모든 상황에 적합하지 않습니다.
  • 추상화와 구현이 지나치게 단순하다면, 오히려 불필요한 구조가 될 수 있습니다.

5. 결론

브리지 패턴은 추상화와 구현을 분리해 서로 독립적으로 확장할 수 있도록 돕는 강력한 디자인 패턴입니다. 특히, 조합 가능한 객체가 많아질 때 설계의 복잡도를 줄이고 코드의 유연성을 높이는 데 효과적입니다. 하지만 과도하게 사용하면 불필요한 복잡성을 초래할 수 있으므로 적절한 상황에서 활용하는 것이 중요합니다. 🚀

6. 관련 링크

반응형

1. 컴포지트 패턴이란? 🌳

컴포지트 패턴(Composite Pattern)은 객체를 트리(tree) 구조로 구성하여 부분-전체 계층 구조를 구현하는 데 사용됩니다. 이를 통해 클라이언트는 개별 객체와 복합 객체를 동일하게 다룰 수 있습니다.

이 패턴은 특히 UI 구성 요소파일 시스템처럼 계층적 구조를 가진 시스템에서 유용합니다. 컴포지트 패턴은 단순한 인터페이스를 통해 복잡한 구조를 효율적으로 관리할 수 있게 해줍니다.

출처: 위키

 

2. 컴포지트 패턴의 구성 요소 🧩

  1. Component (구성 요소): 트리 구조에서 공통 인터페이스를 정의합니다.
  2. Leaf (잎 노드): 트리 구조의 말단에 해당하며, 실제 작업을 수행합니다.
  3. Composite (복합 노드): 다른 Leaf 또는 Composite를 자식으로 포함할 수 있으며, 자식 요소에 작업을 위임합니다.

3. 컴포지트 패턴 예제 🎮

Unity3D에서 게임 오브젝트 트리 관리

Unity의 GameObject 트리 구조는 컴포지트 패턴의 대표적인 예입니다. 이를 간단히 구현한 예제를 보겠습니다.

using System;
using System.Collections.Generic;

// Component: 공통 인터페이스
public interface IGameObject
{
    void Render();
}

// Leaf: 단일 요소
public class GameObject : IGameObject
{
    private string _name;

    public GameObject(string name)
    {
        _name = name;
    }

    public void Render()
    {
        Console.WriteLine($"Rendering GameObject: {_name}");
    }
}

// Composite: 자식 요소를 포함하는 복합 객체
public class GameObjectGroup : IGameObject
{
    private List<IGameObject> _children = new List<IGameObject>();

    public void Add(IGameObject gameObject)
    {
        _children.Add(gameObject);
    }

    public void Remove(IGameObject gameObject)
    {
        _children.Remove(gameObject);
    }

    public void Render()
    {
        Console.WriteLine("Rendering GameObjectGroup:");
        foreach (var child in _children)
        {
            child.Render();
        }
    }
}

// 예제 실행
public class Program
{
    public static void Main()
    {
        // Leaf 생성
        var player = new GameObject("Player");
        var enemy = new GameObject("Enemy");

        // Composite 생성 및 Leaf 추가
        var scene = new GameObjectGroup();
        scene.Add(player);
        scene.Add(enemy);

        // Composite 내에 또 다른 Composite 추가
        var uiGroup = new GameObjectGroup();
        var uiElement = new GameObject("HealthBar");
        uiGroup.Add(uiElement);
        scene.Add(uiGroup);

        // 전체 트리 구조 렌더링
        scene.Render();
    }
}

출력 결과

Rendering GameObjectGroup:
Rendering GameObject: Player
Rendering GameObject: Enemy
Rendering GameObjectGroup:
Rendering GameObject: HealthBar

4. 컴포지트 패턴 사용 시 고려할 점 ⚠️

  • 객체의 복잡성: 트리 구조가 너무 깊거나 복잡하면 관리가 어려워질 수 있습니다. 불필요한 계층화를 피해야 합니다.
  • 단일 책임 원칙: 컴포지트 객체는 자식 관리와 작업 위임이라는 두 가지 책임을 가지므로, 적절히 설계해야 책임이 분리됩니다.
  • 성능: 큰 트리 구조에서는 순회를 최소화하는 최적화가 필요할 수 있습니다.

5. 결론 🚀

컴포지트 패턴은 객체를 계층적으로 구성하고, 클라이언트가 개별 요소와 복합 요소를 동일하게 처리할 수 있게 해줍니다. 특히 Unity3D와 같은 게임 엔진에서 UI, 씬 관리 등에 매우 유용하게 활용됩니다. 하지만 설계 단계에서 계층 구조의 복잡성을 신중히 고려해야 합니다.

6. 관련 링크 🔗

반응형

 

1. 상태 패턴이란? 🤔

상태 패턴(State Pattern)은 객체가 상태에 따라 다른 동작을 하도록 설계하는 디자인 패턴입니다. 객체의 내부 상태를 캡슐화하고, 상태 전환 시 행동이 달라지도록 구현할 수 있습니다. 이를 통해 코드의 복잡도를 줄이고 유지보수성을 높일 수 있습니다.

출처: 위키


2. 상태 패턴의 구성 요소

상태 패턴은 다음 세 가지 주요 구성 요소로 이루어져 있습니다:

  1. Context: 상태를 관리하는 주체로, 상태 변경에 따라 동작을 위임합니다.
  2. State: 상태를 나타내는 인터페이스로, 구체적인 행동을 정의하는 메서드가 포함됩니다.
  3. ConcreteState: 특정 상태를 구현한 클래스입니다. 각 상태마다 서로 다른 동작을 제공합니다.

3. 상태 패턴 예제 💻

Unity3D에서 게임 캐릭터의 무기 상태(예: 기본 무기, 활, 마법지팡이)를 상태에 따라 다르게 처리하는 예제를 만들어 보겠습니다.

// State 인터페이스 정의
public interface IWeaponState
{
    void Attack();
}

// ConcreteState: 기본 무기 상태
public class DefaultWeaponState : IWeaponState
{
    public void Attack()
    {
        Debug.Log("기본 무기로 공격합니다.");
    }
}

// ConcreteState: 활 상태
public class BowState : IWeaponState
{
    public void Attack()
    {
        Debug.Log("활로 원거리 공격을 합니다.");
    }
}

// ConcreteState: 마법지팡이 상태
public class MagicWandState : IWeaponState
{
    public void Attack()
    {
        Debug.Log("마법지팡이로 마법 공격을 합니다.");
    }
}

// Context 클래스 정의
public class Character
{
    private IWeaponState _currentWeaponState;

    public Character()
    {
        _currentWeaponState = new DefaultWeaponState(); // 초기 상태 설정
    }

    public void SetWeaponState(IWeaponState newWeaponState)
    {
        _currentWeaponState = newWeaponState;
        Debug.Log("무기가 변경되었습니다.");
    }

    public void Attack()
    {
        _currentWeaponState.Attack();
    }
}

// Unity에서 사용 예
public class CharacterController : MonoBehaviour
{
    private Character _character;

    void Start()
    {
        _character = new Character();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            _character.SetWeaponState(new DefaultWeaponState());
        }
        else if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            _character.SetWeaponState(new BowState());
        }
        else if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            _character.SetWeaponState(new MagicWandState());
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            _character.Attack();
        }
    }
}

4. 상태 패턴의 사용 시 고려할 점 📌

  1. 상태 추가 용이성: 새로운 상태를 추가할 때 기존 코드를 수정하지 않고 새로운 클래스만 추가하면 됩니다.
  2. 상태 전환 관리: 상태 간 전환이 많아질 경우 복잡도가 증가할 수 있으므로 설계를 잘해야 합니다.
  3. 적용 범위: 상태 패턴은 상태가 명확히 구분되고, 행동이 상태에 따라 크게 달라질 때 적합합니다.

5. 결론 ✅

상태 패턴은 객체의 행동을 상태에 따라 다르게 구현할 때 유용합니다. Unity3D와 같은 게임 개발에서는 캐릭터의 무기 상태 관리, AI 상태 전환 등에 자주 사용됩니다. 적절히 활용하면 코드의 가독성과 유지보수성을 크게 향상시킬 수 있습니다.


6. 관련 링크 🔗

반응형

1. 플라이웨이트 패턴이란?

플라이웨이트 패턴은 많은 객체를 생성해야 할 때 메모리를 절약하기 위해 공유할 수 있는 객체를 재사용하는 디자인 패턴입니다. 동일한 데이터는 공유하고, 개별 객체는 고유한 상태만 유지하도록 설계합니다. 주로 게임, 그래픽 애플리케이션 등에서 효율적인 리소스 관리에 사용됩니다.

출처: 위키


2. 플라이웨이트 패턴의 구성 요소

  1. Flyweight (공유 객체): 공유 가능한 객체를 정의합니다.
  2. Intrinsic State (내재 상태): 공유 객체가 공통적으로 가지는 불변 상태입니다.
  3. Extrinsic State (외재 상태): 객체별로 고유한 가변 상태이며, 외부에서 제공됩니다.
  4. Flyweight Factory (팩토리): Flyweight 객체를 관리하며, 중복 생성 방지를 위해 기존 객체를 반환하거나 새로 생성합니다.

3. 플라이웨이트 패턴 예제 (C#)

캐릭터 글자 렌더링 최적화 예제

플라이웨이트 패턴은 텍스트 렌더링에서 자주 사용됩니다. 같은 글자를 여러 번 렌더링할 때, 동일한 글자 객체를 재사용해 메모리를 절약합니다.

using System;
using System.Collections.Generic;

// Flyweight (공유 객체)
public class Character
{
    public char Symbol { get; private set; }  // Intrinsic State
    public string Font { get; private set; }  // Intrinsic State

    public Character(char symbol, string font)
    {
        Symbol = symbol;
        Font = font;
    }

    public void Display(int positionX, int positionY) // Extrinsic State
    {
        Console.WriteLine($"Character: {Symbol}, Font: {Font}, Position: ({positionX}, {positionY})");
    }
}

// Flyweight Factory
public class CharacterFactory
{
    private readonly Dictionary<string, Character> _characters = new();

    public Character GetCharacter(char symbol, string font)
    {
        string key = $"{symbol}_{font}";
        if (!_characters.ContainsKey(key))
        {
            _characters[key] = new Character(symbol, font);
        }
        return _characters[key];
    }
}

// Client Code
class Program
{
    static void Main(string[] args)
    {
        CharacterFactory factory = new CharacterFactory();

        // Reusing 'A' with the same font
        var charA1 = factory.GetCharacter('A', "Arial");
        var charA2 = factory.GetCharacter('A', "Arial");

        charA1.Display(10, 20);
        charA2.Display(30, 40);

        Console.WriteLine(Object.ReferenceEquals(charA1, charA2)); // True
    }
}

4. 플라이웨이트 패턴의 사용 시 주의점 ✅

  1. 상태 구분: 내재 상태와 외재 상태를 명확히 분리해야 합니다. 잘못된 설계는 객체의 불변성을 깨뜨릴 수 있습니다.
  2. 팩토리 관리: Flyweight Factory에서 객체의 생명 주기를 잘 관리해야 메모리 누수를 방지할 수 있습니다.
  3. 적합한 상황: 객체 생성 비용이 크고, 동일한 상태의 객체가 반복될 때 효과적입니다. 객체가 항상 고유 상태를 가진다면 이 패턴은 적합하지 않을 수 있습니다.

5. 결론

플라이웨이트 패턴은 메모리 사용을 최적화하고 객체 생성 비용을 줄이는 강력한 도구입니다. 특히 대규모 애플리케이션에서 성능 병목을 해결하는 데 유용합니다. 하지만 상태 관리와 사용 환경에 주의해야 제대로 된 효과를 볼 수 있습니다. 😊


6. 관련 링크

반응형

1. 개요

템플릿 메서드 패턴은 알고리즘의 뼈대를 정의하고, 세부 구현은 서브클래스에서 결정하도록 만드는 디자인 패턴입니다. 동일한 구조를 유지하면서도 일부 기능을 커스터마이징할 수 있어 코드 재사용성과 유지보수성을 높입니다. 📜

2. 개념

이 패턴의 핵심은 상위 클래스에서 템플릿 메서드를 정의하고, 하위 클래스가 이 메서드에서 호출되는 세부 작업을 구현하도록 강제하는 것입니다.

구성 요소:

  1. Abstract Class: 템플릿 메서드를 정의하고 공통 동작(선택적으로 구현)을 제공합니다.
  2. Concrete Class: 구체적인 작업을 구현하며, 템플릿 메서드에서 호출됩니다.

템플릿 메서드를 사용하면 중복 코드를 최소화하면서도 공통된 알고리즘 구조를 보장할 수 있습니다. 🛠️

3. 예제

C#에서 템플릿 메서드 패턴을 사용한 간단한 예제입니다. "게임 캐릭터가 특정 행동을 수행"하는 상황을 가정합니다.

using System;

// Abstract Class
abstract class GameCharacter
{
    // Template Method
    public void PerformAction()
    {
        StartAction();
        ExecuteAction();
        EndAction();
    }

    protected abstract void StartAction();
    protected abstract void ExecuteAction();
    protected abstract void EndAction();
}

// Concrete Class 1
class Warrior : GameCharacter
{
    protected override void StartAction()
    {
        Console.WriteLine("전사가 무기를 준비합니다.");
    }

    protected override void ExecuteAction()
    {
        Console.WriteLine("전사가 적을 공격합니다.");
    }

    protected override void EndAction()
    {
        Console.WriteLine("전사가 무기를 정리합니다.");
    }
}

// Concrete Class 2
class Mage : GameCharacter
{
    protected override void StartAction()
    {
        Console.WriteLine("마법사가 주문을 외우기 시작합니다.");
    }

    protected override void ExecuteAction()
    {
        Console.WriteLine("마법사가 강력한 마법을 발사합니다.");
    }

    protected override void EndAction()
    {
        Console.WriteLine("마법사가 마법책을 닫습니다.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        GameCharacter warrior = new Warrior();
        GameCharacter mage = new Mage();

        warrior.PerformAction();
        Console.WriteLine();
        mage.PerformAction();
    }
}

출력:

전사가 무기를 준비합니다.
전사가 적을 공격합니다.
전사가 무기를 정리합니다.

마법사가 주문을 외우기 시작합니다.
마법사가 강력한 마법을 발사합니다.
마법사가 마법책을 닫습니다.

이 코드에서 PerformAction 메서드는 알고리즘의 뼈대를 정의하며, 각 단계는 하위 클래스에서 구체화됩니다. 🛠️

4. 특이점 및 주의점

특이점

  • 알고리즘의 구조를 유지하면서도 각 단계의 구현을 유연하게 바꿀 수 있습니다.
  • 코드 중복을 방지하고, 알고리즘의 흐름을 명확하게 표현합니다.

주의점

  • 서브클래스가 너무 많아지면 관리가 어려워질 수 있습니다. 이를 방지하려면 적절한 계층 구조를 설계해야 합니다.
  • 알고리즘이 너무 단순하거나 변경 가능성이 적다면 오히려 과한 설계가 될 수 있습니다. 🚩

5. 결론

템플릿 메서드 패턴은 알고리즘의 뼈대를 유지하면서도 세부적인 구현을 유연하게 변경해야 할 때 유용합니다. 특히 게임, 웹 애플리케이션 등 다양한 도메인에서 반복적인 로직을 구조화하는 데 효과적입니다.

6. 관련 링크

반응형

1. 미디에이터 패턴이란?

객체들이 서로 직접 통신하지 않고, 중재자(미디에이터)를 통해 간접적으로 소통하도록 만드는 디자인 패턴입니다. 이를 통해 객체 간의 의존성을 줄이고, 시스템의 복잡도를 효과적으로 관리할 수 있습니다. 🌍

 

2. 어떻게 작동하나요?

객체들 간의 상호작용이 복잡해지면 코드 유지보수가 어려워질 수 있습니다. 미디에이터 패턴은 이런 문제를 해결하기 위해 중재자 역할의 객체를 도입하여 다음과 같은 구조를 만듭니다:

  • Colleague: 서로 상호작용하는 객체들.
  • Mediator: Colleague 간의 통신을 관리하는 중재자 객체.

Colleague 객체들은 Mediator를 통해서만 통신하며, 서로를 직접 참조하지 않습니다. 🔐

3. 실생활에서의 예

시나리오

채팅 애플리케이션에서 여러 사용자(User) 객체가 있고, 이들은 채팅룸(ChatRoom)을 통해 메시지를 주고받는 구조를 설계한다고 가정해봅시다.

코드 구현 (C#)

using System;
using System.Collections.Generic;

// Mediator 인터페이스
public interface IChatRoomMediator
{
    void SendMessage(string message, string userId);
    void RegisterUser(User user);
}

// Concrete Mediator
public class ChatRoom : IChatRoomMediator
{
    private Dictionary<string, User> _users = new Dictionary<string, User>();

    public void RegisterUser(User user)
    {
        if (!_users.ContainsKey(user.Id))
        {
            _users[user.Id] = user;
            user.SetChatRoom(this);
        }
    }

    public void SendMessage(string message, string userId)
    {
        if (_users.ContainsKey(userId))
        {
            _users[userId].Receive(message);
        }
    }
}

// Colleague
public class User
{
    public string Id { get; }
    public string Name { get; }
    private IChatRoomMediator _chatRoom;

    public User(string id, string name)
    {
        Id = id;
        Name = name;
    }

    public void SetChatRoom(IChatRoomMediator chatRoom)
    {
        _chatRoom = chatRoom;
    }

    public void Send(string message, string userId)
    {
        Console.WriteLine($"{Name} sends: {message}");
        _chatRoom.SendMessage(message, userId);
    }

    public void Receive(string message)
    {
        Console.WriteLine($"{Name} receives: {message}");
    }
}

// 사용 예
class Program
{
    static void Main(string[] args)
    {
        var chatRoom = new ChatRoom();

        var user1 = new User("1", "Alice");
        var user2 = new User("2", "Bob");

        chatRoom.RegisterUser(user1);
        chatRoom.RegisterUser(user2);

        user1.Send("Hello, Bob!", "2");
        user2.Send("Hi, Alice!", "1");
    }
}

결과

Alice sends: Hello, Bob!
Bob receives: Hello, Bob!
Bob sends: Hi, Alice!
Alice receives: Hi, Alice!

4. 이 패턴을 사용할 때 주의할 점

  • 단일 책임 원칙 준수: Mediator가 지나치게 많은 책임을 가지면 또 다른 복잡성을 초래할 수 있습니다.
  • 적합한 상황에 사용: 객체 간의 상호작용이 단순하다면, 굳이 미디에이터를 도입하지 않아도 됩니다.

5. 마무리

미디에이터 패턴은 객체 간의 의존성을 줄이고 복잡한 상호작용을 단순화하는 데 효과적입니다. 특히, 확장 가능하고 유지보수성이 높은 시스템을 설계할 때 유용합니다. 🚀

6. 참고 자료

반응형

+ Recent posts