1. ECS 패턴이란?

ECS(Entity-Component-System)는 소프트웨어 설계 패턴 중 하나로, 유연하고 재사용 가능한 구조를 제공합니다. 이 패턴은 데이터와 로직을 분리하여 유지보수성과 확장성을 높이는 데 초점을 맞춥니다. 🚀

ECS는 다음과 같은 구조로 구성됩니다:

  • Entity: 고유 식별자를 가진 객체.
  • Component: 데이터를 포함하는 독립적인 객체.
  • System: 데이터를 처리하는 로직.

이 설계 패턴은 대규모 프로젝트에서 객체 간의 강한 결합을 줄이고, 기능을 모듈화하여 효율적인 관리와 확장이 가능하게 합니다.

 

출처: 위키


2. ECS 패턴의 구성 요소

1) Entity (엔티티)

  • 고유 ID를 가진 단순한 객체.
  • 데이터나 로직을 포함하지 않고, 컴포넌트의 컨테이너 역할을 합니다.

2) Component (컴포넌트)

  • 순수 데이터만 포함하며, 엔티티의 속성을 정의합니다.
  • 예: 위치, 속도, 상태 등 다양한 정보.

3) System (시스템)

  • 특정 컴포넌트를 처리하는 로직을 담당합니다.
  • 데이터를 처리하는 데 필요한 알고리즘을 포함하며, 엔티티와 컴포넌트를 직접 참조하지 않습니다.

3. ECS 패턴 예제

다음은 C#으로 간단한 ECS 패턴을 구현한 예제입니다.

using System;
using System.Collections.Generic;

// 1. Component: 데이터를 정의합니다.
public class PositionComponent
{
    public float X { get; set; }
    public float Y { get; set; }
}

public class VelocityComponent
{
    public float X { get; set; }
    public float Y { get; set; }
}

// 2. Entity: 컴포넌트를 포함하는 객체입니다.
public class Entity
{
    public int Id { get; private set; }
    public Dictionary<Type, object> Components = new();

    public Entity(int id)
    {
        Id = id;
    }

    public void AddComponent<T>(T component)
    {
        Components[typeof(T)] = component;
    }

    public T GetComponent<T>()
    {
        return (T)Components[typeof(T)];
    }
}

// 3. System: 로직을 구현합니다.
public class MovementSystem
{
    public void Update(Entity entity, float deltaTime)
    {
        var position = entity.GetComponent<PositionComponent>();
        var velocity = entity.GetComponent<VelocityComponent>();

        position.X += velocity.X * deltaTime;
        position.Y += velocity.Y * deltaTime;
    }
}

// 실행 예제
public class Program
{
    public static void Main()
    {
        var entity = new Entity(1);
        entity.AddComponent(new PositionComponent { X = 0, Y = 0 });
        entity.AddComponent(new VelocityComponent { X = 1, Y = 1 });

        var movementSystem = new MovementSystem();

        for (int i = 0; i < 5; i++)
        {
            movementSystem.Update(entity, 1.0f);
            var position = entity.GetComponent<PositionComponent>();
            Console.WriteLine($"Time: {i + 1}, Position: ({position.X}, {position.Y})");
        }
    }
}

이 예제에서는 PositionComponentVelocityComponent를 가진 엔티티가 MovementSystem을 통해 이동합니다. 데이터와 로직이 명확히 분리되어 유지보수가 용이합니다.


4. ECS 패턴 사용 시 고려할 점

장점 🌟

  1. 유연성: 엔티티에 컴포넌트를 동적으로 추가하거나 제거하여 다양한 동작을 구현할 수 있습니다.
  2. 모듈화: 데이터와 로직의 분리로 재사용성이 높아집니다.
  3. 확장성: 새로운 컴포넌트와 시스템을 추가하는 것이 간단합니다.

주의점 ⚠️

  1. 복잡성 증가: 단순한 프로젝트에서는 과도하게 복잡한 구조가 될 수 있습니다.
  2. 초기 설계 비용: 시스템과 컴포넌트를 세분화하는 초기 비용이 필요합니다.

5. 결론

ECS 패턴은 데이터 중심 설계를 통해 유지보수성과 확장성을 극대화하는 강력한 설계 패턴입니다. 특히 대규모 애플리케이션에서 객체 간의 강한 결합을 줄이고 모듈화된 구조를 제공하는 데 적합합니다. 다만, 모든 프로젝트에 적합하지는 않으므로 복잡성과 요구 사항을 고려해 선택해야 합니다. 😊


6. 관련 링크

반응형

1. 퍼사드 패턴이란?

퍼사드(Facade) 패턴은 복잡한 서브시스템을 단순화하여 클라이언트가 더 쉽게 사용할 수 있도록 인터페이스를 제공하는 디자인 패턴입니다. 말 그대로 "건물의 정면(Facade)"처럼 내부 구조를 숨기고 단순한 접근점을 제공합니다. 이를 통해 코드의 가독성을 높이고 유지보수를 쉽게 만들어줍니다.

2. 퍼사드 패턴의 구성 요소

퍼사드 패턴은 세 가지 주요 구성 요소로 이루어져 있습니다:

  • 서브시스템(Subsystem): 복잡한 내부 기능을 제공하는 클래스나 모듈.
  • 퍼사드(Facade): 서브시스템에 대한 단순화된 인터페이스를 제공하는 클래스.
  • 클라이언트(Client): 퍼사드 클래스를 통해 서브시스템과 상호작용하는 주체.

 

출처: 위키

동작 원리

퍼사드 클래스는 여러 서브시스템을 하나로 묶어주는 역할을 합니다. 클라이언트는 서브시스템의 복잡한 동작을 몰라도 퍼사드를 통해 쉽게 접근할 수 있습니다.

3. 퍼사드 패턴 예제 (C#)

문제 상황

복잡한 홈 엔터테인먼트 시스템(예: TV, 스피커, 스트리밍 장치)을 설정하는 과정을 단순화하기 위해 퍼사드 패턴을 적용해보겠습니다.

// Subsystems
public class Television
{
    public void TurnOn()
    {
        Console.WriteLine("Turning on the TV.");
    }
    public void SetChannel(int channel)
    {
        Console.WriteLine($"Setting TV channel to {channel}.");
    }
}

public class SoundSystem
{
    public void TurnOn()
    {
        Console.WriteLine("Turning on the sound system.");
    }
    public void SetVolume(int volume)
    {
        Console.WriteLine($"Setting sound system volume to {volume}.");
    }
}

public class StreamingDevice
{
    public void Connect()
    {
        Console.WriteLine("Connecting to the streaming device.");
    }
    public void Play(string movie)
    {
        Console.WriteLine($"Playing movie: {movie}.");
    }
}

// Facade
public class HomeTheaterFacade
{
    private Television tv;
    private SoundSystem sound;
    private StreamingDevice streaming;

    public HomeTheaterFacade()
    {
        tv = new Television();
        sound = new SoundSystem();
        streaming = new StreamingDevice();
    }

    public void WatchMovie(string movie)
    {
        Console.WriteLine("Setting up the home theater...");
        tv.TurnOn();
        tv.SetChannel(3);
        sound.TurnOn();
        sound.SetVolume(20);
        streaming.Connect();
        streaming.Play(movie);
        Console.WriteLine("Enjoy your movie!");
    }
}

// Client
public class Program
{
    public static void Main(string[] args)
    {
        HomeTheaterFacade homeTheater = new HomeTheaterFacade();
        homeTheater.WatchMovie("Inception");
    }
}

실행 결과

Setting up the home theater...
Turning on the TV.
Setting TV channel to 3.
Turning on the sound system.
Setting sound system volume to 20.
Connecting to the streaming device.
Playing movie: Inception.
Enjoy your movie!

이 예제에서 클라이언트는 복잡한 홈 엔터테인먼트 시스템의 설정 과정을 몰라도 HomeTheaterFacade 클래스를 통해 간단히 영화 감상을 시작할 수 있습니다.

4. 퍼사드 패턴 사용 시 고려할 점

장점

  • 간결한 인터페이스 제공: 클라이언트는 복잡한 서브시스템에 대해 알 필요가 없습니다.
  • 유지보수 용이성: 서브시스템을 수정해도 퍼사드 인터페이스만 유지되면 클라이언트 코드를 변경할 필요가 없습니다.
  • 결합도 감소: 클라이언트와 서브시스템 간의 의존성을 줄여줍니다.

주의점

  • 유연성 감소: 퍼사드에 의존하게 되면 서브시스템의 세부 기능에 직접 접근하기 어려워질 수 있습니다.
  • 필요 이상으로 단순화하지 않기: 퍼사드를 설계할 때 너무 많은 기능을 포함하면 오히려 유지보수가 어려워질 수 있습니다.

5. 결론

퍼사드 패턴은 복잡한 서브시스템을 숨기고 단순한 인터페이스를 제공함으로써 코드의 가독성과 유지보수성을 높여줍니다. 특히 서브시스템이 복잡하고 여러 모듈이 상호작용해야 하는 상황에서 매우 유용합니다. 하지만 지나치게 단순화하거나 필요 이상으로 사용하지 않도록 주의해야 합니다.

6. 관련 링크

반응형

1. 메멘토 패턴이란?

메멘토 패턴은 객체의 상태를 캡슐화하여 저장하고, 나중에 이를 복원할 수 있도록 하는 디자인 패턴입니다. 주로 실행 취소(Undo) 기능을 구현하거나 특정 시점의 상태를 저장해야 할 때 사용됩니다. 이 패턴은 객체의 내부 구조를 노출하지 않으면서도 상태를 저장할 수 있다는 점에서 유용합니다. 😊

2. 메멘토 패턴의 구성 요소

메멘토 패턴은 다음 세 가지 주요 구성 요소로 이루어집니다:

  1. Originator (원본 객체): 저장하고자 하는 상태를 가진 객체입니다.
  2. Memento (메멘토): 원본 객체의 상태를 저장하는 객체입니다.
  3. Caretaker (관리자): 메멘토 객체를 저장하고 관리하며, 원본 객체의 상태를 복원할 때 이를 사용합니다.

    출처: 위키

3. 메멘토 패턴 예제 (C# 코드)

아래는 메멘토 패턴을 사용해 텍스트 편집기의 실행 취소 기능을 구현한 간단한 예제입니다:

using System;
using System.Collections.Generic;

// Originator: 원본 객체
class TextEditor {
    public string Content { get; private set; } = string.Empty;

    public void Type(string words) {
        Content += words;
    }

    public Memento Save() {
        return new Memento(Content);
    }

    public void Restore(Memento memento) {
        Content = memento.State;
    }

    public override string ToString() {
        return Content;
    }
}

// Memento: 메멘토 객체
class Memento {
    public string State { get; }

    public Memento(string state) {
        State = state;
    }
}

// Caretaker: 관리자 객체
class Caretaker {
    private readonly Stack<Memento> _history = new Stack<Memento>();

    public void Save(TextEditor editor) {
        _history.Push(editor.Save());
    }

    public void Undo(TextEditor editor) {
        if (_history.Count > 0) {
            editor.Restore(_history.Pop());
        } else {
            Console.WriteLine("No states to undo.");
        }
    }
}

// Example Usage
class Program {
    static void Main() {
        var editor = new TextEditor();
        var caretaker = new Caretaker();

        editor.Type("Hello, ");
        caretaker.Save(editor);

        editor.Type("world! ");
        caretaker.Save(editor);

        editor.Type("This is a test.");
        Console.WriteLine("Current Content: " + editor);

        caretaker.Undo(editor);
        Console.WriteLine("After Undo: " + editor);

        caretaker.Undo(editor);
        Console.WriteLine("After Undo: " + editor);
    }
}

출력 결과:

Current Content: Hello, world! This is a test.
After Undo: Hello, world!
After Undo: Hello,

4. 메멘토 패턴 사용 시 고려할 점

  • 상태 캡슐화: 메멘토 객체는 원본 객체의 내부 구조를 노출하지 않아야 합니다. 이를 위해 메멘토를 불변(Immutable)으로 설계하는 것이 좋습니다.

5. 결론

메멘토 패턴은 객체의 상태를 안전하게 저장하고 복구할 수 있는 강력한 도구입니다. 특히, 실행 취소와 같은 기능을 구현할 때 매우 유용합니다. 다만 메모리 사용량과 저장된 상태의 관리 측면에서 주의가 필요합니다. ✨

6. 관련 링크

반응형

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

반응형

+ Recent posts