객체 지행 설계 및 디자인 패턴 마스터 가이드: SOLID와 실무 패턴
Dev
마지막 업데이트

객체 지행 설계 및 디자인 패턴 마스터 가이드: SOLID와 실무 패턴


객체 지향 프로그래밍(OOP)은 현대 소프트웨어 공학의 근간을 이루는 패러다임입니다. 하지만 단순히 클래스를 선언하고 상속하는 기본 문법 수준에 그친다면, 비즈니스 요구사항의 변경에 따라 시스템 전체가 예기치 않게 오작동하는 강결합 스파게티 코드를 유도하게 됩니다.

본 가이드에서는 지속 가능한 설계를 만들기 위한 SOLID 설계 원칙과 상속 대 합성의 개념적 차이, 그리고 현업 실무에서 다루는 4대 핵심 디자인 패턴(전략, 팩토리, 옵저버, 커맨드)의 실전 구현 방식을 완전 정복합니다.


1. SOLID 설계 원칙: 대규모 협업을 위한 5대 아키텍처 법률

SOLID 원칙은 결합도(Coupling)를 낮추고 응집도(Cohesion)를 높여, 코드 변경에 따른 사이드 이펙트(부작용)를 억제하는 핵심 가이드라인입니다.

  1. 단일 책임 원칙 (Single Responsibility - SRP): 클래스는 단 하나의 변경 이유만 가져야 합니다. 즉, 하나의 클래스는 하나의 책임만 담당해야 합니다. 사용자 정보 저장과 직렬화를 별도 저장소(Repository) 및 직렬화기(Serializer) 클래스로 쪼개는 설계가 대표적입니다.
  2. 개방-폐쇄 원칙 (Open-Closed - OCP): 기존 코드를 변경하지 않고 기능을 확장할 수 있어야 합니다. 이는 추상화(인터페이스)를 통해 상속 기반 또는 합성 기반 확장을 구축하여 달성합니다.
  3. 리스코프 치환 원칙 (Liskov Substitution - LSP): 자식 클래스는 언제나 부모 클래스를 부작용 없이 대체할 수 있어야 합니다. 부모의 행동 규약을 자식 클래스가 임의로 축소하거나 다르게 동작시켜 다형성 구조를 훼손하지 않아야 함을 의미합니다.
  4. 인터페이스 분리 원칙 (Interface Segregation - ISP): 범용적인 대형 인터페이스 하나보다 작고 명확한 책임을 갖는 다수의 인터페이스가 훨씬 안전합니다. 프린터와 복사기, 팩스 기능을 개별 인터페이스로 분리하여 필요에 맞게 조합하는 설계가 속합니다.
  5. 의존역전 원칙 (Dependency Inversion - DIP): 고수준 비즈니스 로직 모듈은 구체적인 저수준 기술 구현체(예: MySQL 데이터베이스)에 의존하지 않고, 추상화된 인터페이스에 의존해야 합니다. 이를 구현한 대표적인 기술이 스프링(Spring)의 의존성 주입(DI)입니다.

2. 상속(Inheritance) 대신 합성(Composition)을 권장하는 이유

객체 지향을 처음 다룰 때 코드 재사용의 수단으로 상속을 남발합니다. 하지만 상속은 컴파일 타임에 관계가 영구적으로 결정되어 결합도가 높고, 부모 클래스의 내부 명세가 자식 클래스에게 고스란히 노출되는 문제(White-box reuse)가 있습니다.

반면 **합성(Composition)**은 클래스 내부에 다른 인터페이스 인스턴스를 멤버 필드 변수(Has-A 관계)로 주입받아, 런타임에 필요한 행동을 안전하게 위임(Delegation)하여 조립하는 방식입니다.

  • 합성의 이점: 런타임에 결합 대상을 유연하게 교체할 수 있으며, 다중 상속의 한계를 극복하고 각 결합 모듈의 내부 변화가 외부에 영향을 주지 않으므로 유지보수성이 비약적으로 향상됩니다.

3. 실무를 지배하는 4대 핵심 디자인 패턴

1) 스트래티지 패턴 (Strategy Pattern - 전략 패턴)

동적으로 결제 수단(신용카드, 카카오페이 등)이나 할인 정책과 같은 알고리즘을 런타임에 유연하게 교체하며 실행하는 패턴입니다.

  • 실전 템플릿:
    interface PaymentStrategy { pay(amount: number): void; }
    class CardPayment implements PaymentStrategy { pay(amount: number) { /* 카드 연동 */ } }
    class TossPayment implements PaymentStrategy { pay(amount: number) { /* 토스 연동 */ } }
    
    class PaymentProcessor {
        constructor(private strategy: PaymentStrategy) {}
        execute(amount: number) { this.strategy.pay(amount); }
    }

2) 팩토리 패턴 (Factory Pattern)

클라이언트가 구체적인 생성 클래스를 직접 지정하여 커플링되지 않도록, 객체 생성(Instantiation) 과정을 별도의 전용 클래스나 메서드 내부로 캡슐화하여 분리하는 패턴입니다.

  • 실전 템플릿:
    interface Document { open(): void; }
    class PdfDocument implements Document { open() { console.log("PDF"); } }
    class ExcelDocument implements Document { open() { console.log("Excel"); } }
    
    class DocumentFactory {
        static createDocument(type: string): Document {
            if (type === "pdf") return new PdfDocument();
            if (type === "excel") return new ExcelDocument();
            throw new Error("Unknown document type");
        }
    }

3) 옵저버 패턴 (Observer Pattern)

어떤 상태 변화가 감지되었을 때, 이와 연동된 다수의 리스너(구독자)들에게 이벤트를 자동으로 알리는 느슨하게 결합된 발행-구독(Pub-Sub) 기반 패턴입니다.

  • 실전 템플릿:
    interface Observer { update(state: string): void; }
    class AlertSystem implements Observer { update(state: string) { console.log(`Alert: ${state}`); } }
    
    class Subject {
        private observers: Observer[] = [];
        attach(o: Observer) { this.observers.push(o); }
        notify(state: string) { this.observers.forEach(o => o.update(state)); }
    }

4) 커맨드 패턴 (Command Pattern)

실행할 요청이나 동작을 단일 독립 객체(Command)로 캡슐화하여 전달하고, 이를 통해 요청을 큐에 저장하거나 취소(Undo/Redo) 메커니즘을 구현할 수 있도록 돕는 패턴입니다.

  • 실전 템플릿:
    interface Command { execute(): void; undo(): void; }
    class LightOnCommand implements Command {
        constructor(private light: Light) {}
        execute() { this.light.turnOn(); }
        undo() { this.light.turnOff(); }
    }

4. 아키텍처 병목(Bottlenecks)과 코드 스멜 리팩토링

설계가 무너지고 있음을 알리는 대표적인 코드 스멜(Code Smells) 징후는 다음과 같습니다.

  • God Class (신적 클래스): 하나의 클래스가 수천 라인에 이르며, DB 처리, 네트워크 통신, UI 로깅 등을 혼자 도맡는 현상입니다. 해결책: SRP를 준수하여 책임별로 클래스를 강제 파편화하십시오.
  • Shotgun Surgery (산탄총 수술): 데이터 모델 컬럼 하나를 고치기 위해 프로젝트 내 수십 개의 파일들을 동시다발적으로 열어 수정해야 하는 결합 구조입니다. 해결책: 강하게 결합된 로직들을 하나의 모듈(캡슐화) 내부로 응집시키고 상호 작용은 추상 인터페이스를 통해서만 수행하도록 차단해야 합니다.

먼저 읽어볼 가이드

검색 유입이 많은 핵심 글부터 이어서 보세요.