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

반응형

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. 관련 링크

반응형

+ Recent posts