소프트웨어 개발에서 디자인 패턴은 반복적으로 발생하는 문제를 해결하기 위한 표준화된 솔루션입니다. 이러한 패턴은 코드의 효율성을 높이고, 가독성을 향상시키며, 유지보수를 용이하게 합니다. 본 글에서는 디자인 패턴의 개요와 함께 행동 패턴 중 전략, 옵저버, 상태 패턴에 대해 설명하고, 각 패턴의 C# 구현 예제를 제공합니다.
디자인 패턴 개요
디자인 패턴은 세 가지 주요 카테고리로 나눌 수 있습니다:
- 생성 패턴 (Creational Patterns): 객체 생성 프로세스를 다루며, 객체를 생성하는 방법을 모호하게 하여 시스템의 유연성을 높입니다.
- 구조 패턴 (Structural Patterns): 클래스와 객체를 조합하여 더 크고 복잡한 구조를 형성합니다.
- 행동 패턴 (Behavioral Patterns): 객체 간의 통신을 다루며, 알고리즘과 책임을 정의합니다.
행동 패턴
행동 패턴은 객체 간의 상호작용을 정의하며, 어떻게 상호작용하는지, 각 객체의 책임이 어떻게 분배되는지를 보여줍니다. 이번 섹션에서는 다음 세 가지 행동 패턴을 다룹니다:
- 전략 패턴 (Strategy Pattern)
- 옵저버 패턴 (Observer Pattern)
- 상태 패턴 (State Pattern)
전략 패턴 (Strategy Pattern)
전략 패턴은 알고리즘 군을 정의하고 캡슐화하여 각 알고리즘을 쉽게 교환할 수 있게 해줍니다. 클라이언트는 런타임에 알고리즘을 선택할 수 있는 유연성을 부여받습니다. 이를 통해 클라이언트와 알고리즘 간의 결합도를 낮출 수 있습니다.
구현 예제
using System;
namespace StrategyPatternExample
{
// 전략 인터페이스
public interface IStrategy
{
void Execute();
}
// Concrete Strategy A
public class ConcreteStrategyA : IStrategy
{
public void Execute()
{
Console.WriteLine("전략 A 실행");
}
}
// Concrete Strategy B
public class ConcreteStrategyB : IStrategy
{
public void Execute()
{
Console.WriteLine("전략 B 실행");
}
}
// Context
public class Context
{
private IStrategy _strategy;
public Context(IStrategy strategy)
{
_strategy = strategy;
}
public void SetStrategy(IStrategy strategy)
{
_strategy = strategy;
}
public void ExecuteStrategy()
{
_strategy.Execute();
}
}
// 클라이언트 코드
class Program
{
static void Main(string[] args)
{
Context context = new Context(new ConcreteStrategyA());
context.ExecuteStrategy();
context.SetStrategy(new ConcreteStrategyB());
context.ExecuteStrategy();
}
}
}
위 예제에서 IStrategy
인터페이스를 통해 두 개의 전략(ConcreteStrategyA 및 ConcreteStrategyB)을 정의하고, Context 클래스에서 클라이언트가 원하는 전략을 선택할 수 있습니다.
옵저버 패턴 (Observer Pattern)
옵저버 패턴은 객체의 상태 변화가 다른 객체에 통지되어 자동으로 업데이트되는 일대다 의존성을 정의합니다. 주제를 관찰하는 옵저버가 주제의 상태가 변화할 때마다 알림을 받습니다.
구현 예제
using System;
using System.Collections.Generic;
namespace ObserverPatternExample
{
// 주제 인터페이스
public interface ISubject
{
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify();
}
// 옵저버 인터페이스
public interface IObserver
{
void Update(string message);
}
// Concrete Subject
public class Subject : ISubject
{
private List _observers = new List();
private string _state;
public void Attach(IObserver observer)
{
_observers.Add(observer);
}
public void Detach(IObserver observer)
{
_observers.Remove(observer);
}
public void Notify()
{
foreach (var observer in _observers)
{
observer.Update(_state);
}
}
public void SetState(string state)
{
_state = state;
Notify();
}
}
// Concrete Observer
public 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();
ConcreteObserver observer1 = new ConcreteObserver("옵저버 1");
ConcreteObserver observer2 = new ConcreteObserver("옵저버 2");
subject.Attach(observer1);
subject.Attach(observer2);
subject.SetState("상태가 변경되었습니다.");
}
}
}
위 예제에서 ISubject
와 IObserver
인터페이스를 생성하고, Subject
클래스에서 해당 인터페이스를 구현하였습니다. 옵저버가 등록과 해제를 통해 상태 변화를 감지합니다.
상태 패턴 (State Pattern)
상태 패턴은 객체의 상태를 표현하는 개별 클래스를 만들고, 객체의 상태에 따라 서로 다른 행동을 구현하여 객체의 행동을 변경합니다. 상태 전환이 필요할 경우, 상태 객체를 변경하여 동작합니다.
구현 예제
using System;
namespace StatePatternExample
{
// 상태 인터페이스
public interface IState
{
void Handle(Context context);
}
// Concrete State A
public class ConcreteStateA : IState
{
public void Handle(Context context)
{
Console.WriteLine("상태 A에서 처리 중..");
context.SetState(new ConcreteStateB());
}
}
// Concrete State B
public class ConcreteStateB : IState
{
public void Handle(Context context)
{
Console.WriteLine("상태 B에서 처리 중..");
context.SetState(new ConcreteStateA());
}
}
// Context
public class Context
{
private IState _state;
public Context(IState state)
{
SetState(state);
}
public void SetState(IState state)
{
_state = state;
}
public void Request()
{
_state.Handle(this);
}
}
// 클라이언트 코드
class Program
{
static void Main(string[] args)
{
Context context = new Context(new ConcreteStateA());
context.Request();
context.Request();
}
}
}
위 예제에서 IState
인터페이스와 ConcreteStateA
, ConcreteStateB
클래스에서 각 상태에 대한 행동을 정의했습니다. Context
클래스는 현재 상태에 대한 참조를 유지하고 상태 전환을 관리합니다.
결론
디자인 패턴은 소프트웨어 개발에서 일반적인 문제를 해결하기 위한 유용한 도구입니다. 본 글에서 살펴본 전략 패턴, 옵저버 패턴, 상태 패턴은 행동 패턴에 속하며 각 패턴은 코드의 유연성과 재사용성을 높이는 데 기여합니다. C#에서 이러한 패턴을 구현함으로써 더 나은 소프트웨어 아키텍처를 구축할 수 있습니다.