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. 참고 자료

반응형

1. Builder 패턴이란? 🛠️

Builder 패턴은 객체 생성 과정에서 복잡성을 줄이고, 유연성을 제공하는 디자인 패턴입니다. 특히 다양한 속성을 가진 객체를 생성해야 하거나, 동일한 생성 프로세스를 통해 여러 종류의 객체를 생성해야 할 때 유용합니다.

"객체를 생성하는 방식을 분리하여 코드의 가독성과 재사용성을 높이자!"


2. Builder 패턴의 구성 요소 ✨

Builder 패턴은 객체 생성 코드를 단계별로 분리하여, 클라이언트가 객체 생성 과정에 대해 더 큰 제어권을 갖도록 설계되었습니다. 핵심은 복잡한 객체의 생성 과정을 캡슐화하여 클라이언트와 분리하는 것입니다.

구성 요소:

  • Builder: 객체 생성 단계를 정의하는 인터페이스.
  • ConcreteBuilder: Builder를 구현하며, 생성 단계별 세부 구현을 담당.
  • Director: 객체 생성 과정을 제어하며, Builder를 사용하여 최종 객체를 생성.
  • Product: 최종적으로 생성되는 객체.

3. Builder 패턴 예제 🖥️

예제: 캐릭터 생성기 (Unity3D)

게임에서 플레이어 캐릭터를 생성할 때, 다양한 장비와 스탯을 가진 캐릭터를 만들어야 한다고 가정합니다.

// Product: 생성될 객체
public class Character {
    public string Name { get; set; }
    public string Weapon { get; set; }
    public string Armor { get; set; }
    public int Health { get; set; }

    public override string ToString() {
        return $"Character: {Name}, Weapon: {Weapon}, Armor: {Armor}, Health: {Health}";
    }
}

// Builder: 생성 과정 인터페이스
public interface ICharacterBuilder {
    void SetName(string name);
    void SetWeapon(string weapon);
    void SetArmor(string armor);
    void SetHealth(int health);
    Character GetCharacter();
}

// ConcreteBuilder: 구체적인 구현
public class WarriorBuilder : ICharacterBuilder {
    private Character _character = new Character();

    public void SetName(string name) {
        _character.Name = name;
    }

    public void SetWeapon(string weapon) {
        _character.Weapon = weapon;
    }

    public void SetArmor(string armor) {
        _character.Armor = armor;
    }

    public void SetHealth(int health) {
        _character.Health = health;
    }

    public Character GetCharacter() {
        return _character;
    }
}

// Director: 생성 제어
public class CharacterDirector {
    private readonly ICharacterBuilder _builder;

    public CharacterDirector(ICharacterBuilder builder) {
        _builder = builder;
    }

    public Character Construct(string name, string weapon, string armor, int health) {
        _builder.SetName(name);
        _builder.SetWeapon(weapon);
        _builder.SetArmor(armor);
        _builder.SetHealth(health);
        return _builder.GetCharacter();
    }
}

// 사용 예
public class Example {
    public static void Main(string[] args) {
        ICharacterBuilder builder = new WarriorBuilder();
        CharacterDirector director = new CharacterDirector(builder);

        Character warrior = director.Construct("Warrior", "Sword", "Steel Armor", 100);
        Console.WriteLine(warrior);
    }
}

출력:

Character: Warrior, Weapon: Sword, Armor: Steel Armor, Health: 100

4. Builder 패턴 사용 시 고려할 점 ⚠️

  1. 장점:
    • 복잡한 객체 생성 로직을 분리하여 코드 가독성 향상.
    • 동일한 생성 로직을 사용하여 다양한 객체를 생성 가능.
  2. 단점:
    • 클래스가 많아져 구조가 복잡해질 수 있음.
    • 단순한 객체 생성에는 오히려 과도한 설계가 될 수 있음.
  3. 유의점:
    • Director의 사용은 선택 사항입니다. 필요에 따라 Builder만으로 객체를 생성할 수 있습니다.

5. Builder 패턴 요약 🏁

Builder 패턴은 복잡한 객체 생성 과정을 단순화하고, 코드의 재사용성과 유지 보수성을 높이는 데 큰 도움을 줍니다. 특히 Unity3D 같은 프레임워크에서 다양한 요소를 조합하여 객체를 생성해야 할 때 강력한 도구가 됩니다. 하지만 모든 상황에서 적합한 것은 아니므로, 사용 시 목적과 필요성을 잘 판단해야 합니다.


6. Builder 패턴 참고 자료 🔗

반응형

1. Proxy 패턴이란?

Proxy 패턴은 객체에 접근하는 인터페이스를 대리 객체를 통해 제공하는 디자인 패턴입니다. 이를 통해 원래 객체의 역할을 수행하면서도 추가적인 제어나 기능을 부여할 수 있습니다. 주로 성능 최적화, 접근 제어, 또는 원격 호출을 단순화하는 데 활용됩니다. 🤖

 


2. Proxy 패턴의 구성 요소

Proxy는 진짜 객체(Real Subject)와 동일한 인터페이스를 구현하는 대리 객체(Proxy)를 제공합니다. 이를 통해 사용자는 진짜 객체에 직접 접근하지 않고도 동일한 방식으로 작업을 수행할 수 있습니다. Proxy는 진짜 객체에 추가적인 로직을 삽입할 수 있는 좋은 위치가 됩니다.

Proxy 패턴의 주요 구성 요소는 다음과 같습니다:

  • Subject: 원래 객체와 Proxy가 따르는 인터페이스.
  • Real Subject: 실제 작업을 수행하는 객체.
  • Proxy: Real Subject에 접근을 제어하거나 추가 로직을 제공하는 객체.

예: 게임에서 텍스처와 같은 리소스를 로드할 때, 로딩을 지연시키는 Proxy 객체를 사용해 효율성을 높일 수 있습니다. 🎮


3. Proxy 패턴 예제🖥️

Unity3D 예제: 리소스 로드 지연

게임에서 무거운 리소스를 효율적으로 관리하기 위해 Proxy 패턴을 사용할 수 있습니다. 아래는 텍스처 로딩을 지연시키는 Proxy 패턴의 예제입니다.

public interface ITexture
{
    void Display();
}

// Real Subject
public class RealTexture : ITexture
{
    private string _filePath;

    public RealTexture(string filePath)
    {
        _filePath = filePath;
        LoadTextureFromDisk();
    }

    private void LoadTextureFromDisk()
    {
        Debug.Log($"Loading texture from {_filePath}...");
    }

    public void Display()
    {
        Debug.Log("Displaying texture.");
    }
}

// Proxy
public class ProxyTexture : ITexture
{
    private string _filePath;
    private RealTexture _realTexture;

    public ProxyTexture(string filePath)
    {
        _filePath = filePath;
    }

    public void Display()
    {
        if (_realTexture == null)
        {
            _realTexture = new RealTexture(_filePath); // 실제 로드 지연
        }
        _realTexture.Display();
    }
}

// 사용 예시
public class TextureLoader : MonoBehaviour
{
    void Start()
    {
        ITexture texture = new ProxyTexture("/path/to/texture.png");

        Debug.Log("Texture is not loaded yet.");
        texture.Display(); // 이 시점에서 로드 및 표시
    }
}

이 코드는 리소스를 필요할 때만 로드하는 **지연 로딩(lazy loading)**을 구현합니다. 🌟


4. Proxy 패턴 사용 시 주의점

  • 추가 복잡성: Proxy 객체를 추가하면 설계가 복잡해질 수 있습니다. 필요한 경우에만 사용하는 것이 좋습니다.
  • 성능 비용: Proxy 자체가 추가 작업을 수행하기 때문에, 잘못 구현하면 성능이 저하될 수 있습니다.
  • 메모리 관리: Proxy와 Real Subject 간의 관계를 잘 관리해야 메모리 누수를 방지할 수 있습니다. 🧠

5. 결론

Proxy 패턴은 객체 접근을 제어하고, 성능 및 효율성을 최적화할 수 있는 유용한 도구입니다. 특히 Unity3D와 같은 환경에서 리소스 관리를 개선하는 데 큰 도움이 될 수 있습니다. 하지만, 설계의 복잡도를 높이지 않도록 적재적소에 사용하는 것이 중요합니다. 👍


6. 관련 링크

반응형

1. 개요

Command 패턴은 행동을 캡슐화하여 요청자와 실행자를 분리하는 디자인 패턴입니다. 이 패턴은 행동을 객체로 만들어 동작의 실행, 취소, 재실행 등을 유연하게 관리할 수 있도록 돕습니다. ✨


2. 개념

Command 패턴의 핵심은 요청을 "명령" 객체로 감싸는 것입니다. 이를 통해 행동을 캡슐화하고, 다음과 같은 장점을 제공합니다:

  • 요청의 파라미터화: 행동을 객체로 표현하여 실행 타이밍을 유연하게 제어 가능.
  • 작업 큐 관리: 요청을 큐에 쌓아 순차적으로 처리 가능.
  • Undo/Redo: 이전 행동을 쉽게 취소하거나 반복 실행 가능.

구성 요소는 아래와 같습니다:

  1. Command 인터페이스: 실행 및 취소 동작을 정의.
  2. ConcreteCommand: 실제 동작 구현.
  3. Invoker: 명령을 실행하거나 취소.
  4. Receiver: 실제 행동을 수행하는 객체.

3. 예제

아래는 C#을 활용한 간단한 예제입니다. Unity3D에서도 동일한 원리로 활용 가능합니다.

요구사항

  • 캐릭터가 점프하거나 이동하는 행동을 Command 패턴으로 관리.

코드 구현

// Command 인터페이스
public interface ICommand
{
    void Execute();
    void Undo();
}

// Receiver
public class Character
{
    public void Jump() => Debug.Log("Character jumps");
    public void MoveLeft() => Debug.Log("Character moves left");
    public void MoveRight() => Debug.Log("Character moves right");
}

// ConcreteCommand
public class JumpCommand : ICommand
{
    private Character _character;

    public JumpCommand(Character character)
    {
        _character = character;
    }

    public void Execute() => _character.Jump();

    public void Undo() => Debug.Log("Undo Jump");
}

public class MoveCommand : ICommand
{
    private Character _character;
    private string _direction;

    public MoveCommand(Character character, string direction)
    {
        _character = character;
        _direction = direction;
    }

    public void Execute()
    {
        if (_direction == "left")
            _character.MoveLeft();
        else if (_direction == "right")
            _character.MoveRight();
    }

    public void Undo() => Debug.Log($"Undo Move {_direction}");
}

// Invoker
public class InputHandler
{
    private ICommand _command;

    public void SetCommand(ICommand command)
    {
        _command = command;
    }

    public void ExecuteCommand()
    {
        _command.Execute();
    }

    public void UndoCommand()
    {
        _command.Undo();
    }
}

// Usage
public class Game : MonoBehaviour
{
    void Start()
    {
        Character character = new Character();
        InputHandler inputHandler = new InputHandler();

        ICommand jumpCommand = new JumpCommand(character);
        ICommand moveLeftCommand = new MoveCommand(character, "left");

        inputHandler.SetCommand(jumpCommand);
        inputHandler.ExecuteCommand(); // Output: "Character jumps"

        inputHandler.SetCommand(moveLeftCommand);
        inputHandler.ExecuteCommand(); // Output: "Character moves left"

        inputHandler.UndoCommand(); // Output: "Undo Move left"
    }
}

4. 주의점 🚨

  • 명령 객체 증가: Command 패턴을 사용하면 행동마다 객체가 생성됩니다. 프로젝트 규모가 커질수록 관리가 복잡해질 수 있습니다.
  • 간단한 경우 비효율: 단순한 요청을 처리하기 위해 Command 패턴을 적용하면 오히려 코드가 복잡해질 수 있습니다. 상황에 맞게 사용해야 합니다.

5. 결론

Command 패턴은 행동을 객체로 캡슐화하여 요청과 실행을 분리하는 강력한 도구입니다. 특히 Undo/Redo 기능이나 작업 큐 관리가 필요한 경우 유용합니다. 하지만 모든 상황에서 적합한 것은 아니니, 복잡성과 효율성을 고려해 선택적으로 사용하는 것이 중요합니다. 💡


6. 관련 링크 🔗

반응형

ㅎ1. 어댑터 패턴이란?

어댑터(Adapter) 패턴은 두 개의 호환되지 않는 인터페이스를 연결해주는 디자인 패턴입니다. 마치 110V만 지원하는 전자제품을 220V 콘센트에 꽂을 수 있게 하는 변압기처럼, 서로 다른 코드 구조를 간단히 연결할 수 있습니다. 이 패턴은 코드 재사용성을 높이고 기존 클래스를 변경하지 않으면서 새 요구 사항을 만족시킬 수 있다는 장점이 있습니다. 🎯

 


2. 핵심 개념

어댑터 패턴은 다음 세 가지 구성요소로 이루어집니다:

  • 클라이언트(Client): 원하는 기능을 호출하는 측.
  • 타깃(Target): 클라이언트가 요구하는 인터페이스.
  • 어댑터(Adapter): 기존 클래스(Adaptee)를 타깃 인터페이스에 맞게 변환하는 클래스.

간단히 말해, 어댑터는 기존 코드의 "모양"을 변경해 클라이언트가 사용할 수 있도록 돕는 역할을 합니다. 🤝


3. C# 구현 예제

다음은 어댑터 패턴을 C#으로 구현한 예제입니다.

요구사항

클라이언트는 IRenderer 인터페이스를 사용하여 물체를 렌더링해야 합니다. 하지만, 기존의 두 클래스 LegacyRendererAdvancedLegacyRenderer는 이 인터페이스를 따르지 않습니다.

// Target Interface
public interface IRenderer
{
    void Render();
}

// Existing Class (Adaptee 1)
public class LegacyRenderer
{
    public void LegacyRender()
    {
        Console.WriteLine("Rendering using LegacyRenderer");
    }
}

// Existing Class (Adaptee 2)
public class AdvancedLegacyRenderer
{
    public void AdvancedRender()
    {
        Console.WriteLine("Rendering using AdvancedLegacyRenderer");
    }
}

// Adapter Class
public class RendererAdapter : IRenderer
{
    private readonly object _adaptee;

    public RendererAdapter(object adaptee)
    {
        _adaptee = adaptee;
    }

    public void Render()
    {
        if (_adaptee is LegacyRenderer legacyRenderer)
        {
            legacyRenderer.LegacyRender();
        }
        else if (_adaptee is AdvancedLegacyRenderer advancedRenderer)
        {
            advancedRenderer.AdvancedRender();
        }
        else
        {
            throw new NotSupportedException("Unsupported adaptee type");
        }
    }
}

// Client Code
class Program
{
    static void Main()
    {
        LegacyRenderer legacyRenderer = new LegacyRenderer();
        AdvancedLegacyRenderer advancedRenderer = new AdvancedLegacyRenderer();

        IRenderer renderer1 = new RendererAdapter(legacyRenderer);
        IRenderer renderer2 = new RendererAdapter(advancedRenderer);

        renderer1.Render(); // Output: Rendering using LegacyRenderer
        renderer2.Render(); // Output: Rendering using AdvancedLegacyRenderer
    }
}

설명

  1. 클라이언트는 IRenderer 인터페이스만 사용합니다.
  2. 기존 클래스 LegacyRendererAdvancedLegacyRendererIRenderer를 따르지 않지만, 어댑터 RendererAdapter를 통해 연결되었습니다.
  3. 어댑터는 각 클래스의 메서드를 호출하면서도 클라이언트의 기대에 맞는 인터페이스를 제공합니다. 🧩

4. 어댑터가 필요한 순간 🧐

어댑터 패턴은 다음과 같은 상황에서 특히 유용합니다:

  1. 레거시 코드 통합: 기존 시스템과 새로운 시스템을 연결할 때.
  2. 다른 인터페이스 간 호환성 확보: 서로 다른 팀에서 개발된 모듈을 연결해야 할 때.
  3. 인터페이스 변경 없이 기능 확장: 기존 클래스를 수정하지 않고 기능을 추가해야 할 때.

주의: 어댑터 패턴을 너무 자주 사용하면 코드가 복잡해질 수 있습니다. 따라서 필요 이상으로 사용하지 않도록 주의하세요! ⚠️


5. 결론

어댑터 패턴은 기존 코드를 재사용하며 새로운 요구 사항에 대응할 수 있는 강력한 도구입니다. 하지만, 단순히 코드 구조를 맞추는 역할만 하기 때문에 지나치게 남용하면 오히려 유지보수가 어려워질 수 있습니다.

언제 사용하는 것이 적절할지 신중하게 판단하고, 필요할 때만 적용하는 것이 중요합니다. 🚀


6. 관련 링크

반응형

1. 개요

디자인 패턴 중 Decorator 패턴은 기존 객체에 새로운 기능을 동적으로 추가하고 싶을 때 유용한 패턴입니다. 상속 없이도 객체의 행동을 확장할 수 있어, 코드 재사용성과 유지보수성을 높이는 데 탁월합니다. 🎯


2. 개념

Decorator 패턴은 객체를 감싸는(wrapper) 방식으로 동작합니다. 핵심 아이디어는 기존 객체를 수정하지 않고도 새로운 책임을 부여할 수 있다는 점입니다.

구조

  1. Component: 기본 인터페이스 또는 추상 클래스
  2. ConcreteComponent: 실제 기능을 구현한 클래스
  3. Decorator: Component를 확장할 추상 클래스

ConcreteDecorator: 구체적인 기능 확장을 구현한 클래스

출처: 위키


3. 예제

C#에서의 간단한 예제로, 커피에 첨가물을 추가하는 시뮬레이션을 만들어보겠습니다. ☕

// Component
public interface ICoffee
{
    string GetDescription();
    double GetCost();
}

// ConcreteComponent
public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Simple Coffee";

    public double GetCost() => 2.0;
}

// Decorator
public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;

    public CoffeeDecorator(ICoffee coffee)
    {
        _coffee = coffee;
    }

    public virtual string GetDescription() => _coffee.GetDescription();

    public virtual double GetCost() => _coffee.GetCost();
}

// ConcreteDecorator
public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }

    public override string GetDescription() => base.GetDescription() + ", Milk";

    public override double GetCost() => base.GetCost() + 0.5;
}

public class SugarDecorator : CoffeeDecorator
{
    public SugarDecorator(ICoffee coffee) : base(coffee) { }

    public override string GetDescription() => base.GetDescription() + ", Sugar";

    public override double GetCost() => base.GetCost() + 0.2;
}

// Usage
class Program
{
    static void Main(string[] args)
    {
        ICoffee coffee = new SimpleCoffee();
        Console.WriteLine($"{coffee.GetDescription()} : ${coffee.GetCost()}");

        coffee = new MilkDecorator(coffee);
        Console.WriteLine($"{coffee.GetDescription()} : ${coffee.GetCost()}");

        coffee = new SugarDecorator(coffee);
        Console.WriteLine($"{coffee.GetDescription()} : ${coffee.GetCost()}");
    }
}

실행 결과

Simple Coffee : $2.0
Simple Coffee, Milk : $2.5
Simple Coffee, Milk, Sugar : $2.7

4. 장점과 주의점 🛠️

장점

  • 유연성: 객체의 기능을 런타임에 동적으로 확장 가능.
  • 조합 가능성: 다양한 Decorator를 조합해 새로운 기능 생성.
  • OCP(Open/Closed Principle): 기존 코드를 변경하지 않고 기능 추가 가능.

주의점

  • 많은 클래스: Decorator와 ConcreteDecorator가 많아질 경우 복잡도가 증가.
  • 디버깅 어려움: 래핑된 구조로 인해 디버깅이 다소 까다로울 수 있음.

5. 결론

Decorator 패턴은 객체 지향 설계의 유연성을 극대화하는 강력한 도구입니다. 기존 객체를 수정하지 않고 기능을 확장해야 하는 상황에서 매우 유용하며, 특히 계층적 상속의 한계를 극복하는 데 도움을 줍니다. 다만, 과도한 사용은 복잡도를 높일 수 있으니 신중히 활용해야 합니다. ✅


6. 관련 링크

반응형

1. 개요

Observer Pattern은 객체 간의 1:N 관계를 설정하여 한 객체의 상태 변화가 다른 객체들에게 자동으로 통지되도록 만드는 디자인 패턴입니다. 게임에서 UI 업데이트, 이벤트 처리, 데이터 동기화와 같은 상황에서 자주 사용됩니다. 🎯


2. 개념

Observer Pattern은 다음 두 가지 주요 구성 요소로 이루어집니다:

  1. Subject (주체): 상태를 보유하며 Observer들을 등록하거나 해제하고, 상태가 변경될 때 Observer들에게 알리는 역할을 합니다.
  2. Observer (관찰자): Subject를 구독하여 상태 변경 알림을 수신하고 적절한 작업을 수행합니다.

이 패턴은 느슨한 결합(loose coupling)을 유지하도록 설계되어, 코드의 유지보수가 쉬워집니다. 🔧


3. 예제

아래는 Unity3D에서 Observer Pattern을 구현한 간단한 예제입니다.

namespace System
{
    public interface IObservable<out T>
    {
        IDisposable Subscribe(IObserver<T> observer);
    }

    public interface IObserver<in T>
    {
        void OnCompleted();
        void OnError(Exception error);
        void OnNext(T value);
    }
}

위 인터페이스를 구현한 예제는 이 링크를 참고바랍니다.

 

구현 시나리오

플레이어의 체력이 변할 때 UI를 자동으로 업데이트하는 상황을 가정합니다.

using System;
using System.Collections.Generic;
using UnityEngine;

// Subject: PlayerHealth
public class PlayerHealth : MonoBehaviour
{
    public event Action<int> OnHealthChanged; // Observer들에게 알림

    private int health;

    public int Health
    {
        get => health;
        set
        {
            health = value;
            OnHealthChanged?.Invoke(health); // 상태 변경 시 알림
        }
    }
}

// Observer: HealthUI
public class HealthUI : MonoBehaviour
{
    [SerializeField] private PlayerHealth playerHealth;

    private void OnEnable()
    {
        playerHealth.OnHealthChanged += UpdateUI; // 이벤트 구독
    }

    private void OnDisable()
    {
        playerHealth.OnHealthChanged -= UpdateUI; // 이벤트 해제
    }

    private void UpdateUI(int currentHealth)
    {
        Debug.Log($"Player Health Updated: {currentHealth}");
        // 실제 UI 업데이트 로직 삽입
    }
}

주요 흐름

  1. PlayerHealth 클래스는 OnHealthChanged 이벤트를 통해 Observer들에게 체력 변경 사항을 알립니다.
  2. HealthUI 클래스는 해당 이벤트를 구독하여 체력 변경 시 UI를 업데이트합니다. 💡

 



 

 

4. Observer Pattern 활용 시 주의점

💡 이벤트 관리의 중요성

Observer Pattern은 강력하지만, 이벤트 해제를 적절히 처리하지 않으면 메모리 누수와 같은 문제가 발생할 수 있습니다. 특히 Unity3D에서는 OnEnableOnDisable에서 이벤트 등록과 해제를 관리하는 것이 중요합니다.


5. 결론

Observer Pattern은 객체 간의 관계를 간결하게 설정하고 유지보수를 용이하게 만듭니다. 특히, 게임 개발에서 UI 동기화, 이벤트 관리, 데이터 변경 반영과 같은 작업에 유용합니다. 하지만 이벤트 관리에 신경 쓰지 않으면 오히려 버그를 유발할 수 있으므로 주의가 필요합니다. 🚀


6. 관련 링크

 

관찰자 디자인 패턴 - .NET

.NET의 관찰자 디자인 패턴에 대해 알아봅니다. 이 패턴은 구독자가 공급자에 등록하고 공급자로부터 알림을 받을 수 있게 합니다.

learn.microsoft.com

 

반응형

+ Recent posts