C# · 심층 가이드
C# 패턴 완전 정리
전략과 옵저버, 데코레이터 같은 고전 패턴을 델리게이트와 이벤트, 의존성 주입이 기본으로 깔린 C# 환경에 맞춰 다시 쓴 스무 가지 구현을 코드로 비교합니다.
고전 디자인 패턴 책의 예제를 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# 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.