PHpullh

C# · 심층 가이드

C# 패턴 완전 정리

전략과 옵저버, 데코레이터 같은 고전 패턴을 델리게이트와 이벤트, 의존성 주입이 기본으로 깔린 C# 환경에 맞춰 다시 쓴 스무 가지 구현을 코드로 비교합니다.

주제 20개 · 예제 코드 포함 · 최종 수정 2026-08-30 · 작성 pullh 편집팀

고전 디자인 패턴 책의 예제를 C#으로 그대로 옮기면 대개 과합니다. 전략 패턴은 인터페이스와 구현체 세 개 대신 Func 하나로 끝나는 경우가 많고, 옵저버는 이벤트가 언어 차원에서 지원됩니다. 반대로 이 생태계에서 훨씬 중요해진 축도 있습니다. 생성자 주입과 컨테이너가 사실상 표준이 되면서, 팩터리와 저장소 패턴은 객체를 만드는 방법보다 수명을 누가 관리하느냐의 문제로 옮겨 갔습니다. 그래서 패턴 이름을 외우기보다, 이 언어에서 그 의도가 어떤 문법으로 실현되는지를 보는 편이 실전에 가깝습니다.

설정 객체는 뒤에 나오는 대부분의 예제가 값을 주입받는 통로라 먼저 봐 두면 편합니다. 전략 패턴옵저버는 인터페이스판과 델리게이트판을 비교하며 읽고, 데코레이터에서 캐시 래퍼retry 유틸로 이어지는 흐름은 같은 감싸기 기법을 세 가지 목적에 쓴 변주입니다. 미들웨어 체인은 그 감싸기를 여러 겹 쌓은 형태이고, 의존성 주입 컨테이너저장소 패턴은 조립 지점을 한곳으로 모으는 이야기로 묶입니다.

컨테이너를 쓰기 시작하면 수명 불일치가 조용한 사고로 돌아옵니다. 싱글턴으로 등록한 객체가 요청 단위 수명을 가진 의존성을 생성자로 받으면, 그 짧은 수명의 인스턴스가 싱글턴에 붙잡혀 앱이 끝날 때까지 살아남습니다. 데이터베이스 컨텍스트에서 특히 잘 터지는 형태입니다. 옵저버 쪽에도 비슷한 함정이 있습니다. 이벤트에 구독만 하고 해제하지 않으면 발행자가 구독자를 계속 참조해 회수되지 않으니, 구독 시점과 해제 시점을 짝으로 설계해야 합니다.

01설정 객체

C#에서 설정 객체 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 설정 객체
using System;

var strategies = new Dictionary<string, Func<int, int>> { ["percent"] = value => (int)(value * 0.9), ["flat"] = value => value - 5 };
Console.WriteLine(strategies["percent"](100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

02전략 패턴

C#에서 전략 패턴 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 전략 패턴
using System;

var cache = new Dictionary<int, string>();
string GetUser(int id) { if (!cache.ContainsKey(id)) cache[id] = "user"; return cache[id]; }
Console.WriteLine(GetUser(2));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

03옵저버

C#에서 옵저버 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 옵저버
using System;

var strategies = new Dictionary<string, Func<int, int>> { ["percent"] = value => (int)(value * 0.9), ["flat"] = value => value - 5 };
Console.WriteLine(strategies["percent"](100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

04데코레이터

C#에서 데코레이터 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 데코레이터
using System;

var cache = new Dictionary<int, string>();
string GetUser(int id) { if (!cache.ContainsKey(id)) cache[id] = "user"; return cache[id]; }
Console.WriteLine(GetUser(4));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

05어댑터

C#에서 어댑터 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 어댑터
using System;

var strategies = new Dictionary<string, Func<int, int>> { ["percent"] = value => (int)(value * 0.9), ["flat"] = value => value - 5 };
Console.WriteLine(strategies["percent"](100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

06커맨드

C#에서 커맨드 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 커맨드
using System;

var cache = new Dictionary<int, string>();
string GetUser(int id) { if (!cache.ContainsKey(id)) cache[id] = "user"; return cache[id]; }
Console.WriteLine(GetUser(6));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

07상태 패턴

C#에서 상태 패턴 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 상태 패턴
using System;

var strategies = new Dictionary<string, Func<int, int>> { ["percent"] = value => (int)(value * 0.9), ["flat"] = value => value - 5 };
Console.WriteLine(strategies["percent"](100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

08팩터리 메서드

C#에서 팩터리 메서드 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 팩터리 메서드
using System;

var cache = new Dictionary<int, string>();
string GetUser(int id) { if (!cache.ContainsKey(id)) cache[id] = "user"; return cache[id]; }
Console.WriteLine(GetUser(8));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

09빌더 패턴

C#에서 빌더 패턴 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 빌더 패턴
using System;

var strategies = new Dictionary<string, Func<int, int>> { ["percent"] = value => (int)(value * 0.9), ["flat"] = value => value - 5 };
Console.WriteLine(strategies["percent"](100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

10캐시 래퍼

C#에서 캐시 래퍼 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 캐시 래퍼
using System;

var cache = new Dictionary<int, string>();
string GetUser(int id) { if (!cache.ContainsKey(id)) cache[id] = "user"; return cache[id]; }
Console.WriteLine(GetUser(10));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

11retry 유틸

C#에서 retry 유틸 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// retry 유틸
using System;

var strategies = new Dictionary<string, Func<int, int>> { ["percent"] = value => (int)(value * 0.9), ["flat"] = value => value - 5 };
Console.WriteLine(strategies["percent"](100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

12미들웨어 체인

C#에서 미들웨어 체인 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 미들웨어 체인
using System;

var cache = new Dictionary<int, string>();
string GetUser(int id) { if (!cache.ContainsKey(id)) cache[id] = "user"; return cache[id]; }
Console.WriteLine(GetUser(12));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

13검증기 조합

C#에서 검증기 조합 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 검증기 조합
using System;

var strategies = new Dictionary<string, Func<int, int>> { ["percent"] = value => (int)(value * 0.9), ["flat"] = value => value - 5 };
Console.WriteLine(strategies["percent"](100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

14포매터 파이프

C#에서 포매터 파이프 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 포매터 파이프
using System;

var cache = new Dictionary<int, string>();
string GetUser(int id) { if (!cache.ContainsKey(id)) cache[id] = "user"; return cache[id]; }
Console.WriteLine(GetUser(14));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

15페이지네이션

C#에서 페이지네이션 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 페이지네이션
using System;

var strategies = new Dictionary<string, Func<int, int>> { ["percent"] = value => (int)(value * 0.9), ["flat"] = value => value - 5 };
Console.WriteLine(strategies["percent"](100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

16데이터 매퍼

C#에서 데이터 매퍼 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 데이터 매퍼
using System;

var cache = new Dictionary<int, string>();
string GetUser(int id) { if (!cache.ContainsKey(id)) cache[id] = "user"; return cache[id]; }
Console.WriteLine(GetUser(16));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

17저장소 패턴

C#에서 저장소 패턴 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 저장소 패턴
using System;

var strategies = new Dictionary<string, Func<int, int>> { ["percent"] = value => (int)(value * 0.9), ["flat"] = value => value - 5 };
Console.WriteLine(strategies["percent"](100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

18의존성 주입 컨테이너

C#에서 의존성 주입 컨테이너 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 의존성 주입 컨테이너
using System;

var cache = new Dictionary<int, string>();
string GetUser(int id) { if (!cache.ContainsKey(id)) cache[id] = "user"; return cache[id]; }
Console.WriteLine(GetUser(18));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

19템플릿 메서드

C#에서 템플릿 메서드 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 템플릿 메서드
using System;

var strategies = new Dictionary<string, Func<int, int>> { ["percent"] = value => (int)(value * 0.9), ["flat"] = value => value - 5 };
Console.WriteLine(strategies["percent"](100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

20파사드

C#에서 파사드 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

C# code

// 파사드
using System;

var cache = new Dictionary<int, string>();
string GetUser(int id) { if (!cache.ContainsKey(id)) cache[id] = "user"; return cache[id]; }
Console.WriteLine(GetUser(20));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

정리하며

  • 인터페이스와 구현체를 늘리기 전에 델리게이트 하나로 끝나는지 먼저 따져 봅니다
  • 감싸기 한 기법으로 캐시, 재시도, 로깅을 모두 만들 수 있으니 순서만 잘 정합니다
  • 싱글턴이 짧은 수명 의존성을 주입받으면 그 인스턴스가 앱 전체 수명 동안 붙잡힙니다
  • 이벤트는 구독 코드를 쓰는 자리에서 해제 시점을 함께 정해야 누수가 없습니다

더 깊이 들어가고 싶다면 C# 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.