기본 콘텐츠로 건너뛰기

디자인 패턴 총정리: GoF 23 + 실무 확장 5가지


 


디자인 패턴은 특정 맥락의 문제를 체계적으로 해결하기 위한 ‘재사용 가능한 솔루션’입니다.

  • 목표: 코드의 재사용성유지보수성확장성을 높이는 것
  • 종류: 크게 생성(Creational), 구조(Structural), 행위(Behavioral)로 나누며, 23개 GoF 패턴 이외에도 실무 환경에서 자주 쓰이는 여러 패턴이 있습니다.

본 글에서는 다음과 같이 28개 패턴을 소개합니다.

  1. 생성(Creational) 패턴
    1. Abstract Factory
    2. Builder
    3. Factory Method
    4. Prototype
    5. Singleton
    6. Object Pool (추가)
  2. 구조(Structural) 패턴
    7. Adapter
    8. Bridge
    9. Composite
    10. Decorator
    11. Facade
    12. Flyweight
    13. Proxy
  3. 행위(Behavioral) 패턴
    14. Chain of Responsibility
    15. Command
    16. Interpreter
    17. Iterator
    18. Mediator
    19. Memento
    20. Null Object (추가)
    21. Observer
    22. State
    23. Strategy
    24. Template Method
    25. Visitor
  4. 추가적인 패턴
    26. Model-View-Controller (MVC)
    27. Repository
    28. Dependency Injection

1. 생성(Creational) 패턴

1-1. Abstract Factory

개요

  • 의도: 관련된 객체들의 ‘가족’을 생성하기 위해, 구체적인 클래스를 지정하지 않고도 객체를 생성하는 인터페이스 제공
  • 특징: 여러 종류의 제품을 한 번에 만들어야 할 때, 제품군별로 호환성을 유지하면서 객체 생성 로직을 추상화

C# 예시 (간단 버전)

// 추상 제품
public interface IButton { void Render(); }
public interface ITextBox { void Render(); }

// 구체 제품: Win 스타일
public class WinButton : IButton { public void Render() => Console.WriteLine("윈도우 버튼"); }
public class WinTextBox : ITextBox { public void Render() => Console.WriteLine("윈도우 텍스트박스"); }

// 구체 제품: Mac 스타일
public class MacButton : IButton { public void Render() => Console.WriteLine("맥 버튼"); }
public class MacTextBox : ITextBox { public void Render() => Console.WriteLine("맥 텍스트박스"); }

// 추상 팩토리
public interface IGUIFactory
{
    IButton CreateButton();
    ITextBox CreateTextBox();
}

// 구체 팩토리
public class WinFactory : IGUIFactory
{
    public IButton CreateButton() => new WinButton();
    public ITextBox CreateTextBox() => new WinTextBox();
}

public class MacFactory : IGUIFactory
{
    public IButton CreateButton() => new MacButton();
    public ITextBox CreateTextBox() => new MacTextBox();
}

// 사용 예시
class Program
{
    static void Main()
    {
        IGUIFactory factory = new WinFactory(); // 또는 new MacFactory();
        IButton button = factory.CreateButton();
        ITextBox textBox = factory.CreateTextBox();

        button.Render();
        textBox.Render();
    }
}

1-2. Builder

개요

  • 의도: 복합 객체(예: 복잡한 생성 과정이 필요한 객체)를 단계별로 조립할 때, 구현 방법을 분리하고 유연성을 제공
  • 특징: ‘조립 과정을 동일’하게 유지하면서, ‘각 단계별 구현체’를 바꿔 다른 결과물을 만들 수 있음

C# 예시 (간단 버전)

public class Computer
{
    public string CPU { get; set; }
    public string GPU { get; set; }
    public string RAM { get; set; }
}

public interface IComputerBuilder
{
    void SetCPU();
    void SetGPU();
    void SetRAM();
    Computer GetResult();
}

public class GamingComputerBuilder : IComputerBuilder
{
    private Computer _computer = new Computer();
    public void SetCPU() => _computer.CPU = "High-end CPU";
    public void SetGPU() => _computer.GPU = "High-end GPU";
    public void SetRAM() => _computer.RAM = "16GB";
    public Computer GetResult() => _computer;
}

public class Director
{
    public void Construct(IComputerBuilder builder)
    {
        builder.SetCPU();
        builder.SetGPU();
        builder.SetRAM();
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        Director director = new Director();
        IComputerBuilder builder = new GamingComputerBuilder();

        director.Construct(builder);
        Computer gamingPC = builder.GetResult();
        Console.WriteLine($"{gamingPC.CPU}, {gamingPC.GPU}, {gamingPC.RAM}");
    }
}

1-3. Factory Method

개요

  • 의도: 객체 생성을 서브클래스(또는 별도 팩토리)에서 담당하도록 위임하여, ‘어떤’ 객체가 생성되는지에 대한 의존성을 줄임
  • 특징: 객체를 생성할 때 구체 클래스를 직접 지정하지 않고, 팩토리 메서드를 통해 인스턴스를 반환

C# 예시 (간단 버전)

public abstract class Creator
{
    // 팩토리 메서드
    public abstract IProduct FactoryMethod();

    public string SomeOperation()
    {
        IProduct product = FactoryMethod();
        return product.GetName();
    }
}

public interface IProduct { string GetName(); }

public class ConcreteProductA : IProduct { public string GetName() => "Product A"; }
public class ConcreteProductB : IProduct { public string GetName() => "Product B"; }

public class ConcreteCreatorA : Creator
{
    public override IProduct FactoryMethod() => new ConcreteProductA();
}

public class ConcreteCreatorB : Creator
{
    public override IProduct FactoryMethod() => new ConcreteProductB();
}

// 사용 예시
class Program
{
    static void Main()
    {
        Creator creatorA = new ConcreteCreatorA();
        Console.WriteLine(creatorA.SomeOperation());

        Creator creatorB = new ConcreteCreatorB();
        Console.WriteLine(creatorB.SomeOperation());
    }
}

1-4. Prototype

개요

  • 의도: 기존 객체를 복제(clone)해서 새로운 객체를 생성
  • 특징: 복제가 반복적으로 필요한 경우, new로 생성하는 비용을 줄이거나 특정 상태를 그대로 복제할 수 있음

C# 예시 (간단 버전)

public abstract class Shape
{
    public int X { get; set; }
    public int Y { get; set; }
    public abstract Shape Clone();
}

public class Circle : Shape
{
    public int Radius { get; set; }

    public Circle(int x, int y, int r)
    {
        X = x; Y = y; Radius = r;
    }

    // 프로토타입 복제 메서드
    public override Shape Clone()
    {
        return new Circle(X, Y, Radius);
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        Circle circle1 = new Circle(10, 20, 5);
        Circle circle2 = (Circle)circle1.Clone();

        Console.WriteLine($"{circle2.X}, {circle2.Y}, {circle2.Radius}"); // 10, 20, 5
    }
}

1-5. Singleton

개요

  • 의도: 클래스의 인스턴스가 오직 하나만 존재하도록 제한하고, 어디서든 전역 접근을 가능케 함
  • 특징: 주로 설정, 로거, 캐시 등 전역적으로 하나만 있으면 충분한 자원 관리용으로 자주 사용

C# 예시 (간단 버전)

public sealed class Logger
{
    private static readonly Logger _instance = new Logger();
    private Logger() { }

    public static Logger Instance => _instance;

    public void Log(string message) => Console.WriteLine($"[Log] {message}");
}

// 사용 예시
class Program
{
    static void Main()
    {
        Logger.Instance.Log("싱글턴 테스트");
    }
}

1-6. Object Pool (추가)

개요

  • 의도: 객체 생성 비용이 큰 경우, 미리 일정 수의 객체를 만들어 풀(Pool)에 저장해두고 필요 시 꺼내 쓰는 패턴
  • 특징: 사용 후 반환하면 재활용하므로, 빈번한 생성/소멸 비용을 줄일 수 있음

C# 예시 (간단 버전)

public class Connection
{
    public int Id { get; set; }
    public void Connect() => Console.WriteLine($"Connection {Id} 연결");
    public void Disconnect() => Console.WriteLine($"Connection {Id} 해제");
}

public class ConnectionPool
{
    private Queue<Connection> _available = new Queue<Connection>();

    public ConnectionPool(int size)
    {
        for (int i = 0; i < size; i++)
        {
            _available.Enqueue(new Connection { Id = i });
        }
    }

    public Connection Acquire()
    {
        if (_available.Count > 0)
            return _available.Dequeue();
        throw new Exception("풀에 더 이상 Connection이 없습니다.");
    }

    public void Release(Connection conn)
    {
        conn.Disconnect();
        _available.Enqueue(conn);
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        ConnectionPool pool = new ConnectionPool(2);
        var conn1 = pool.Acquire();
        conn1.Connect();

        var conn2 = pool.Acquire();
        conn2.Connect();

        pool.Release(conn1); // 다시 풀에 반환
        var conn3 = pool.Acquire(); // conn1 재활용
        conn3.Connect();
    }
}

2. 구조(Structural) 패턴

2-1. Adapter

개요

  • 의도: 호환되지 않는 인터페이스를 다른 인터페이스로 ‘변환’하여, 기존 코드를 수정하지 않고 재사용
  • 특징: 클래스나 객체의 인터페이스를 ‘중간에 맞춰주는 어댑터’를 통해 연결

C# 예시 (간단 버전)

// 기존에 사용하던 인터페이스
public interface ITarget
{
    void Request();
}

// 새로 도입된, 호환되지 않는 클래스
public class Adaptee
{
    public void SpecificRequest() => Console.WriteLine("Adaptee 실행");
}

// 어댑터
public class Adapter : ITarget
{
    private readonly Adaptee _adaptee = new Adaptee();
    public void Request() => _adaptee.SpecificRequest();
}

// 사용 예시
class Program
{
    static void Main()
    {
        ITarget target = new Adapter();
        target.Request();
    }
}

2-2. Bridge

개요

  • 의도: 구현부와 추상부를 분리하여, 둘이 독립적으로 확장 가능하게 하는 패턴
  • 특징: 계층(추상화)과 구현부를 별도의 클래스로 두고, 런타임에 상호 조합을 통해 기능 확장

C# 예시 (간단 버전)

// 구현부 인터페이스
public interface IRenderer
{
    void RenderCircle(float radius);
}

// 구체 구현
public class VectorRenderer : IRenderer
{
    public void RenderCircle(float radius) => Console.WriteLine($"벡터로 원 그리기: 반지름 {radius}");
}

public class RasterRenderer : IRenderer
{
    public void RenderCircle(float radius) => Console.WriteLine($"래스터로 원 그리기: 반지름 {radius}");
}

// 추상부
public abstract class Shape
{
    protected IRenderer renderer;
    protected Shape(IRenderer renderer) { this.renderer = renderer; }
    public abstract void Draw();
}

public class Circle : Shape
{
    private float _radius;
    public Circle(IRenderer renderer, float radius) : base(renderer)
    {
        _radius = radius;
    }
    public override void Draw() => renderer.RenderCircle(_radius);
}

// 사용 예시
class Program
{
    static void Main()
    {
        Shape circle1 = new Circle(new VectorRenderer(), 5);
        circle1.Draw();

        Shape circle2 = new Circle(new RasterRenderer(), 10);
        circle2.Draw();
    }
}

2-3. Composite

개요

  • 의도: 트리 구조로 객체를 구성하여, 단일 객체와 복합 객체를 동일하게 다루도록 함
  • 특징: ‘부분-전체(Part-Whole)’ 계층을 표현하고, 클라이언트 입장에서 일관된 인터페이스를 제공

C# 예시 (간단 버전)

public abstract class Component
{
    protected string name;
    public Component(string name) { this.name = name; }
    public abstract void Display(int depth);
}

public class Leaf : Component
{
    public Leaf(string name) : base(name) {}
    public override void Display(int depth) => 
        Console.WriteLine(new string('-', depth) + name);
}

public class CompositeNode : Component
{
    private List<Component> _children = new List<Component>();

    public CompositeNode(string name) : base(name) {}

    public void Add(Component c) => _children.Add(c);
    public void Remove(Component c) => _children.Remove(c);

    public override void Display(int depth)
    {
        Console.WriteLine(new string('-', depth) + name);
        foreach (var child in _children)
        {
            child.Display(depth + 2);
        }
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        CompositeNode root = new CompositeNode("Root");
        root.Add(new Leaf("Leaf A"));
        root.Add(new Leaf("Leaf B"));

        CompositeNode sub = new CompositeNode("Sub");
        sub.Add(new Leaf("Leaf X"));
        sub.Add(new Leaf("Leaf Y"));
        root.Add(sub);

        root.Display(1);
    }
}

2-4. Decorator

개요

  • 의도: 기존 객체에 동적으로 기능을 덧씌우는(확장) 패턴
  • 특징: ‘상속’ 대신 ‘포함(Composition)’을 통해 확장하여, 여러 데코레이터를 조합하여 사용할 수 있음

C# 예시 (간단 버전)

public interface ICoffee
{
    string GetDescription();
    int GetCost();
}

public class BasicCoffee : ICoffee
{
    public string GetDescription() => "기본 커피";
    public int GetCost() => 2000;
}

// 데코레이터 기본
public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual int GetCost() => _coffee.GetCost();
}

// 구체 데코레이터
public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) {}
    public override string GetDescription() => base.GetDescription() + ", 우유";
    public override int GetCost() => base.GetCost() + 500;
}

public class SyrupDecorator : CoffeeDecorator
{
    public SyrupDecorator(ICoffee coffee) : base(coffee) {}
    public override string GetDescription() => base.GetDescription() + ", 시럽";
    public override int GetCost() => base.GetCost() + 300;
}

// 사용 예시
class Program
{
    static void Main()
    {
        ICoffee coffee = new BasicCoffee();
        coffee = new MilkDecorator(coffee);
        coffee = new SyrupDecorator(coffee);

        Console.WriteLine(coffee.GetDescription()); // "기본 커피, 우유, 시럽"
        Console.WriteLine(coffee.GetCost());        // 2800
    }
}

2-5. Facade

개요

  • 의도: 복잡한 서브시스템들을 단순화된 인터페이스(파사드)로 감싸서 쉽게 사용하도록 함
  • 특징: 여러 클래스로 구성된 복잡한 로직을 한 곳에서 간략히 제공

C# 예시 (간단 버전)

public class SubSystemA
{
    public void DoA() => Console.WriteLine("서브시스템 A 실행");
}
public class SubSystemB
{
    public void DoB() => Console.WriteLine("서브시스템 B 실행");
}
public class SubSystemC
{
    public void DoC() => Console.WriteLine("서브시스템 C 실행");
}

// 파사드
public class Facade
{
    private SubSystemA a = new SubSystemA();
    private SubSystemB b = new SubSystemB();
    private SubSystemC c = new SubSystemC();

    public void Operation()
    {
        a.DoA();
        b.DoB();
        c.DoC();
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        Facade facade = new Facade();
        facade.Operation();
    }
}

2-6. Flyweight

개요

  • 의도: 많은 수의 객체가 필요할 때, 공통 상태를 공유하여 메모리 사용량을 줄이는 패턴
  • 특징: 내부 상태(공유)와 외부 상태(독립)를 구분, 내부 상태를 재사용하여 자원 절약

C# 예시 (간단 버전)

public class Flyweight
{
    public string IntrinsicState { get; private set; }
    public Flyweight(string intrinsicState) => IntrinsicState = intrinsicState;
    public void Operation(string extrinsicState)
    {
        Console.WriteLine($"Intrinsic: {IntrinsicState}, Extrinsic: {extrinsicState}");
    }
}

public class FlyweightFactory
{
    private Dictionary<string, Flyweight> _flyweights = new Dictionary<string, Flyweight>();
    public Flyweight GetFlyweight(string key)
    {
        if (!_flyweights.ContainsKey(key))
        {
            _flyweights[key] = new Flyweight(key);
        }
        return _flyweights[key];
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        FlyweightFactory factory = new FlyweightFactory();

        Flyweight fw1 = factory.GetFlyweight("A");
        fw1.Operation("X");

        Flyweight fw2 = factory.GetFlyweight("A");
        fw2.Operation("Y");

        // fw1과 fw2는 IntrinsicState가 "A"로 동일 객체(혹은 공유)
    }
}

2-7. Proxy

개요

  • 의도: 실제 객체에 대한 대리인을 두어, 접근 제어나 지연 로딩, 로깅 등 추가 작업을 수행
  • 특징: 클라이언트는 프록시를 통해 실제 객체에 접근하며, 외부적으로는 동일한 인터페이스

C# 예시 (간단 버전)

public interface IService
{
    void Operation();
}

public class RealService : IService
{
    public void Operation() => Console.WriteLine("실제 서비스 동작");
}

public class ServiceProxy : IService
{
    private RealService _realService;
    public void Operation()
    {
        Console.WriteLine("프록시에서 접근 제어/로깅 가능");
        if (_realService == null) _realService = new RealService();
        _realService.Operation();
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        IService proxy = new ServiceProxy();
        proxy.Operation(); // 내부에서 실제 서비스를 생성 후 실행
    }
}

3. 행위(Behavioral) 패턴

3-1. Chain of Responsibility

개요

  • 의도: 요청을 보내면, 해당 요청을 처리할 수 있는 ‘체인(연쇄)’을 따라 순서대로 처리 기회를 부여
  • 특징: 각 객체는 요청을 처리하거나, 다음 객체로 넘길 수 있음 (분산된 책임)

C# 예시 (간단 버전)

public abstract class Handler
{
    protected Handler _next;
    public Handler SetNext(Handler next)
    {
        _next = next;
        return next;
    }
    public abstract void HandleRequest(int request);
}

public class ConcreteHandlerA : Handler
{
    public override void HandleRequest(int request)
    {
        if (request < 10)
            Console.WriteLine($"Handler A 처리: {request}");
        else
            _next?.HandleRequest(request);
    }
}

public class ConcreteHandlerB : Handler
{
    public override void HandleRequest(int request)
    {
        if (request < 20)
            Console.WriteLine($"Handler B 처리: {request}");
        else
            _next?.HandleRequest(request);
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        Handler h1 = new ConcreteHandlerA();
        Handler h2 = new ConcreteHandlerB();
        h1.SetNext(h2);

        h1.HandleRequest(5);  // A가 처리
        h1.HandleRequest(15); // B가 처리
        h1.HandleRequest(25); // 아무도 처리 안 함
    }
}

3-2. Command

개요

  • 의도: 요청을 객체(커맨드)로 캡슐화하여, 실행/취소(Undo)/재실행 등을 유연하게 관리
  • 특징: 수행할 동작(Receiver)과 실행 로직(Invoker)을 명령 객체로 분리, 큐나 스택에 보관 가능

C# 예시 (간단 버전)

public interface ICommand
{
    void Execute();
    void Undo();
}

// Receiver
public class Light
{
    public void On() => Console.WriteLine("전등 켜짐");
    public void Off() => Console.WriteLine("전등 꺼짐");
}

// Concrete Command
public class LightOnCommand : ICommand
{
    private Light _light;
    public LightOnCommand(Light light) => _light = light;
    public void Execute() => _light.On();
    public void Undo() => _light.Off();
}

// Invoker
public class RemoteControl
{
    private ICommand _command;
    public void SetCommand(ICommand command) => _command = command;
    public void PressButton() => _command?.Execute();
    public void PressUndo() => _command?.Undo();
}

// 사용 예시
class Program
{
    static void Main()
    {
        Light light = new Light();
        ICommand onCommand = new LightOnCommand(light);

        RemoteControl remote = new RemoteControl();
        remote.SetCommand(onCommand);
        remote.PressButton(); // 전등 켜짐
        remote.PressUndo();   // 전등 꺼짐
    }
}

3-3. Interpreter

개요

  • 의도: 간단한 언어(또는 문법)에서 문장을 해석(Interpreter)하기 위한 패턴
  • 특징: 문법 규칙을 클래스로 정의하고, 해당 규칙에 따라 해석 로직을 구성

C# 예시 (단순 버전)

public interface IExpression
{
    int Interpret(Dictionary<string, int> context);
}

public class NumberExpression : IExpression
{
    private string _name;
    public NumberExpression(string name) => _name = name;
    public int Interpret(Dictionary<string, int> context) 
        => context[_name];
}

public class PlusExpression : IExpression
{
    private IExpression _left, _right;
    public PlusExpression(IExpression left, IExpression right)
    {
        _left = left; _right = right;
    }
    public int Interpret(Dictionary<string, int> context)
        => _left.Interpret(context) + _right.Interpret(context);
}

// 사용 예시
class Program
{
    static void Main()
    {
        // 표현식: x + y
        IExpression expression = new PlusExpression(
            new NumberExpression("x"),
            new NumberExpression("y")
        );

        Dictionary<string, int> context = new Dictionary<string, int>
        {
            { "x", 10 },
            { "y", 5 }
        };

        Console.WriteLine(expression.Interpret(context)); // 15
    }
}

3-4. Iterator

개요

  • 의도: 내부 표현을 노출하지 않고 객체 묶음(컬렉션)을 순회(Iterator)하는 방법을 제공
  • 특징: C#의 IEnumerator, IEnumerable이 대표적인 예시

C# 예시 (간단 버전)

public class CustomCollection : IEnumerable<int>
{
    private int[] _items;
    public CustomCollection(int[] items) => _items = items;

    public IEnumerator<int> GetEnumerator()
    {
        for(int i=0; i<_items.Length; i++)
        {
            yield return _items[i];
        }
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

// 사용 예시
class Program
{
    static void Main()
    {
        var collection = new CustomCollection(new[] {1, 2, 3});
        foreach(var item in collection)
        {
            Console.WriteLine(item);
        }
    }
}

3-5. Mediator

개요

  • 의도: 객체들 간 복잡한 상호작용을 ‘중재자’ 객체 하나가 맡아, 결합도를 낮춤
  • 특징: 각 객체는 중재자만 참조하여, 상호 직접 연결을 최소화

C# 예시 (간단 버전)

public abstract class Mediator
{
    public abstract void SendMessage(string message, Colleague colleague);
}

public abstract class Colleague
{
    protected Mediator _mediator;
    public Colleague(Mediator mediator) => _mediator = mediator;
}

public class ConcreteColleagueA : Colleague
{
    public ConcreteColleagueA(Mediator mediator) : base(mediator) {}
    public void Notify(string message) => Console.WriteLine($"A가 받은 메시지: {message}");
    public void Send(string message) => _mediator.SendMessage(message, this);
}

public class ConcreteColleagueB : Colleague
{
    public ConcreteColleagueB(Mediator mediator) : base(mediator) {}
    public void Notify(string message) => Console.WriteLine($"B가 받은 메시지: {message}");
    public void Send(string message) => _mediator.SendMessage(message, this);
}

// 구체 Mediator
public class ConcreteMediator : Mediator
{
    public ConcreteColleagueA ColleagueA { get; set; }
    public ConcreteColleagueB ColleagueB { get; set; }

    public override void SendMessage(string message, Colleague colleague)
    {
        if(colleague == ColleagueA)
            ColleagueB.Notify(message);
        else
            ColleagueA.Notify(message);
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        ConcreteMediator mediator = new ConcreteMediator();
        ConcreteColleagueA a = new ConcreteColleagueA(mediator);
        ConcreteColleagueB b = new ConcreteColleagueB(mediator);

        mediator.ColleagueA = a;
        mediator.ColleagueB = b;

        a.Send("안녕 B?");
        b.Send("안녕 A!");
    }
}

3-6. Memento

개요

  • 의도: 객체의 내부 상태를 캡슐화하여 저장하고, 나중에 해당 상태로 복원할 수 있게 함
  • 특징: 상태 히스토리를 관리하여 undo/redo 기능 등을 구현

C# 예시 (간단 버전)

// Originator
public class Editor
{
    public string Content { get; set; }

    public Memento Save() => new Memento(Content);
    public void Restore(Memento memento) => Content = memento.State;
}

// Memento
public class Memento
{
    public string State { get; }
    public Memento(string state) => State = state;
}

// Caretaker
public class History
{
    private Stack<Memento> _history = new Stack<Memento>();
    public void Push(Memento m) => _history.Push(m);
    public Memento Pop() => _history.Pop();
}

// 사용 예시
class Program
{
    static void Main()
    {
        Editor editor = new Editor();
        History history = new History();

        editor.Content = "버전 1";
        history.Push(editor.Save());

        editor.Content = "버전 2";
        history.Push(editor.Save());

        // 복원
        editor.Restore(history.Pop());
        Console.WriteLine(editor.Content); // "버전 2"
        editor.Restore(history.Pop());
        Console.WriteLine(editor.Content); // "버전 1"
    }
}

3-7. Null Object (추가)

개요

  • 의도: ‘Null’ 참조를 반환하는 대신, 아무 동작도 하지 않는 ‘Null 객체’를 사용하여 조건문을 줄이고 코드 단순화
  • 특징: if (obj != null) 같은 방어코드 없이 호출 가능. Null 대신 ‘빈(Empty)’ 동작을 제공

C# 예시 (간단 버전)

public interface ILogger
{
    void Log(string message);
}

public class ConsoleLogger : ILogger
{
    public void Log(string message) => Console.WriteLine(message);
}

public class NullLogger : ILogger
{
    public void Log(string message) { /* 아무 것도 안 함 */ }
}

class Program
{
    static void Main()
    {
        ILogger logger = new NullLogger(); // 혹은 new ConsoleLogger()
        // logger가 null이 아님 -> 바로 메서드 호출 가능
        logger.Log("이 메시지는 아무 일도 일어나지 않습니다.");
    }
}

3-8. Observer

개요

  • 의도: 어떤 객체(Subject)의 상태가 바뀔 때, 이를 의존하고 있는 객체(Observers)들에게 자동으로 알림
  • 특징: 여러 옵저버가 동시에 Subject를 구독하고, 변경 시 일괄 통지

C# 예시 (간단 버전)

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

public interface ISubject
{
    void Attach(IObserver observer);
    void Detach(IObserver observer);
    void Notify();
}

public class WeatherStation : ISubject
{
    private List<IObserver> _observers = new List<IObserver>();
    private string _weather;
    public string Weather
    {
        get => _weather;
        set { _weather = value; Notify(); }
    }

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

    public void Notify()
    {
        foreach (var obs in _observers)
        {
            obs.Update(_weather);
        }
    }
}

public class DisplayDevice : IObserver
{
    private string _name;
    public DisplayDevice(string name) => _name = name;
    public void Update(string data) => Console.WriteLine($"{_name} 디스플레이: {data}");
}

// 사용 예시
class Program
{
    static void Main()
    {
        WeatherStation station = new WeatherStation();
        DisplayDevice phone = new DisplayDevice("폰");
        DisplayDevice tv = new DisplayDevice("TV");

        station.Attach(phone);
        station.Attach(tv);

        station.Weather = "맑음";
        station.Weather = "비";
    }
}

3-9. State

개요

  • 의도: 객체의 내부 상태에 따라 행동이 달라지도록, 상태를 객체로 분리하여 캡슐화
  • 특징: 상태 전환 로직을 각 상태 클래스 안에 두어, 조건문 없이도 상태 전이가 가능

C# 예시 (간단 버전)

public interface IState
{
    void Handle(Context context);
}

public class Context
{
    public IState State { get; set; }
    public Context(IState state) => State = state;
    public void Request() => State.Handle(this);
}

public class ConcreteStateA : IState
{
    public void Handle(Context context)
    {
        Console.WriteLine("State A 처리");
        context.State = new ConcreteStateB();
    }
}

public class ConcreteStateB : IState
{
    public void Handle(Context context)
    {
        Console.WriteLine("State B 처리");
        context.State = new ConcreteStateA();
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        Context context = new Context(new ConcreteStateA());
        context.Request(); // A -> B
        context.Request(); // B -> A
        context.Request(); // A -> B
    }
}

3-10. Strategy

개요

  • 의도: 알고리즘 군(여러 로직)을 정의하고, 런타임에 선택적으로 바꿔 쓸 수 있게 캡슐화
  • 특징: 조건문 없이도 적절한 알고리즘을 교체 가능. 예: 할인 정책, 정렬 방식 등

C# 예시 (간단 버전)

public interface IDiscountStrategy
{
    decimal GetDiscountedPrice(decimal originalPrice);
}

public class FixedDiscount : IDiscountStrategy
{
    private decimal _discount;
    public FixedDiscount(decimal discount) => _discount = discount;
    public decimal GetDiscountedPrice(decimal originalPrice) => originalPrice - _discount;
}

public class RateDiscount : IDiscountStrategy
{
    private decimal _rate;
    public RateDiscount(decimal rate) => _rate = rate; // 0.2 = 20% 할인
    public decimal GetDiscountedPrice(decimal originalPrice) => originalPrice * (1 - _rate);
}

public class ShoppingCart
{
    private IDiscountStrategy _strategy;
    public ShoppingCart(IDiscountStrategy strategy) => _strategy = strategy;
    public decimal CalculatePrice(decimal price) => _strategy.GetDiscountedPrice(price);
}

// 사용 예시
class Program
{
    static void Main()
    {
        var cart1 = new ShoppingCart(new FixedDiscount(500));
        Console.WriteLine(cart1.CalculatePrice(2000)); // 1500

        var cart2 = new ShoppingCart(new RateDiscount(0.2m));
        Console.WriteLine(cart2.CalculatePrice(2000)); // 1600
    }
}

3-11. Template Method

개요

  • 의도: 알고리즘의 골격(템플릿)을 정의하고, 세부 단계 구현은 서브클래스가 담당
  • 특징: 상위 클래스에서 전체 흐름을 정의, 하위 클래스에서 특정 단계만 오버라이드

C# 예시 (간단 버전)

public abstract class Game
{
    public void Play()
    {
        Initialize();
        StartPlay();
        EndPlay();
    }
    protected abstract void Initialize();
    protected abstract void StartPlay();
    protected abstract void EndPlay();
}

public class Soccer : Game
{
    protected override void Initialize() => Console.WriteLine("축구 게임 준비");
    protected override void StartPlay() => Console.WriteLine("축구 시작");
    protected override void EndPlay() => Console.WriteLine("축구 종료");
}

// 사용 예시
class Program
{
    static void Main()
    {
        Game game = new Soccer();
        game.Play();
    }
}

3-12. Visitor

개요

  • 의도: 객체 구조에 새 연산을 추가할 때, 원래 구조를 변경하지 않고도 확장 가능
  • 특징: 방문자(Visitor)가 요소들의 ‘클래스 종류에 따라’ 다른 동작을 수행

C# 예시 (간단 버전)

public interface IVisitor
{
    void Visit(ElementA element);
    void Visit(ElementB element);
}

public abstract class Element
{
    public abstract void Accept(IVisitor visitor);
}

public class ElementA : Element
{
    public override void Accept(IVisitor visitor) => visitor.Visit(this);
    public string AFeature() => "A 요소 기능";
}

public class ElementB : Element
{
    public override void Accept(IVisitor visitor) => visitor.Visit(this);
    public string BFeature() => "B 요소 기능";
}

public class ConcreteVisitor : IVisitor
{
    public void Visit(ElementA element)
    {
        Console.WriteLine($"방문자, {element.AFeature()} 처리");
    }

    public void Visit(ElementB element)
    {
        Console.WriteLine($"방문자, {element.BFeature()} 처리");
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        List<Element> elements = new List<Element> { new ElementA(), new ElementB() };
        IVisitor visitor = new ConcreteVisitor();

        foreach(var e in elements)
        {
            e.Accept(visitor);
        }
    }
}

4. 추가적인 패턴

위에서 GoF가 정의한 23개 패턴과 함께, 현업에서도 자주 언급되는 패턴 5가지 예시를 살펴봅니다.

4-1. Model-View-Controller (MVC)

개요

  • 의도: UI, 데이터, 로직을 분리하여 유지보수와 확장성을 높이는 아키텍처 패턴
  • 특징:
    • Model: 비즈니스 로직, 데이터 관리
    • View: 사용자 인터페이스
    • Controller: 입력 제어 및 Model-View 연결

C# 예시 (간단 스케치: ASP.NET Core MVC 형태 X, 개념만)

// Model
public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// View (간단하게 콘솔 출력)
public class ProductView
{
    public void ShowProduct(string name, decimal price)
    {
        Console.WriteLine($"상품명: {name}, 가격: {price}");
    }
}

// Controller
public class ProductController
{
    private Product _model;
    private ProductView _view;

    public ProductController(Product model, ProductView view)
    {
        _model = model;
        _view = view;
    }

    public void UpdateView()
    {
        _view.ShowProduct(_model.Name, _model.Price);
    }

    public void SetProductName(string name) => _model.Name = name;
    public void SetProductPrice(decimal price) => _model.Price = price;
}

// 사용 예시
class Program
{
    static void Main()
    {
        Product model = new Product { Name = "커피", Price = 3000 };
        ProductView view = new ProductView();
        ProductController controller = new ProductController(model, view);

        controller.UpdateView();
        controller.SetProductName("라떼");
        controller.SetProductPrice(4000);
        controller.UpdateView();
    }
}

4-2. Repository

개요

  • 의도: 데이터 접근 로직(ORM, DB 쿼리 등)을 캡슐화하여, 도메인 로직과 분리
  • 특징: ‘가짜(InMemory) 구현’과 ‘실 DB 구현’을 쉽게 교체하고, 비즈니스 레이어가 DB와 직접 상호 작용하지 않도록 정리

C# 예시 (간단 버전)

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public interface IProductRepository
{
    Product GetById(int id);
    void Add(Product product);
    // etc...
}

// InMemory Repository 예시
public class InMemoryProductRepository : IProductRepository
{
    private List<Product> _products = new List<Product>();
    public Product GetById(int id) => _products.FirstOrDefault(p => p.Id == id);
    public void Add(Product product) => _products.Add(product);
}

// 사용 예시
class Program
{
    static void Main()
    {
        IProductRepository repo = new InMemoryProductRepository();
        repo.Add(new Product { Id = 1, Name = "커피" });
        Product p = repo.GetById(1);
        Console.WriteLine(p?.Name); // "커피"
    }
}

4-3. Dependency Injection (DI)

개요

  • 의도: 객체 간 의존성을 외부에서 주입(Injection) 받아, 클래스 간 결합도를 낮춤
  • 특징: IoC(Inversion of Control) 컨테이너를 이용하거나, 생성자/속성/메서드 인젝션 방식을 활용

C# 예시 (간단 버전)

public interface IMessageService
{
    void SendMessage(string message);
}

public class EmailService : IMessageService
{
    public void SendMessage(string message) => Console.WriteLine($"이메일 전송: {message}");
}

public class SmsService : IMessageService
{
    public void SendMessage(string message) => Console.WriteLine($"SMS 전송: {message}");
}

public class Notifier
{
    private readonly IMessageService _messageService;
    public Notifier(IMessageService messageService) // 생성자 주입
    {
        _messageService = messageService;
    }
    
    public void Notify(string message)
    {
        _messageService.SendMessage(message);
    }
}

// 사용 예시
class Program
{
    static void Main()
    {
        // 외부에서 어떤 구현을 넣어줄지 결정
        IMessageService emailService = new EmailService();
        Notifier notifier = new Notifier(emailService);
        notifier.Notify("안녕하세요!");

        // 필요에 따라 다른 구현체 주입
        IMessageService smsService = new SmsService();
        Notifier notifier2 = new Notifier(smsService);
        notifier2.Notify("SMS 발송");
    }
}

마무리

위에서 살펴본 28개 패턴(GoF 23 + 추가 5)은 대표적이고도 실무에서 유용하게 쓰이는 사례들입니다.

  • 특정 상황에서 어떤 패턴을 적용해야 할지 판단 기준은 **의도(Intent)**와 **특징(Structure)**이 가장 중요합니다.
  • 코드를 작성하다 보면 자연스럽게 “이 문제를 해결하려면 어떤 구조가 필요하지?” 하고 고민하게 되고, 그 해답의 일부가 바로 디자인 패턴입니다.

정리

  1. 생성 패턴(Creational): 객체 생성 로직을 체계적으로 캡슐화
  2. 구조 패턴(Structural): 클래스나 객체를 조합해 더 큰 구조를 만들 때 유용
  3. 행위 패턴(Behavioral): 객체 간의 상호작용(로직 흐름)에 초점
  4. 추가 패턴: MVC, Repository, DI 등 현대 개발에서 자주 언급

이 글을 통해 디자인 패턴의 전체적인 숲을 조망해 보셨길 바랍니다. 실제로는 각 패턴을 더 깊이 파고들고, 자신의 프로젝트에 맞춰 재해석/응용하는 과정이 중요합니다. 필요에 맞게 유연하게 적용하면서 유지보수성 높은 코드를 작성해보세요!

감사합니다.

댓글

이 블로그의 인기 게시물

Log4Net vs. Serilog 비교

🔍 Log4Net vs. Serilog 간단 비교 기준Log4NetSerilog 성능 중간 (충분하지만, 최신 라이브러리보단 느림) 빠르고 효율적 설정 방식 XML 기반 (전통적) JSON 기반 (모던함) 구조적 로깅 미지원 (기본 텍스트 로그) 강력한 구조적 로깅 지원 ASP.NET Core 통합 가능하지만 설정이 좀 복잡 간단하고 직관적 Sink(대상) 다양성 적당한 편 매우 다양하고 확장성 높음 생태계 & 유지보수 전통적, 유지보수 상태는 다소 정체 활발한 개발과 업데이트 🎯 어떤 프레임워크가 좋을까? Serilog를 추천하는 이유: 최신 기술 : ASP.NET Core와 완벽히 통합, 구조적 로깅이 뛰어나 JSON 로그 처리, 분석에 최적화됨. 높은 생산성 : 설정과 유지보수가 쉬움. 코드 기반 및 JSON 설정으로 직관적이고 빠른 개발이 가능. 확장성 : 파일, 콘솔, DB, Elasticsearch, Seq 등 다양한 Sink를 제공해 향후 확장성이 좋음. 🚀 결론 요약 Log4Net을 추천할 때Serilog를 추천할 때 기존 시스템과 호환이 필수 최신 ASP.NET Core 프로젝트 XML 설정 선호 JSON, 코드 기반 설정 선호 구조적 로깅 필요 없음 구조적 로깅과 분석 필요

키움증권 Open API를 활용하여 갭 매매 전략 구현- C#

  1. 갭 매매 전략이란? 갭 매매란 주가가 전일 종가 대비 큰 폭으로 상승(갭업)하거나 하락(갭다운)하여 개장할 때, 발생한 갭의 움직임을 활용하여 수익을 내는 전략입니다. 이번에 소개할 전략은 다음 조건에 해당하는 종목을 대상으로 합니다. 조건검색 : 전일 대비 오늘 시가가 5% 이상 갭업으로 시작한 종목 전략 실행 : 개장 후 30분 이내에 주가가 전일 종가 수준까지 회귀하면 매수 진입 후, 갭이 완전히 메워질 때 매도합니다. 손절매 : 진입 가격에서 갭 상승분의 50% 하락 시 손절 익절매 : 갭 상승분의 50% 이상 추가 상승 시 익절합니다. 2. 구현 환경 및 준비사항 키움증권 API+ 설치 (영웅문 API) Visual Studio Community Edition (C#) 키움증권 계좌 (모의투자 계좌 추천) 3. C# 코드 전체 구현 (주석 포함) 다음 코드에는 갭 매매 전략, 주문 실행, 체결 확인 및 정정 주문까지 포함되어 있습니다. using System; using System.Collections.Generic; using AxKHOpenAPILib; public class GapTrading { private AxKHOpenAPI axKHOpenAPI; private string accountNumber; // 매수 후 체결된 정보를 저장할 Dictionary (종목코드, (체결가격, 체결수량)) private Dictionary< string , ( int price, int qty)> positions = new Dictionary< string , ( int , int )>(); public GapTradingStrategy (AxKHOpenAPI api, string accountNo) { axKHOpenAPI = api; accountNumber = accountNo; ...

실시간 웹 애플리케이션 구축하기: SignalR 이용하기

  IT 개발자로서 실시간 통신 기능을 웹 애플리케이션에 통합하는 것은 사용자 경험을 대폭 향상시킬 수 있는 중요한 방법 중 하나입니다. 오늘은 Microsoft의 SignalR을 이용하여 실시간 웹 애플리케이션을 구축하는 방법을 소개하려 합니다. SignalR은 웹소켓을 사용하여 실시간 통신을 쉽게 구현할 수 있도록 도와주는 라이브러리입니다. SignalR 소개 SignalR은 .NET 개발자들이 실시간 웹 기능을 손쉽게 구현할 수 있도록 지원하는 라이브러리입니다. 클라이언트와 서버 간의 양방향 통신을 가능하게 해 주며, 채팅 애플리케이션, 실시간 게임, 실시간 데이터 업데이트 등 다양한 실시간 애플리케이션을 개발할 때 유용하게 사용됩니다. 주요 기능 자동 연결 관리 : SignalR은 연결, 재연결 및 연결 해제 과정을 자동으로 처리합니다. 규모 확장성 : SignalR은 Redis, Azure SignalR Service 등을 통해 서버를 확장할 수 있는 기능을 지원합니다. 다양한 플랫폼 지원 : JavaScript, .NET, Java 등 다양한 클라이언트에서 사용 가능합니다. 실시간 채팅 애플리케이션 구축하기 개발 환경 설정 .NET Core SDK 설치 : SignalR은 .NET Core에 포함되어 있으므로, .NET Core SDK가 설치되어 있어야 합니다. Visual Studio 또는 VS Code 설치 : 개발 환경으로 Visual Studio 또는 VS Code를 사용할 수 있습니다. 프로젝트 생성 Visual Studio에서 새 프로젝트 생성 : 'ASP.NET Core Web Application' 선택 프로젝트 이름과 위치 설정 'ASP.NET Core Empty' 템플릿 선택 SignalR 라이브러리 추가 : NuGet 패키지 관리자를 통해 Microsoft.AspNetCore.SignalR을 설치합니다. SignalR 허브 생성 SignalR 허브는 클라이언트와 서버 간의 통신을 중개하는 역할을 합니다...

.NET MAUI: 크로스플랫폼 앱 개발의 미래

  1. .NET MAUI란? **.NET MAUI (Multi-platform App UI)**는 마이크로소프트에서 개발한 크로스플랫폼 애플리케이션 프레임워크로, 하나의 코드베이스로  iOS, Android, Windows, macOS 에서 실행되는 애플리케이션을 만들 수 있습니다. 주요 특징 단일 코드베이스 : 하나의 코드로 여러 플랫폼 지원 네이티브 UI : 각 플랫폼의 네이티브 UI 컨트롤을 활용 .NET 기반 : C#과 .NET 기술을 사용 MVU 및 MVVM 패턴 지원 핫 리로드 지원 : UI 변경 시 즉시 반영 강력한 성능 및 최적화 2. .NET MAUI 개발 환경 설정 필수 도구 운영체제 필수 소프트웨어 Windows Visual Studio 2022, .NET SDK 6 이상 Mac Visual Studio for Mac, .NET SDK 6 이상, Xcode 프로젝트 생성 방법 Visual Studio에서 생성 Visual Studio 2022 실행 "새 프로젝트 만들기" 선택 .NET MAUI 앱 템플릿 선택 프로젝트 이름 및 경로 설정 후 생성 명령어로 생성 dotnet new maui -n MyMauiApp cd MyMauiApp dotnet build dotnet run 3. .NET MAUI 프로젝트 구조 📂 MyMauiApp ├── 📂 Platforms # Android, iOS, macOS, Windows 코드 ├── 📂 Resources # 이미지, 폰트, 스타일 등 ├── 📂 Views # XAML UI 코드 ├── 📂 Models # 데이터 모델 ├── 📂 ViewModels # MVVM 패턴을 위한 ViewModel ├── App.xaml # 전역 스타일 및 테마 ├── MainPage.xaml # 메인 페이지 UI ├── MauiProgram.cs # 앱 초기 설정 4. 기본 UI 개발 (XAML & C#) XAM...

스마트팜과 AI

  스마트팜과 AI 융합, 농업 혁신의 미래를 보다 안녕하세요, IT 개발자 여러분 그리고 기술에 관심이 많은 일반 독자분들! 오늘은 농업과 최신 IT 기술의 만남, 바로 "스마트팜과 AI"에 대해서 이야기하고자 합니다. 스마트팜 기술은 이미 다양한 분야에서 활용되고 있지만, 인공지능(AI)을 접목하면 어떤 가능성이 있는지, 또 어떤 방향으로 개발하면 좋을지 함께 생각해보겠습니다. 스마트팜이란? 스마트팜은 사물인터넷(IoT) 기술을 농업에 적용하여 온도, 습도, 일조량, 영양분 등을 자동으로 제어하는 시스템입니다. 이미 많은 농가에서 사용하고 있는 기술로, 효율성을 높이고 노동력을 줄이는 데 크게 기여하고 있습니다. 왜 스마트팜에 AI가 필요한가? 기존 스마트팜은 센서를 통해 데이터를 수집하고 이를 기반으로 단순 제어만 수행했습니다. 하지만 AI가 결합되면 수집된 데이터를 심층적으로 분석하여 보다 정밀한 제어와 예측이 가능해집니다. 예를 들어, AI를 활용한 이미지 인식 기술을 통해 병충해 발생을 초기에 발견하고 대응할 수 있으며, 빅데이터 분석을 통한 작물 생장 예측으로 생산량과 품질을 획기적으로 향상시킬 수 있습니다. AI 스마트팜 아이디어 제안 1. 작물 건강 모니터링 시스템 이미지 처리 딥러닝 모델을 이용해 식물 잎이나 줄기의 이미지를 실시간 분석하여 병충해 및 영양 결핍 상태를 자동으로 감지하고 경고 메시지를 제공합니다. 2. 자율 농장 로봇 AI 기반의 자율 주행 로봇을 개발하여 농작물의 관리(파종, 관수, 수확)를 자동화합니다. 강화학습(Reinforcement Learning)을 적용해 로봇이 스스로 환경에 적응하고 효율적인 작업 방식을 학습하도록 합니다. 3. 예측형 기후 대응 시스템 과거 기상 데이터와 현재 환경 데이터를 머신러닝으로 분석하여 작물 생육에 최적화된 환경 조건을 예측하고 제어합니다. 갑작스러운 기후 변화에도 농작물 피해를 최소화할 수 있습니다. 4. 스마트 해충 방제 시스템 AI 기반으로 해충의 종류와 개체수를...