--- id: wiki-2026-0508-open-closed-principle-ocp title: Open-Closed Principle (OCP) category: 10_Wiki/Topics status: verified canonical_id: self aliases: [OCP, Open Closed Principle, SOLID OCP] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [solid, ocp, design-principles, oop, architecture] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: TypeScript framework: none --- # Open-Closed Principle (OCP) ## 매 한 줄 > **"매 extension 에 open, modification 에 closed"**. Bertrand Meyer (1988) 매 originally inheritance, Robert Martin (1996) 매 abstraction-based redefinition. 새 behavior 매 *추가* 의 way 로, 매 existing code 의 *수정* 없이 — strategy / plugin / open enum 등 매 광범위 응용. ## 매 핵심 ### 매 두 가지 해석 - **Meyer's OCP**: 매 inheritance — class 매 closed for modification (매 published interface 안정), open for extension (매 subclass). - **Polymorphic OCP** (Martin): 매 abstraction (interface) 의존, 매 implementation 추가 만 으로 extend. ### 매 왜 중요 - 매 regression 위험 줄임 (existing 안 건드림). - 매 plugin / extension model 가능. - 매 testability — 매 mock implementation 추가 만. ### 매 응용 1. Strategy pattern — payment processor (Stripe, Toss, PayPal). 2. Discriminated union 의 add new variant. 3. Plugin architecture (Vite plugin, ESLint rule). 4. Visitor pattern. 5. Middleware chain (Koa, Express). ## 💻 패턴 ### Strategy interface (TS) ```ts interface PaymentProcessor { charge(amount: number, token: string): Promise<{ id: string }>; } class StripeProcessor implements PaymentProcessor { async charge(amount: number, token: string) { return { id: `stripe_${Date.now()}` }; } } class TossProcessor implements PaymentProcessor { async charge(amount: number, token: string) { return { id: `toss_${Date.now()}` }; } } // New processor → just add new class. CheckoutService unchanged. class CheckoutService { constructor(private processor: PaymentProcessor) {} pay(amt: number, t: string) { return this.processor.charge(amt, t); } } ``` ### Discriminated union + exhaustiveness ```ts type Shape = | { kind: "circle"; r: number } | { kind: "square"; side: number } | { kind: "triangle"; base: number; height: number }; function area(s: Shape): number { switch (s.kind) { case "circle": return Math.PI * s.r ** 2; case "square": return s.side ** 2; case "triangle": return (s.base * s.height) / 2; default: { const _exhaust: never = s; throw new Error(_exhaust); } } } // Add 'pentagon' → TS error in `area`, no silent miss. ``` ### Plugin / hook registry ```ts type Hook = (ctx: T) => void | Promise; class HookRegistry { private hooks: Hook[] = []; add(h: Hook) { this.hooks.push(h); } async run(ctx: T) { for (const h of this.hooks) await h(ctx); } } const onUserCreated = new HookRegistry<{ userId: string }>(); onUserCreated.add(({ userId }) => sendWelcomeEmail(userId)); onUserCreated.add(({ userId }) => analytics.track("user_created", userId)); // New behavior → add new hook, no service change. ``` ### Visitor pattern ```ts interface NodeVisitor { visitNumber(n: NumberNode): R; visitBinary(n: BinaryNode): R; } abstract class AstNode { abstract accept(v: NodeVisitor): R; } class NumberNode extends AstNode { constructor(public val: number) { super(); } accept(v: NodeVisitor) { return v.visitNumber(this); } } class BinaryNode extends AstNode { constructor(public op: "+"|"-", public l: AstNode, public r: AstNode) { super(); } accept(v: NodeVisitor) { return v.visitBinary(this); } } // New traversal (printer, evaluator, optimizer) → new visitor class. ``` ### Middleware chain ```ts type Middleware = (ctx: C, next: () => Promise) => Promise; function compose(mws: Middleware[]): (ctx: C) => Promise { return async (ctx) => { let i = -1; const dispatch = async (idx: number): Promise => { if (idx <= i) throw new Error("next() called multiple times"); i = idx; const fn = mws[idx]; if (!fn) return; await fn(ctx, () => dispatch(idx + 1)); }; await dispatch(0); }; } const app = compose<{ req: Request; res?: Response }>([ async (c, next) => { console.log("log"); await next(); }, async (c, next) => { c.res = new Response("ok"); await next(); }, ]); ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | Multiple swappable algos | Strategy / interface | | Closed set of variants | Discriminated union + exhaustive switch | | Open set of behaviors | Plugin / hook registry | | Tree traversal, multiple ops | Visitor | | Pipeline | Middleware chain | | Just one impl, no plans | Don't pre-abstract (YAGNI) | **기본값**: 매 second variation 발견 시 abstract — 매 first time 의 over-abstraction X. ## 🔗 Graph - 부모: [[SOLID Principles]] · [[Design-Principles]] - 변형: [[Visitor-Pattern]] - 응용: [[Middleware]] · [[API Response & State Modeling|Discriminated-Unions]] · [[Dependency_Injection_(DI)|Dependency-Injection]] - Adjacent: [[DIP]] · [[Composition-over-Inheritance]] ## 🤖 LLM 활용 **언제**: 매 add-only domain (payment processor, plugin, parser AST), variant 매 자주 추가. **언제 X**: 매 small / unchanging code — 매 abstraction 의 cost > benefit. ## ❌ 안티패턴 - **Premature abstraction**: 매 single impl 의 interface — 매 indirection 만 추가, value 0. - **Speculative generality**: 매 "혹시 나중에" — YAGNI. - **Inheritance for code reuse**: 매 LSP 위반 risk. Composition 우선. - **Open everything**: 매 모든 method virtual / hook — 매 cognitive load 폭증. - **Ignore exhaustiveness**: 매 switch default fallback — 매 new variant 의 silent skip. ## 🧪 검증 / 중복 - Verified (Meyer "Object-Oriented Software Construction", Martin "Clean Architecture", refactoring.guru). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — strategy, discriminated union, plugin/visitor/middleware 패턴 |