All 항등원
Specification<T>.All은 모든 엔터티를 만족하는 특별한 Specification입니다. 이 장에서는 All이 AND 연산의 항등원(identity element)으로서 동적 필터 구성을 어떻게 간결하게 만드는지 배웁니다.
All & X == X— 수학의 1 * x == x처럼, All은 AND 연산에서 아무 영향을 주지 않는 중립 요소입니다.
학습 목표
섹션 제목: “학습 목표”핵심 학습 목표
섹션 제목: “핵심 학습 목표”-
Null Object 패턴 이해
All은 “조건 없음”을 표현하는 Null Object- null 체크 대신
All로 시작하여 안전하게 조건 누적
-
항등원(Identity Element) 개념
All & X == X,X & All == X(AND 연산의 항등원)&연산자가All을 감지하면 새 객체를 만들지 않고 상대방을 그대로 반환
-
동적 필터 패턴
- 사용자 입력이나 검색 조건에 따라 Specification을 점진적으로 조립
All에서 시작하여&=로 조건을 추가하는 패턴
실습을 통해 확인할 내용
섹션 제목: “실습을 통해 확인할 내용”All.IsSatisfiedBy()가 항상 true 반환All & X가X와 동일한 참조 반환 (ReferenceEquals)- 동적 필터 구성 및 실행
핵심 개념
섹션 제목: “핵심 개념”Null Object 패턴
섹션 제목: “Null Object 패턴”프로그래밍에서 “조건 없음”을 표현할 때 흔히 null을 사용합니다:
// null 사용 - 매번 null 체크 필요Specification<Product>? spec = null;
if (hasCategory) spec = spec == null ? new ProductCategorySpec(cat) : spec.And(new ProductCategorySpec(cat));
if (hasPrice) spec = spec == null ? new ProductPriceRangeSpec(min, max) : spec.And(new ProductPriceRangeSpec(min, max));
// 실행 시에도 null 체크var results = spec == null ? products : products.Where(p => spec.IsSatisfiedBy(p));All을 사용하면 null 체크가 사라집니다:
// All 사용 - null 체크 불필요var spec = Specification<Product>.All;
if (hasCategory) spec &= new ProductCategorySpec(cat);
if (hasPrice) spec &= new ProductPriceRangeSpec(min, max);
// 실행 - All이면 모든 상품, 조건이 있으면 필터링var results = products.Where(p => spec.IsSatisfiedBy(p));항등원의 참조 최적화
섹션 제목: “항등원의 참조 최적화”& 연산자는 All을 감지하면 새 객체를 생성하지 않고 상대방을 그대로 반환합니다:
var all = Specification<Product>.All;var inStock = new ProductInStockSpec();
var result = all & inStock;ReferenceEquals(result, inStock); // true - 새 객체가 아님!이는 단순한 최적화가 아니라, 항등원의 수학적 정의를 코드로 표현한 것입니다. 곱셈에서 1 * x == x인 것처럼, AND 연산에서 All & X == X입니다.
동적 필터 패턴
섹션 제목: “동적 필터 패턴”실무에서 가장 많이 사용되는 패턴입니다. 검색 화면에서 사용자가 선택한 조건만 적용해야 할 때:
var spec = Specification<Product>.All;
if (categoryFilter is not null) spec &= new ProductCategorySpec(categoryFilter);
if (nameFilter is not null) spec &= new ProductNameContainsSpec(nameFilter);
if (onlyInStock) spec &= new ProductInStockSpec();
var results = products.Where(p => spec.IsSatisfiedBy(p));조건이 하나도 선택되지 않으면 spec은 All 그대로이므로 모든 상품이 반환됩니다.
프로젝트 설명
섹션 제목: “프로젝트 설명”프로젝트 구조
섹션 제목: “프로젝트 구조”AllIdentity/├── Program.cs├── Product.cs├── SampleProducts.cs├── Specifications/│ ├── ProductInStockSpec.cs│ ├── ProductPriceRangeSpec.cs│ ├── ProductCategorySpec.cs│ └── ProductNameContainsSpec.cs└── AllIdentity.csproj
AllIdentity.Tests.Unit/├── AllIdentityTests.cs├── Using.cs├── xunit.runner.json└── AllIdentity.Tests.Unit.csproj핵심 코드
섹션 제목: “핵심 코드”동적 필터 구성
섹션 제목: “동적 필터 구성”var spec = Specification<Product>.All;
if (categoryFilter is not null) spec &= new ProductCategorySpec(categoryFilter);
if (onlyInStock) spec &= new ProductInStockSpec();
var results = SampleProducts.All.Where(p => spec.IsSatisfiedBy(p));한눈에 보는 정리
섹션 제목: “한눈에 보는 정리”All 항등원 특성
섹션 제목: “All 항등원 특성”| 속성 | 값/동작 |
|---|---|
All.IsSatisfiedBy(x) | 항상 true |
All.IsAll | true |
All & X | X 반환 (ReferenceEquals) |
X & All | X 반환 (ReferenceEquals) |
null vs All 비교
섹션 제목: “null vs All 비교”| 구분 | null 방식 | All 방식 |
|---|---|---|
| 초기값 | null | Specification<T>.All |
| 조건 추가 | null 체크 후 And 또는 할당 | &= 연산자 |
| 실행 | null 체크 후 필터 또는 전체 반환 | 그냥 필터 (All이면 전체 반환) |
| 안전성 | NullReferenceException 위험 | null-safe |
FAQ
섹션 제목: “FAQ”Q1: All은 싱글턴인가요?
섹션 제목: “Q1: All은 싱글턴인가요?”A: 네, AllSpecification<T>는 static readonly 인스턴스를 통해 타입별 싱글턴으로 구현되어 있습니다. Specification<Product>.All은 항상 동일한 객체를 반환합니다.
Q2: All을 Or 연산에서도 사용할 수 있나요?
섹션 제목: “Q2: All을 Or 연산에서도 사용할 수 있나요?”A: 기술적으로 가능하지만 의미가 다릅니다. All | X는 항상 true (All이 모든 것을 만족하므로)가 되어 사실상 All과 동일합니다. All은 AND 연산의 항등원으로 설계된 것이며, Or 연산에서는 흡수원(absorbing element)이 됩니다.
Q3: 왜 And() 메서드에는 항등원 최적화가 없나요?
섹션 제목: “Q3: 왜 And() 메서드에는 항등원 최적화가 없나요?”A: And() 메서드는 단순히 new AndSpecification<T>(this, other)를 반환합니다. 항등원 최적화는 & 연산자에만 포함되어 있습니다. 이는 메서드의 단순성을 유지하면서도, 연산자 사용 시 성능과 참조 동일성을 보장하기 위한 설계 결정입니다.
Q4: 동적 필터에서 Or 조건도 추가할 수 있나요?
섹션 제목: “Q4: 동적 필터에서 Or 조건도 추가할 수 있나요?”A: 네, |= 연산자를 사용할 수 있습니다. 다만 And와 Or를 혼합할 때는 연산 우선순위에 주의해야 합니다. 복잡한 조합이 필요하다면 중간 변수로 의도를 명확히 하는 것이 좋습니다.
Q5: 이 패턴은 실무에서 어떻게 활용되나요?
섹션 제목: “Q5: 이 패턴은 실무에서 어떻게 활용되나요?”A: 검색 API, 필터링 UI, 리포트 조건 등 사용자 입력에 따라 쿼리를 동적으로 구성해야 하는 모든 곳에서 활용됩니다. Part 3에서 EF Core와 결합하여 동적 SQL 쿼리를 생성하는 방법을 배웁니다.
Part 1에서는 Specification의 기초를 다졌습니다 — 조건 캡슐화, 조합, 연산자, 그리고 All 항등원까지. 하지만 지금까지의 Specification은 메모리 컬렉션에서만 동작합니다. Part 2에서는 Expression Tree를 도입하여 Specification을 EF Core 같은 ORM에서도 사용할 수 있게 만듭니다.