Study/C#

C# 11편: 디자인 패턴으로 코드의 재사용성 및 유지보수성 향상시키기

wawManager 2024. 10. 30. 12:00
728x90

1. 디자인 패턴이란?

**디자인 패턴(Design Pattern)**은 자주 발생하는 문제를 해결하기 위한 객체지향 설계 원칙으로, 코드의 구조를 표준화하여 재사용성유지보수성을 높이는 데 도움을 줍니다. 특히 C#에서는 다양한 디자인 패턴을 활용하여 효율적이고 확장 가능한 코드를 작성할 수 있습니다.
디자인 패턴은 크게 세 가지로 분류됩니다:

  • 생성 패턴: 객체 생성 방식을 관리하는 패턴
  • 구조 패턴: 클래스 및 객체를 조합해 더 큰 구조를 만드는 패턴
  • 행위 패턴: 객체 간 상호작용을 정의하고 관리하는 패턴

2. 생성 패턴 (Creational Patterns)

생성 패턴은 객체 생성 방식을 제어하여 필요할 때 유연하게 객체를 생성하고, 코드의 결합도를 낮추는 패턴입니다.
 

2.1 싱글톤 패턴 (Singleton Pattern)

싱글톤 패턴은 특정 클래스의 인스턴스를 하나만 생성하고, 어디에서든 이 인스턴스에 접근할 수 있도록 보장하는 패턴입니다. 주로 공유 자원이나 전역 상태를 관리할 때 유용합니다.

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

    private Singleton() { }

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

    public void ShowMessage() => Console.WriteLine("싱글톤 인스턴스입니다!");
}

사용 예시

class Program
{
    static void Main(string[] args)
    {
        Singleton instance1 = Singleton.Instance;
        Singleton instance2 = Singleton.Instance;

        instance1.ShowMessage();
        Console.WriteLine(instance1 == instance2); // True (동일한 인스턴스)
    }
}

2.2 팩토리 패턴 (Factory Pattern)

팩토리 패턴은 객체 생성 로직을 팩토리(공장) 메서드에 캡슐화하여, 객체 생성 방식을 변경할 수 있는 패턴입니다. 이 패턴을 사용하면 객체 생성 로직을 한 곳에서 관리하여 유연성을 높일 수 있습니다.

interface IProduct
{
    void Use();
}

class ProductA : IProduct
{
    public void Use() => Console.WriteLine("ProductA 사용");
}

class ProductB : IProduct
{
    public void Use() => Console.WriteLine("ProductB 사용");
}

class ProductFactory
{
    public static IProduct CreateProduct(string type)
    {
        return type switch
        {
            "A" => new ProductA(),
            "B" => new ProductB(),
            _ => throw new ArgumentException("잘못된 제품 타입")
        };
    }
}

사용 예시

class Program
{
    static void Main(string[] args)
    {
        IProduct product = ProductFactory.CreateProduct("A");
        product.Use(); // ProductA 사용
    }
}

3. 구조 패턴 (Structural Patterns)

구조 패턴은 객체나 클래스를 조합해 더 큰 구조를 만들고, 객체 간의 관계를 쉽게 관리할 수 있도록 하는 패턴입니다.
 

3.1 어댑터 패턴 (Adapter Pattern)

어댑터 패턴호환되지 않는 인터페이스를 연결해주는 패턴으로, 서로 다른 인터페이스를 가진 클래스들이 상호작용할 수 있게 도와줍니다.

interface ITarget
{
    void Request();
}

class Adaptee
{
    public void SpecificRequest() => Console.WriteLine("호환되지 않는 요청");
}

class Adapter : ITarget
{
    private Adaptee _adaptee;
    public Adapter(Adaptee adaptee) => _adaptee = adaptee;

    public void Request() => _adaptee.SpecificRequest();
}

사용 예시

class Program
{
    static void Main(string[] args)
    {
        Adaptee adaptee = new Adaptee();
        ITarget target = new Adapter(adaptee);
        target.Request(); // 호환되지 않는 요청
    }
}

3.2 데코레이터 패턴 (Decorator Pattern)

데코레이터 패턴객체를 감싸는 방식으로 기능을 추가할 때 사용합니다. 기존 클래스의 기능을 변경하지 않고 새로운 기능을 추가할 수 있는 장점이 있습니다.

interface IComponent
{
    void Operation();
}

class ConcreteComponent : IComponent
{
    public void Operation() => Console.WriteLine("기본 동작");
}

class Decorator : IComponent
{
    private IComponent _component;
    public Decorator(IComponent component) => _component = component;

    public virtual void Operation() => _component.Operation();
}

class ConcreteDecorator : Decorator
{
    public ConcreteDecorator(IComponent component) : base(component) { }

    public override void Operation()
    {
        base.Operation();
        Console.WriteLine("추가 기능 동작");
    }
}

사용 예시

class Program
{
    static void Main(string[] args)
    {
        IComponent component = new ConcreteComponent();
        IComponent decoratedComponent = new ConcreteDecorator(component);
        decoratedComponent.Operation(); // 기본 동작, 추가 기능 동작
    }
}

4. 행위 패턴 (Behavioral Patterns)

행위 패턴은 객체나 클래스 간 상호작용을 정의하고 관리하는 패턴입니다.
 

4.1 옵저버 패턴 (Observer Pattern)

옵저버 패턴상태 변화를 감지하여 자동으로 알림을 받는 패턴으로, 주로 이벤트 기반 시스템에서 자주 사용됩니다.

interface IObserver
{
    void Update(string message);
}

class Subject
{
    private List<IObserver> _observers = new List<IObserver>();

    public void Attach(IObserver observer) => _observers.Add(observer);
    public void Detach(IObserver observer) => _observers.Remove(observer);

    public void Notify(string message)
    {
        foreach (var observer in _observers)
        {
            observer.Update(message);
        }
    }
}

class ConcreteObserver : IObserver
{
    private string _name;
    public ConcreteObserver(string name) => _name = name;

    public void Update(string message) => Console.WriteLine($"{_name}이(가) 메시지를 받았습니다: {message}");
}

사용 예시

class Program
{
    static void Main(string[] args)
    {
        Subject subject = new Subject();
        IObserver observer1 = new ConcreteObserver("Observer1");
        IObserver observer2 = new ConcreteObserver("Observer2");

        subject.Attach(observer1);
        subject.Attach(observer2);
        subject.Notify("이벤트 발생!");
    }
}

출력

Observer1이(가) 메시지를 받았습니다: 이벤트 발생!
Observer2이(가) 메시지를 받았습니다: 이벤트 발생!

4.2 전략 패턴 (Strategy Pattern)

전략 패턴동일한 작업을 여러 방식으로 처리할 수 있도록 알고리즘을 선택할 수 있게 해줍니다.

interface IStrategy
{
    void Execute();
}

class ConcreteStrategyA : IStrategy
{
    public void Execute() => Console.WriteLine("전략 A 실행");
}

class ConcreteStrategyB : IStrategy
{
    public void Execute() => Console.WriteLine("전략 B 실행");
}

class Context
{
    private IStrategy _strategy;
    public void SetStrategy(IStrategy strategy) => _strategy = strategy;

    public void ExecuteStrategy() => _strategy.Execute();
}

사용 예시

class Program
{
    static void Main(string[] args)
    {
        Context context = new Context();
        context.SetStrategy(new ConcreteStrategyA());
        context.ExecuteStrategy(); // 전략 A 실행

        context.SetStrategy(new ConcreteStrategyB());
        context.ExecuteStrategy(); // 전략 B 실행
    }
}

5. 요약

  • 싱글톤 패턴: 단일 인스턴스를 유지하여 전역에서 사용
  • 팩토리 패턴: 객체 생성 로직을 팩토리 메서드로 분리하여 유연하게 생성
  • 어댑터 패턴: 호환되지 않는 인터페이스를 연결
  • 데코레이터 패턴: 기존 객체에 새로운 기능을 추가
  • 옵저버 패턴: 상태 변화를 자동으로 감지하여 알림
  • 전략 패턴: 여러 알고리즘을 유연하게 선택하여 사용

디자인 패턴을 사용하면 코드의 재사용성과 유연성이 크게 향상됩니다. 각 패턴을 실습해보며 다양한 상황에서 어떻게 적용할 수 있는지 익혀보세요!

728x90