C# Design Patterns


디자인 패턴 소개 (Introduction to Design Patterns)

디자인 패턴(Design Patterns)은 소프트웨어 개발에서 자주 발생하는 문제를 해결하기 위한 표준화된 솔루션입니다. 이러한 패턴은 코드의 재사용성을 높이고, 유지보수를 용이하게 하며, 소프트웨어 구조를 개선하는 데 도움을 줍니다. 디자인 패턴은 객체 지향 프로그래밍의 원칙을 따르며, 공통적인 문제를 해결하는 데 필요한 구현을 제공하는 템플릿입니다.

디자인 패턴의 분류 (Classification of Design Patterns)

디자인 패턴은 주로 세 가지 범주로 분류됩니다:

  1. 생성 패턴 (Creational Patterns): 객체 생성과 관련된 패턴으로, 객체의 생성 방식을 추상화하여 객체 생성의 복잡성을 줄입니다.
  2. 구조 패턴 (Structural Patterns): 클래스나 객체의 구성 방식과 관계를 정의하여 복잡한 구조를 단순화합니다.
  3. 행위 패턴 (Behavioral Patterns): 객체 간의 상호작용과 책임 분배를 정의하여 유연한 협력과 분산 처리를 가능하게 합니다.

생성 패턴 (Creational Patterns)

싱글턴 패턴 (Singleton Pattern)

싱글턴 패턴은 클래스의 인스턴스가 오직 하나만 존재하도록 보장하는 패턴입니다. 이 패턴은 전역적인 접근점을 제공하며, 애플리케이션 전역에서 공유되는 리소스나 설정을 관리하는 데 유용합니다.

예제: 싱글턴 패턴 구현 (Implementing Singleton Pattern)
public 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 DoSomething()
    {
        Console.WriteLine("Singleton instance is doing something.");
    }
}

이 예제에서는 Singleton 클래스의 인스턴스가 하나만 생성되도록 보장하며, 멀티스레딩 환경에서도 안전합니다.

팩토리 메서드 패턴 (Factory Method Pattern)

팩토리 메서드 패턴은 객체 생성의 책임을 서브클래스에 위임하여, 객체 생성의 구체적인 클래스를 클라이언트 코드에서 분리하는 패턴입니다.

예제: 팩토리 메서드 패턴 구현 (Implementing Factory Method Pattern)
public abstract class Product
{
    public abstract string Operation();
}

public class ConcreteProductA : Product
{
    public override string Operation()
    {
        return "Result of ConcreteProductA";
    }
}

public class ConcreteProductB : Product
{
    public override string Operation()
    {
        return "Result of ConcreteProductB";
    }
}

public abstract class Creator
{
    public abstract Product FactoryMethod();
}

public class ConcreteCreatorA : Creator
{
    public override Product FactoryMethod()
    {
        return new ConcreteProductA();
    }
}

public class ConcreteCreatorB : Creator
{
    public override Product FactoryMethod()
    {
        return new ConcreteProductB();
    }
}

이 예제에서 Creator 클래스는 팩토리 메서드를 정의하고, ConcreteCreatorAConcreteCreatorB는 구체적인 제품을 생성하는 역할을 합니다.

구조 패턴 (Structural Patterns)

어댑터 패턴 (Adapter Pattern)

어댑터 패턴은 호환되지 않는 인터페이스를 가진 클래스를 함께 작동할 수 있도록 변환하는 패턴입니다. 이를 통해 기존 클래스를 수정하지 않고도 새로운 인터페이스와 호환되게 할 수 있습니다.

예제: 어댑터 패턴 구현 (Implementing Adapter Pattern)
public interface ITarget
{
    string Request();
}

public class Adaptee
{
    public string SpecificRequest()
    {
        return "Specific request.";
    }
}

public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }

    public string Request()
    {
        return _adaptee.SpecificRequest();
    }
}

이 예제에서 Adapter 클래스는 Adaptee의 인터페이스를 ITarget 인터페이스에 맞게 변환합니다.

데코레이터 패턴 (Decorator Pattern)

데코레이터 패턴은 객체에 추가적인 기능을 동적으로 추가할 수 있게 해주는 패턴입니다. 이를 통해 객체의 기능을 확장하면서도 기존 코드를 변경하지 않습니다.

예제: 데코레이터 패턴 구현 (Implementing Decorator Pattern)
public abstract class Coffee
{
    public abstract string GetDescription();
    public abstract double Cost();
}

public class SimpleCoffee : Coffee
{
    public override string GetDescription()
    {
        return "Simple coffee";
    }

    public override double Cost()
    {
        return 5.0;
    }
}

public abstract class CoffeeDecorator : Coffee
{
    protected Coffee _coffee;

    protected CoffeeDecorator(Coffee coffee)
    {
        _coffee = coffee;
    }
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(Coffee coffee) : base(coffee) { }

    public override string GetDescription()
    {
        return _coffee.GetDescription() + ", milk";
    }

    public override double Cost()
    {
        return _coffee.Cost() + 1.0;
    }
}

이 예제에서는 CoffeeDecorator를 상속하여 커피에 우유를 추가하는 MilkDecorator를 구현하고 있습니다.

행위 패턴 (Behavioral Patterns)

옵저버 패턴 (Observer Pattern)

옵저버 패턴은 객체의 상태 변화가 있을 때, 그 객체에 의존하는 다른 객체들에게 자동으로 통지하는 패턴입니다. 이는 주로 이벤트 처리나 상태 변화의 알림을 구현할 때 사용됩니다.

예제: 옵저버 패턴 구현 (Implementing Observer Pattern)
using System;
using System.Collections.Generic;

public interface IObserver
{
    void Update(string message);
}

public class Subject
{
    private readonly 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);
        }
    }
}

public class ConcreteObserver : IObserver
{
    private readonly string _name;

    public ConcreteObserver(string name)
    {
        _name = name;
    }

    public void Update(string message)
    {
        Console.WriteLine($"{_name} received: {message}");
    }
}

이 예제에서는 Subject 클래스가 상태 변화 시 IObserver 객체들에게 알림을 보내는 구조를 가지고 있습니다.

전략 패턴 (Strategy Pattern)

전략 패턴은 알고리즘을 캡슐화하여 교환할 수 있게 해주는 패턴입니다. 이 패턴을 사용하면 클라이언트 코드에서 알고리즘을 변경하지 않고도 알고리즘을 변경할 수 있습니다.

예제: 전략 패턴 구현 (Implementing Strategy Pattern)
public interface IStrategy
{
    int Execute(int a, int b);
}

public class AddStrategy : IStrategy
{
    public int Execute(int a, int b)
    {
        return a + b;
    }
}

public class SubtractStrategy : IStrategy
{
    public int Execute(int a, int b)
    {
        return a - b;
    }
}

public class Context
{
    private readonly IStrategy _strategy;

    public Context(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public int ExecuteStrategy(int a, int b)
    {
        return _strategy.Execute(a, b);
    }
}

이 예제에서는 Context 클래스가 다양한 전략을 사용하여 연산을 수행할 수 있습니다.

결론 (Conclusion)

디자인 패턴은 소프트웨어 개발에서 자주 발생하는 문제를 해결하기 위한 검증된 솔루션을 제공합니다. 디자인 패턴을 활용하면 코드의 유지보수성을 높이고, 코드의 구조를 개선할 수 있습니다. 각 패턴은 특정 문제를 해결하기 위한 방법을 제시하며, 개발자는 이러한 패턴을 적절히 사용하여 유연하고 확장 가능한 소프트웨어를 개발할 수 있습니다. 디자인 패턴을 이해하고 적용함으로써 더 나은 소프트웨어 설계와 구현을 할 수 있습니다.


Posted in C#

Leave a Reply

Your email address will not be published. Required fields are marked *