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

 

반응형

1. 개요

Factory 패턴은 객체 생성 로직을 별도의 클래스나 메서드로 분리하여, 객체 생성 과정을 더 유연하고 확장 가능하게 만드는 디자인 패턴입니다. ✨ 이 패턴은 객체 생성에 따른 의존성을 줄이고, 코드의 유지보수성을 높이는 데 매우 유용합니다.

언제 사용할까?

  • 클래스 생성 로직이 복잡할 때
  • 유사한 객체가 여러 종류일 때
  • 객체의 생성 방식이 변경될 가능성이 있을 때

2. 개념

Factory 패턴은 팩토리 메서드(Factory Method)를 통해 객체를 생성합니다. 객체 생성 과정을 캡슐화함으로써, 클라이언트가 생성된 객체의 내부 구조를 알 필요가 없게 만듭니다.

핵심 구성 요소

  1. Creator: 객체를 생성하는 역할을 담당 (보통 추상 클래스 또는 인터페이스)
  2. ConcreteCreator: 객체 생성 로직을 구체적으로 구현하는 클래스
  3. Product: 생성되는 객체의 인터페이스
  4. ConcreteProduct: 실제 생성된 객체

3. 예제

아래는 C#으로 간단히 구현한 예제입니다. 🎉

문제: 여러 종류의 피자를 만드는 애플리케이션

// Product
public interface IPizza
{
    void Prepare();
}

// ConcreteProduct
public class MargheritaPizza : IPizza
{
    public void Prepare()
    {
        Console.WriteLine("Preparing Margherita Pizza! 🍕");
    }
}

public class PepperoniPizza : IPizza
{
    public void Prepare()
    {
        Console.WriteLine("Preparing Pepperoni Pizza! 🍕");
    }
}

// Creator
public abstract class PizzaFactory
{
    public abstract IPizza CreatePizza();
}

// ConcreteCreator
public class MargheritaPizzaFactory : PizzaFactory
{
    public override IPizza CreatePizza()
    {
        return new MargheritaPizza();
    }
}

public class PepperoniPizzaFactory : PizzaFactory
{
    public override IPizza CreatePizza()
    {
        return new PepperoniPizza();
    }
}

// Client
class Program
{
    static void Main(string[] args)
    {
        PizzaFactory factory = new MargheritaPizzaFactory();
        IPizza pizza = factory.CreatePizza();
        pizza.Prepare();

        factory = new PepperoniPizzaFactory();
        pizza = factory.CreatePizza();
        pizza.Prepare();
    }
}

결과

Preparing Margherita Pizza! 🍕
Preparing Pepperoni Pizza! 🍕

4. 주의점

  • 추가적인 클래스 증가: Factory 패턴을 사용하면 클래스가 증가할 수 있으므로, 간단한 프로젝트에서는 오히려 복잡도를 높일 수 있습니다.
  • 추상화와 유연성: 객체 생성 로직이 복잡하거나 변동이 잦지 않다면 굳이 Factory 패턴을 사용하지 않아도 됩니다. 🚧

5. 결론

Factory 패턴은 객체 생성의 책임을 분리함으로써, 코드의 유연성과 재사용성을 높이는 강력한 도구입니다. ✨ 하지만 필요 이상으로 사용하면 코드가 과도하게 복잡해질 수 있으니, 상황에 맞게 적용하는 것이 중요합니다.

6. 관련 링크

 

Factory method pattern - Wikipedia

From Wikipedia, the free encyclopedia Object-oriented software design pattern In object-oriented programming, the factory method pattern is a design pattern that uses factory methods to deal with the problem of creating objects without having to specify th

en.wikipedia.org

다이어그램

반응형

1. 개요

소프트웨어 개발을 하다 보면 알고리즘이나 동작 방식을 상황에 따라 유연하게 변경해야 할 때가 많습니다. Strategy 패턴은 이러한 문제를 해결하기 위해 고안된 디자인 패턴으로, 알고리즘을 캡슐화하여 서로 교체 가능하도록 만드는 것을 목표로 합니다. 이 글에서는 Strategy 패턴의 개념, C# 구현 예제, 그리고 사용 시 주의할 점을 알아보겠습니다.


2. 개념

Strategy 패턴은 다음과 같은 상황에서 유용합니다:

  • 여러 알고리즘 중에서 런타임에 하나를 선택해야 하는 경우.
  • 특정 동작을 다른 객체와 독립적으로 교체하거나 확장하고 싶은 경우.

Strategy 패턴의 핵심은 "행동(Behavior)을 객체로 캡슐화"하여 클라이언트 코드에서 알고리즘을 직접 변경하지 않고도 동작을 바꿀 수 있도록 하는 것입니다.

구조는 다음과 같습니다:

  1. Context: 전략 객체를 사용하는 주요 클래스.
  2. Strategy: 알고리즘의 인터페이스.
  3. ConcreteStrategy: 특정 알고리즘을 구현한 클래스.

3. 예제

요구사항

사용자가 할인 정책을 선택할 수 있는 온라인 쇼핑몰을 만든다고 가정합시다. 할인 정책에는 다음이 포함됩니다:

  • 기본 할인 없음
  • 정률 할인 (10% 할인)
  • 정액 할인 (5,000원 할인)

코드 구현

// Strategy 인터페이스
public interface IDiscountStrategy
{
    decimal ApplyDiscount(decimal price);
}

// ConcreteStrategy: 기본 할인 없음
public class NoDiscountStrategy : IDiscountStrategy
{
    public decimal ApplyDiscount(decimal price)
    {
        return price;
    }
}

// ConcreteStrategy: 정률 할인
public class PercentageDiscountStrategy : IDiscountStrategy
{
    private readonly decimal _percentage;

    public PercentageDiscountStrategy(decimal percentage)
    {
        _percentage = percentage;
    }

    public decimal ApplyDiscount(decimal price)
    {
        return price - (price * _percentage);
    }
}

// ConcreteStrategy: 정액 할인
public class FixedDiscountStrategy : IDiscountStrategy
{
    private readonly decimal _discountAmount;

    public FixedDiscountStrategy(decimal discountAmount)
    {
        _discountAmount = discountAmount;
    }

    public decimal ApplyDiscount(decimal price)
    {
        return price - _discountAmount;
    }
}

// Context 클래스
public class ShoppingCart
{
    private IDiscountStrategy _discountStrategy;

    public ShoppingCart(IDiscountStrategy discountStrategy)
    {
        _discountStrategy = discountStrategy;
    }

    public void SetDiscountStrategy(IDiscountStrategy discountStrategy)
    {
        _discountStrategy = discountStrategy;
    }

    public decimal CalculateTotal(decimal price)
    {
        return _discountStrategy.ApplyDiscount(price);
    }
}

// 사용 예
class Program
{
    static void Main(string[] args)
    {
        decimal originalPrice = 50000m;

        // 기본 할인 없음
        var noDiscount = new ShoppingCart(new NoDiscountStrategy());
        Console.WriteLine($"No Discount: {noDiscount.CalculateTotal(originalPrice)}원");

        // 10% 정률 할인
        var percentageDiscount = new ShoppingCart(new PercentageDiscountStrategy(0.1m));
        Console.WriteLine($"10% Discount: {percentageDiscount.CalculateTotal(originalPrice)}원");

        // 5,000원 정액 할인
        var fixedDiscount = new ShoppingCart(new FixedDiscountStrategy(5000m));
        Console.WriteLine($"5,000원 Discount: {fixedDiscount.CalculateTotal(originalPrice)}원");
    }
}

4. 주의점: 남용을 피하라

Strategy 패턴은 분명 강력한 도구지만, 모든 경우에 적합하지는 않습니다. 다음 사항을 염두에 두세요:

  1. 간단한 경우에 복잡도 증가: 너무 간단한 문제를 해결하려고 Strategy 패턴을 사용하면 오히려 코드가 복잡해질 수 있습니다.
  2. 객체 생성 비용: 많은 전략 객체를 생성하고 교체하는 과정에서 성능에 영향을 줄 수 있습니다.

5. 결론

Strategy 패턴은 다양한 알고리즘을 유연하게 사용할 수 있도록 해주는 강력한 디자인 패턴입니다. 특히, 런타임에 동작 방식을 변경해야 하거나 코드의 가독성과 확장성을 높이고 싶을 때 유용합니다. 하지만 과도한 사용은 피해야 하며, 상황에 맞는지 신중히 검토해야 합니다.


6. 관련 링크

다이어그램

반응형

1. 개요

Singleton은 객체의 인스턴스를 하나만 생성하여 전역적으로 접근할 수 있도록 보장하는 디자인 패턴입니다. 애플리케이션에서 설정 관리, 로깅, 데이터베이스 연결과 같은 전역적으로 공유해야 하는 리소스에 적합합니다.


2. 개념

Singleton 패턴의 핵심은 다음과 같습니다:

  • 클래스의 인스턴스가 단 하나만 생성되도록 제한.
  • 해당 인스턴스에 글로벌 접근점을 제공.

이 패턴은 정적 변수를 통해 인스턴스를 관리하며, 외부에서 생성자를 직접 호출하지 못하도록 private으로 제한합니다.


3. 예제

다음은 C#으로 작성된 Singleton 패턴의 간단한 구현 예제입니다:

public class Singleton
{
    private static Singleton _instance;
    private static readonly object _lock = new object();

    // Private constructor to prevent instantiation from outside
    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new Singleton();
                }
            }
            return _instance;
        }
    }

    public void LogMessage(string message)
    {
        Console.WriteLine($"[Singleton Log]: {message}");
    }
}

// Usage
class Program
{
    static void Main()
    {
        Singleton.Instance.LogMessage("Hello, Singleton!");
    }
}

설명:

  1. _instance: Singleton 객체를 담는 정적 변수.
  2. _lock: 멀티스레드 환경에서 동시 접근 문제를 방지.
  3. Private 생성자: 외부에서 객체 생성을 제한.
  4. Instance 프로퍼티: Singleton 객체를 반환하며, 필요 시 초기화.

4. 주의점

  • 결합도 증가: Singleton은 전역적으로 접근 가능하기 때문에 코드 흐름이 복잡해지고, 의존성을 파악하기 힘듭니다. 유지보수가 어려워질 수 있습니다.
  • SRP(Single Responsibility Principle) 위반 가능성: Singleton 클래스가 너무 많은 역할을 맡는 슈퍼클래스가 되기 쉬워, 단일 책임 원칙이 깨질 위험이 있습니다.

5. 결론

Singleton 패턴은 자원을 효율적으로 관리하고 일관된 접근 방식을 제공합니다. 하지만, 남용하면 결합도를 높이고 테스트를 어렵게 만들 수 있으니 신중히 사용해야 합니다.


6. 관련 링크


다이어그램

반응형

+ Recent posts