C# 디자인 패턴 핵심 3종류 정리: 생성·구조·행위 패턴 + 예제 코드 포함

디자인 패턴은 개발을 진행하면서 많이 사용할 수 밖에없는 소프트웨어 아키텍쳐 설계도와 같습니다.
특정상황에는 이런 패턴을 사용하자 이런 느낌으로 기억해두었다가 해당 상황이 나오면 사용하는데 그러기 위해선 디자인 패턴의 구분과 종류 또 어떤 상황에서 사용을 해야하는지 명확하게 알아야 합니다.


디자인 패턴의 종류 표
분류 패턴 목적
생성 Singleton 인스턴스 1개만 생성. 객체 생성을 특정 클래스에 위임
Factory Method 객체 생성을 서브 클래스에 위임
Abstract Factory 관련 객체 집합 생성
Builder 복잡한 객체 생성 단계 분리
Prototype 객체 복제를 통한 생성
구조 Adapter 호환되지 않는 인터페이스 연결
Bridge 추상화와 구현 분리
Composite 부분-전체 계층 표현
Decorator 동적으로 책임 및 확장 부여
Facade 복잡한 서브시스템 단순화
Flyweight 객체 공유로 메모리 절약
Proxy 접근 제어 및 지연 로딩
행위 Chain of Responsibility 요청을 체인으로 연결
Command 요청을 객체로 캡슐화
Interpreter 언어/문법 규칙 해석
Iterator 컬렉션 순회 접근
Mediator 복잡한 관계를 중재
Memento 객체 상태 저장 및 복원
Observer 상태 변화 시 알림
State 상태에 따라 행동 변화
Strategy 알고리즘 교체 가능
Template Method 알고리즘 골격 정의, 구체화 하위
Visitor 구조 변경 없이 기능 추가

위의 표를 기준으로 하나씩 디자인 패턴에 관해 알아보겠습니다.


생성패턴
생성패턴은 객체의 생성방식을 추상화하고 시스템이 객체를 생성,조합하는 과정을 유연하고 재사용 가능하게 만드는 패턴입니다.

Singleton(싱글턴)
클래스의 인스턴스를 오직 단 하나만 생성하도록 보장하고 전역접근을 제공합니다.

  • 메모리낭비 최소화
  • 전역변수 사용 없이 어디서든 참조가능
  • 특정 연산에만 쓰는 객체 관리 유용
예시 코드
public sealed class Logger
{
    private static readonly Lazy<Logger> _instance = new(() => new Logger());
    public static Logger Instance => _instance.Value;
    private Logger() { }
    public void Log(string message) => Console.WriteLine(message);
}
// 프로그램 전체에서 Logger.Instance로 동일한 인스턴스 사용.

Factory Method(팩토리메서드)
객체 생성을 서브클래스에 위임하여 클라이언트 코드의 결합도를 낮추는 패턴

  • 생성자 제약에서 벗어남
  • 객체 생성로직을 별도 함수로 캡슐화 
  • 상위클래스에서 인터페이스 정의, 하위 클래스에서 구현
예시 코드
interface IButton { void Render(); }

abstract class Dialog
{
    public abstract IButton CreateButton();
}

class WindowsDialog : Dialog
{
    public override IButton CreateButton() => new WinButton();
}

class WinButton : IButton
{
    public void Render() => Console.WriteLine("Windows Button");
}
// 사용: new WindowsDialog().CreateButton().Render();

Abstract Factory(추상팩토리 패턴)
관련된 객체 집합을 생성하는 인터페이스를 제공하며 구체적인 클래스를 지정하지 않고 생성

  • 연관된 객체들을 한꺼번에 묶어서 생성
  • 플랫폼별 컴포넌트 생성같은 느낌의 작업에 유용
  • 객체간의 의존성 제거
예시 코드
interface IButton { void Render(); }

abstract class Dialog
{
    public abstract IButton CreateButton();
}

class WindowsDialog : Dialog
{
    public override IButton CreateButton() => new WinButton();
}

class WinButton : IButton
{
    public void Render() => Console.WriteLine("Windows Button");
}
// 사용: new WindowsDialog().CreateButton().Render();

Builder(빌더 패턴)
복잡한 객체의 생성 과정과 표현을 분리해 동일한 생성 절차에 다양한 표현을 생성

  • 복잡한 인스턴스를 단계별로 조립함
  • 생성 메서드, 구현 메서드 분리
  • 선택적 매개 변수가 많을 때 유용
예시 코드
class Report { public string Title = ""; public string Body = ""; }

interface IReportBuilder
{
    IReportBuilder SetTitle(string t);
    IReportBuilder SetBody(string b);
    Report Build();
}

class ReportBuilder : IReportBuilder
{
    private readonly Report _r = new();
    public IReportBuilder SetTitle(string t) { _r.Title = t; return this; }
    public IReportBuilder SetBody(string b) { _r.Body = b; return this; }
    public Report Build() => _r;
}
// 사용: var r = new ReportBuilder().SetTitle("T").SetBody("B").Build();

Prototype(프로토타입 패턴)
기존 객체를 복제하여 새로운 객체를 생성하는 패턴입니다.

  • 처음무터 일반적인 원형을 만들고 필요한 부분만 수정
  • 생성할 객체의 원형을제공
  • 사이즈가 큰 객체 생성시 유용
예시 코드
public class Shape : ICloneable
{
    public string Color = "Black";
    public object Clone() => MemberwiseClone();
}
// 사용: var copy = (Shape)new Shape().Clone();

구조 패턴
클래스나 객체를 조합하여 더 큰 구조를 만들 때 사용하는 패턴, 클래스간의 관계를 효율적으로 구성합니다.

Adapter(어댑터 패턴)
호환되지않는 인터페이스르 가진 클래스들을 연결해 함께 작동하도록 하는 패턴입니다.

  • 기존클래스를 재사용 가능하게 만듬
  • 중간에서 인터페이스를 맞춰주는 역할
  • 레거시 시스템과 신규 시스템 연동에 유용
예시 코드
class OldPayment { public void PayOld(int amount) => Console.WriteLine($"PayOld {amount}"); }

interface INewPayment { void Pay(decimal amount); }

class PaymentAdapter : INewPayment
{
    private readonly OldPayment _old = new();
    public void Pay(decimal amount) => _old.PayOld((int)amount);
}
// 사용: INewPayment p = new PaymentAdapter(); p.Pay(100m);

Bridge(브릿지 패턴)
구현부와 추상화를 분리해 각자 독립적으로 확장할 수 있게 하는 패턴입니다.

  • 추상화와 구현이 강하게 결합되는 문제를 해결
  • 두 계층을 독립적으로 변경 가능
  • 확장성과 유연성 증대
예시 코드
interface IRenderer { void RenderCircle(float radius); }
class VectorRenderer : IRenderer { public void RenderCircle(float r) => Console.WriteLine($"Vector circle {r}"); }

abstract class ShapeB
{
    protected readonly IRenderer renderer;
    protected ShapeB(IRenderer r) { renderer = r; }
    public abstract void Draw();
}

class CircleB : ShapeB
{
    private readonly float radius;
    public CircleB(IRenderer r, float rad) : base(r) { radius = rad; }
    public override void Draw() => renderer.RenderCircle(radius);
}
// 사용: new CircleB(new VectorRenderer(), 5).Draw();

Composite(컴포지트 패턴)
객체들을 트리 구조로 구성해 전체~부분적 관계를 동일하게 다루는 패턴입니다.

  • 단일 객체와 복합 객체 동일 처리
  • 재귀적 트리구조 표현
  • 폴더/파일 구조관리에 유용
예시 코드
interface IGraphic { void Draw(); }

class Dot : IGraphic { public void Draw() => Console.WriteLine("dot"); }

class CompoundGraphic : IGraphic
{
    private readonly List<IGraphic> _children = new();
    public void Add(IGraphic g) => _children.Add(g);
    public void Draw() { foreach (var c in _children) c.Draw(); }
}
// 사용: var g = new CompoundGraphic(); g.Add(new Dot()); g.Draw();

Decorator(데코레이터 패턴)
객체에 동적으로 책임을 추가하는 구조, 서브클래싱의 유연한 대안을 하는 패턴입니다.

  • Wrapper를 활용한 기능 추가
  • 상속대신 조합을 통한 확장
  • 런타임에 기능 조합 가능
예시 코드
interface INotifier { void Send(string msg); }

class EmailNotifier : INotifier { public void Send(string msg) => Console.WriteLine($"Email: {msg}"); }

class NotifierDecorator : INotifier
{
    protected readonly INotifier wrap;
    public NotifierDecorator(INotifier w) { wrap = w; }
    public virtual void Send(string msg) => wrap.Send(msg);
}

class SlackNotifier : NotifierDecorator
{
    public SlackNotifier(INotifier w) : base(w) { }
    public override void Send(string msg) { base.Send(msg); Console.WriteLine($"Slack: {msg}"); }
}
// 사용: new SlackNotifier(new EmailNotifier()).Send("hi");

Facade(파사드 패턴)
복잡한 서브시스템에 대한 단순한 통합 인터페이스를 제공하는 패턴입니다.

  • 복잡성 숨기기
  • 클라이언트 코드 간소화
  • api래퍼 역할
예시 코드
class SubA { public void DoA() => Console.WriteLine("A"); }
class SubB { public void DoB() => Console.WriteLine("B"); }

class SystemFacade
{
    private readonly SubA _a = new();
    private readonly SubB _b = new();
    public void DoAll() { _a.DoA(); _b.DoB(); }
}
// 사용: new SystemFacade().DoAll();

Flyweight(플라이웨이트 패턴)
동일한 객체를 공유하여 메모리를 절약하는 구조패턴입니다.

  • 대량의 비슷한 객체 생성 시 메모리 최적화
  • 내재적 상태 , 외재적 상태로 분리
  • 문자 렌더링,ui컴포넌트에 유리
예시 코드
class Glyph
{
    public readonly char Char;
    public readonly string Font;
    public Glyph(char c, string f) { Char = c; Font = f; }
}

class GlyphFactory
{
    private readonly Dictionary<string, Glyph> _pool = new();
    public Glyph Get(char c, string font)
    {
        var key = $"{c}:{font}";
        return _pool.TryGetValue(key, out var g) ? g : _pool[key] = new Glyph(c, font);
    }
}
// 사용: var g = new GlyphFactory().Get('a', "Consolas");

Proxy(프록시 패턴)
대리 객체를 통한 접근 제어를 제공하는 패턴입니다.

  • 접근 제어, 로깅 ,캐싱 가능
  • 실제 객체 생성을 지연시킬수 있음(lazy loading)
  • 원격 접근, 보안등에 활용
예시 코드
interface IImage { void Display(); }

class RealImage : IImage
{
    private readonly string file;
    public RealImage(string f) { file = f; Console.WriteLine("Loading..."); }
    public void Display() => Console.WriteLine($"Display {file}");
}

class ImageProxy : IImage
{
    private readonly string file; private RealImage? real;
    public ImageProxy(string f) { file = f; }
    public void Display() { real ??= new RealImage(file); real.Display(); }
}
// 사용: IImage img = new ImageProxy("a.png"); img.Display();

행위 패턴
객체나 클래스 간의 상호작용 방식을 정의하여 책임을 분배하고 통신을 효율적으로 하는 패턴이며 복잡한 로직을 분리하고 결합도를 낮추고 유연하게 흐름을 제어하도록 함

Chain of Responsibility(책임 연쇄 패턴)
요청 처리하는 객체를 체인으로 연결한 뒤 순차적으로 전달하는 패턴입니다.

  • 요청 처리를 여러 객체에 분산
  • 런타임에 체인 동적 구성간으
  • 이벤트 처리, 필터 체인에 유용
예시 코드
abstract class Handler
{
    private Handler? _next;
    public Handler SetNext(Handler n) { _next = n; return n; }
    public void Handle(int level) { if (!Process(level)) _next?.Handle(level); }
    protected abstract bool Process(int level);
}

class Manager : Handler
{
    protected override bool Process(int level) { if (level <= 1) { Console.WriteLine("Manager"); return true; } return false; }
}

class Director : Handler
{
    protected override bool Process(int level) { if (level <= 2) { Console.WriteLine("Director"); return true; } return false; }
}
// 사용: new Manager().SetNext(new Director()).Handle(2);

Command(명령 패턴)
요청을 객체로 캡슐화해 실행/취소등을 유연하게 제어하는 패턴입니다.

  • 요청을 매개변수화하여 전달 가능
  • 요청 큐잉 실행 취소 지원
  • 리모컨 메뉴시스템에 유용함
예시 코드
interface ICommand { void Execute(); }

class Light { public void On() => Console.WriteLine("on"); public void Off() => Console.WriteLine("off"); }

class LightOnCommand : ICommand
{
    private readonly Light _l;
    public LightOnCommand(Light l) { _l = l; }
    public void Execute() => _l.On();
}

class Remote { public void Submit(ICommand c) => c.Execute(); }
// 사용: new Remote().Submit(new LightOnCommand(new Light()));

Interpreter(인터프리터 패턴)
언어나 문법 규칙을 클래스화해 해석하는 패턴입니다.

  • 간단한 문법 규칙을 클래스로 표현
  • 복잡한 언어 파싱에 유용
  • 간단한 계산기, dsl에 활용함
예시 코드
interface IExpression { int Interpret(); }

class Number : IExpression
{
    private readonly int v;
    public Number(int v) { this.v = v; }
    public int Interpret() => v;
}

class Plus : IExpression
{
    private readonly IExpression l, r;
    public Plus(IExpression l, IExpression r) { this.l = l; this.r = r; }
    public int Interpret() => l.Interpret() + r.Interpret();
}
// 사용: new Plus(new Number(1), new Number(2)).Interpret(); // 3

Iterator(반복자 패턴)
컬렉션의 내부 구조를 노출하지않고 요소들을 순차적으로 접근하는 패턴입니다.

  • 컬렉션 구조와 무관하게 순회 가능
  • 여러 순회 방식의 지원 
  • c# foreach, java Iterator, python for-each와 같음
예시 코드
class MyCollection : IEnumerable<int>
{
    private readonly int[] data;
    public MyCollection(params int[] d) { data = d; }
    public IEnumerator<int> GetEnumerator() { foreach (var i in data) yield return i; }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
}
// 사용: foreach (var x in new MyCollection(1,2,3)) Console.WriteLine(x);

Mediator(중재자 패턴)
객체간의 복잡한 관계를 중재자 하나에 집중해 의존성을 제거하는 패턴입니다.

  • 다대다 관계 일대다로 단순화 
  • 객체 간 결합도 감소
  • 채팅방, 항공제어 시스템에서 유용
예시 코드
class ChatRoom { public void Show(string user, string msg) => Console.WriteLine($"{user}: {msg}"); }

class User
{
    private readonly string name;  
    private readonly ChatRoom room;
 
    public User(string n, ChatRoom r) { name = n; room = r; }
    public void Send(string m) => room.Show(name, m);
}
// 사용: var room=new ChatRoom();
new User("A",room).Send("hi");
new User("B",room).Send("hi");

Memento(메멘토 패턴)
객체의 이전 상태를 외부에 저장하고 필요할 때 복원하는 패턴입니다.

  • 객체 캡슐화 유지하면서 상태 저장
  • 되돌리기 기능구현
  • 스냅샷 세이브 포인트에 유용
예시 코드
record Memento(string State);

class Editor
{
    public string Text { get; set; } = "";
    public Memento Save() => new(Text);
    public void Restore(Memento m) => Text = m.State;
}
// 사용: var e=new Editor{Text="v1"}; var m=e.Save(); e.Text="v2"; e.Restore(m);

Observer(옵져버 패턴)
한 객체의 상태가 변화하면 관련된 객체들에게 자동으로 알리는 패턴입니다.

  • 하나의 소스, 여러 객체가 수신(one-to-many)구조
  • 느슨한 결합
  • 이벤트 리스너, pub/sub구조
예시 코드
class Subject
{
    public event Action<int>? Changed;
    private int _v;
    public int Value { get => _v; set { _v = value; Changed?.Invoke(_v); } }
}
// 사용: var s=new Subject(); s.Changed += v=>Console.WriteLine(v); s.Value=42;

State(상태 패턴)
객체의 상태에 따라 행동이 달라지는 구조를 캡슐화하는 패턴입니다.

  • 상태별로 클래스를 나누어서 구현
  • 상태 전이 로직 간소화
  • 문이나 신호등처럼 상태가 여러개인 객체에 유용함
예시 코드
interface ITurnstileState { void Coin(Turnstile t); void Push(Turnstile t); }

class Locked : ITurnstileState
{
    public void Coin(Turnstile t) { Console.WriteLine("Unlocked"); t.State = new Unlocked(); }
    public void Push(Turnstile t) => Console.WriteLine("Locked");
}

class Unlocked : ITurnstileState
{
    public void Coin(Turnstile t) => Console.WriteLine("Already unlocked");
    public void Push(Turnstile t) { Console.WriteLine("Pass"); t.State = new Locked(); }
}

class Turnstile
{
    public ITurnstileState State { get; set; } = new Locked();
    public void Coin() => State.Coin(this);
    public void Push() => State.Push(this);
}
// 사용: var t=new Turnstile(); t.Coin(); t.Push();

Strategy(전략 패턴)
알고리즘을 캡슐화해 교체 가능하게 하여서 유연성을 높이는 패턴입니다.

  • 런타임에 알고리즘 선택 가능
  • 조건문 제거
  • 정렬 결제방식 선택등에 유용
예시 코드
interface ISortStrategy { IEnumerable<int> Sort(IEnumerable<int> data); }

class QuickSortStrategy : ISortStrategy
{
    public IEnumerable<int> Sort(IEnumerable<int> data) => data.OrderBy(x => x);
}

class ContextSort
{
    private ISortStrategy strategy;
    public ContextSort(ISortStrategy s) { strategy = s; }
    public IEnumerable<int> DoSort(IEnumerable<int> d) => strategy.Sort(d);
}
// 사용: new ContextSort(new QuickSortStrategy()).DoSort(new[]{3,1,2});

Template Method(템플릿 메서드 패턴)
알고리즘의 구조를 상위 클래스에 정의하고 일부단계를 하위클래스가 구현하는 패턴입니다.

  • 상위 클래스에서 알고리즘 스켈레톤 정의
  • 하위클래스에서 오버라이딩으로 구현
  • 코드 중복 감소
예시 코드
abstract class DataExporter
{
    public void Export() { Read(); Transform(); Save(); }
    protected abstract void Read();
    protected virtual void Transform() { }
    protected abstract void Save();
}

class CsvExporter : DataExporter
{
    protected override void Read() => Console.WriteLine("Read CSV");
    protected override void Save() => Console.WriteLine("Save");
}
// 사용: new CsvExporter().Export();

Visitor(방문자 패턴)
객체의 구조는 변경하지 않으면서 새로운 기능을 추가할 수 있는 패턴입니다.

  • 데이터 구조와 연산 분리
  • 새로운 연산 추가가 용이
  • 컴파일러 ast 방문 보고서 생성등에 유용
예시 코드
interface IVisitor { void Visit(Foo f); void Visit(Bar b); }
interface IElement { void Accept(IVisitor v); }

class Foo : IElement { public void Accept(IVisitor v) => v.Visit(this); }
class Bar : IElement { public void Accept(IVisitor v) => v.Visit(this); }

class PrintVisitor : IVisitor
{
    public void Visit(Foo f) => Console.WriteLine("Foo");
    public void Visit(Bar b) => Console.WriteLine("Bar");
}
// 사용: IVisitor v=new PrintVisitor(); IElement e=new Foo(); e.Accept(v);

상황에 맞는 패턴 정리

전역 객체가 필요할 때 | Singleton 
 객체 생성 로직이 복잡할 때 | Factory Method, Builder 
 다양한 플랫폼 지원이 필요할 때 | Abstract Factory 
 기존 코드와 신규 코드 연동 | Adapter 
 여러 기능의 조합이 필요할 때 | Decorator 
 복잡한 시스템을 단순화할 때 | Facade 
 요청을 미루거나 취소할 때 | Command 
 상태에 따라 동작이 달라질 때 | State 
 여러 알고리즘 중 선택할 때 | Strategy 
 이벤트 기반 시스템 | Observer 


 

반응형