Composition Over Inheritance

“Composition Over Inheritance” 는 객체지향 프로그래밍에서 코드 재사용과 유연한 설계를 위해 상속보다 컴포지션을 우선적으로 활용하라는 설계 원칙이다. 상속의 단점인 강한 결합도와 클래스 폭발 문제를 해결하고자 한다. 인터페이스와 컴포지션을 통해 런타임 유연성을 제공하며, 인터페이스와 컴포지션을 통해 런타임 유연성을 제공하며, Bridge, Decorator, Strategy 등 다양한 디자인 패턴의 기반이 된다. 특히 다중 상속의 Diamond Problem 을 회피하고 단일 책임 원칙을 지원하여 현대적인 소프트웨어 설계에서 필수적인 원칙이다.

핵심 개념

Composition Over Inheritance (컴포지션 우선 원칙) 은 객체지향 프로그래밍에서 클래스들이 상속을 통한 “is-a” 관계보다는 컴포지션을 통한 “has-a” 관계로 기능을 재사용하고 다형성을 구현해야 한다는 설계 원칙이다.

전략 패턴 (Strategy Pattern), 데코레이터 패턴 (Decorator Pattern) 등 다양한 디자인 패턴에서 이 원칙이 적용된다.

배경

역사적 배경:

문제 상황:

목적 및 필요성

목적설명
유연성 향상런타임에 객체의 행동을 동적으로 변경 가능
재사용성 증대기존 컴포넌트를 조합하여 새로운 기능 구현
결합도 감소클래스 간 의존성을 최소화하여 독립적 개발
테스트 용이성Mock 객체를 이용한 단위 테스트 용이
유지보수성변경 사항이 다른 클래스에 미치는 영향 최소화

주요 원리 및 작동 원리

객체가 필요한 기능 (행동) 을 인터페이스로 정의하고, 해당 기능을 구현한 객체를 조합하여 원하는 기능을 완성한다.

작동 원리 다이어그램

graph TD
    A[Client] --> B[CompositeClass]
    B --> C[Interface1]
    B --> D[Interface2]
    C --> E[Implementation1A]
    C --> F[Implementation1B]
    D --> G[Implementation2A]
    D --> H[Implementation2B]
    
    style A fill:#e1f5fe
    style B fill:#f3e5f5
    style C fill:#e8f5e8
    style D fill:#e8f5e8
    style E fill:#fff3e0
    style F fill:#fff3e0
    style G fill:#fff3e0
    style H fill:#fff3e0

상속 (Inheritance) vs. 컴포지션 (Composition) 비교

상속은 명확한 계층적 관계와 다형성이 필요한 경우에 적합하며, 컴포지션은 유연성, 재사용성, 테스트 용이성이 중요한 현대적 소프트웨어 개발에 더 적합하다. “Composition Over Inheritance” 원칙에 따라 컴포지션을 기본으로 선택하고, 명확한 is-a 관계가 존재하는 경우에만 상속을 사용하는 것이 권장된다.

구분상속 (Inheritance)컴포지션 (Composition)
정의부모 클래스로부터 속성과 메소드를 물려받는 메커니즘다른 객체의 인스턴스를 포함하여 기능을 재사용하는 메커니즘
관계 유형“is-a” 관계 (예: 자동차는 차량이다)“has-a” 관계 (예: 자동차는 엔진을 가진다)
결합도강한 결합 (Tight Coupling)느슨한 결합 (Loose Coupling)
가시성White-box 재사용 (내부 구조 노출)Black-box 재사용 (내부 구조 숨김)

기술적 특성 비교

특성상속 (Inheritance)컴포지션 (Composition)
런타임 유연성컴파일타임에 관계 고정런타임에 동적 변경 가능
다중 기능 조합다중 상속 시 Diamond Problem 발생여러 컴포넌트 자유롭게 조합 가능
인터페이스 노출부모의 모든 public 메소드 상속필요한 인터페이스만 노출
메소드 재정의오버라이딩을 통한 행동 변경델리게이션을 통한 행동 위임
메모리 사용단일 객체 인스턴스여러 객체 인스턴스 (약간의 오버헤드)
성능직접 메소드 호출간접 메소드 호출 (미미한 성능 비용)

설계 관점 비교

설계 측면상속 (Inheritance)컴포지션 (Composition)
SOLID 원칙 준수단일 책임 원칙 위반 가능성 높음단일 책임 원칙 준수 용이
개방 - 폐쇄 원칙부분적 지원 (확장은 쉬우나 수정 위험)완전 지원 (확장 쉽고 수정 불필요)
리스코프 치환 원칙준수 필수 (위반 시 설계 오류)해당 없음 (인터페이스 기반)
캡슐화부모 클래스 세부사항 노출완전한 캡슐화 유지
응집도부모 - 자식 간 강한 응집각 컴포넌트의 독립적 응집

개발 및 유지보수 비교

개발 측면상속 (Inheritance)컴포지션 (Composition)
코드 작성량적음 (자동으로 메소드 상속)많음 (위임 메소드 작성 필요)
초기 개발 속도빠름느림 (인터페이스 설계 필요)
테스트 용이성어려움 (부모 클래스 의존)쉬움 (Mock 객체 활용 가능)
디버깅복잡함 (상속 체인 추적)단순함 (명확한 호출 경로)
리팩토링어려움 (영향 범위 넓음)쉬움 (독립적 컴포넌트)
문서화상속 관계 이해 필요컴포넌트별 독립적 문서화

확장성 및 재사용성 비교

확장성 측면상속 (Inheritance)컴포지션 (Composition)
새 기능 추가상속 체인 수정 또는 새 클래스 생성새 컴포넌트 추가 및 조합
기존 기능 수정모든 하위 클래스에 영향해당 컴포넌트만 수정
코드 재사용수직적 재사용 (계층적)수평적 재사용 (조합적)
기능 조합제한적 (클래스 폭발 위험)자유로운 조합
버전 관리호환성 유지 어려움컴포넌트별 독립적 버전 관리

사용 시나리오 비교

시나리오상속 권장컴포지션 권장이유
명확한 is-a 관계개념적 모델링에 적합
코드 재사용 목적유연한 재사용 구조
다형성 구현둘 다 가능, 컴포지션이 더 유연
기능 확장런타임 확장성
테스트 중심 개발Mock 객체 활용
라이브러리 설계사용자 확장성
프레임워크 설계부분적플러그인 아키텍처
도메인 모델링상황에 따라 선택

성능 비교

성능 측면상속 (Inheritance)컴포지션 (Composition)
메소드 호출 비용직접 호출 (빠름)간접 호출 (약간 느림)
메모리 사용량적음약간 많음 (추가 객체)
객체 생성 비용적음많음 (여러 객체 생성)
캐시 지역성좋음보통
JIT 최적화쉬움보통
실제 성능 영향거의 없음거의 없음 (마이크로 벤치마크 수준)

상속 (Inheritance) vs. 컴포지션 (Composition) 예시

상속 (Inheritance)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 부모 클래스 정의
class Animal:
    def speak(self):
        print("동물이 소리를 냅니다.")

# 자식 클래스가 부모 클래스를 상속
class Dog(Animal):
    def speak(self):
        print("멍멍!")

dog = Dog()
dog.speak()  # 출력: 멍멍!
graph LR
    subgraph "상속 (Inheritance)"
        A1[BaseClass] --> A2[DerivedClass1]
        A1 --> A3[DerivedClass2]
        A2 --> A4[SubDerivedClass]
    end
    
    
    style A1 fill:#ffcdd2
    style A2 fill:#ffcdd2
    style A3 fill:#ffcdd2
    style A4 fill:#ffcdd2
    
컴포지션 (Composition)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 동물의 행동을 별도의 클래스로 분리
class SpeakBehavior:
    def speak(self):
        print("동물이 소리를 냅니다.")

class Dog:
    def __init__(self, speak_behavior):
        self.speak_behavior = speak_behavior

    def speak(self):
        self.speak_behavior.speak()

dog = Dog(SpeakBehavior())
dog.speak()  # 출력: 동물이 소리를 냅니다.
graph LR    
    subgraph "컴포지션 (Composition)"
        B1[MainClass] --> B2[Component1]
        B1 --> B3[Component2]
        B1 --> B4[Component3]
    end

    style B1 fill:#c8e6c9
    style B2 fill:#c8e6c9
    style B3 fill:#c8e6c9
    style B4 fill:#c8e6c9

구조 및 아키텍처

구성 요소

구성 요소기능역할
인터페이스/추상 클래스계약 정의구현체들의 공통 규격 제공
구현 클래스들실제 기능 구현특정 행동의 구체적 구현
컴포지트 클래스객체 조합여러 컴포넌트를 조합하여 새로운 기능 제공
델리게이션 메커니즘작업 위임적절한 컴포넌트에게 작업 전달
팩토리 클래스객체 생성 관리적절한 구현체 선택 및 생성
설정 관리자구성 정보 관리런타임 구성 변경 지원

아키텍처 다이어그램

graph TB
    subgraph "클라이언트 계층"
        Client[클라이언트 코드]
    end
    
    subgraph "조합 계층"
        Composite[컴포지트 클래스]
        Factory[팩토리]
    end
    
    subgraph "인터페이스 계층"
        IFilter[IFilter 인터페이스]
        ILogger[ILogger 인터페이스]
        IHandler[IHandler 인터페이스]
    end
    
    subgraph "구현 계층"
        TextFilter[텍스트 필터]
        LevelFilter[레벨 필터]
        FileLogger[파일 로거]
        ConsoleLogger[콘솔 로거]
        EmailHandler[이메일 핸들러]
        DBHandler[DB 핸들러]
    end
    
    Client --> Composite
    Client --> Factory
    Composite --> IFilter
    Composite --> ILogger
    Composite --> IHandler
    IFilter --> TextFilter
    IFilter --> LevelFilter
    ILogger --> FileLogger
    ILogger --> ConsoleLogger
    IHandler --> EmailHandler
    IHandler --> DBHandler
    Factory --> TextFilter
    Factory --> FileLogger
    Factory --> EmailHandler
    
    style Client fill:#e3f2fd
    style Composite fill:#f3e5f5
    style Factory fill:#f3e5f5
    style IFilter fill:#e8f5e8
    style ILogger fill:#e8f5e8
    style IHandler fill:#e8f5e8
    style TextFilter fill:#fff3e0
    style LevelFilter fill:#fff3e0
    style FileLogger fill:#fff3e0
    style ConsoleLogger fill:#fff3e0
    style EmailHandler fill:#fff3e0
    style DBHandler fill:#fff3e0

구현 기법

기법명정의 및 구성목적예시/시나리오
인터페이스 기반 컴포지션인터페이스로 행동 정의, 구현체 주입행동의 유연한 확장전략 패턴 (Strategy Pattern)
데코레이터 패턴객체에 기능을 동적으로 추가런타임 기능 확장데코레이터 패턴 (Decorator Pattern)
의존성 주입 (DI)외부에서 구현 객체 주입결합도 감소, 테스트 용이성 향상스프링 프레임워크의 DI

장점과 단점

구분항목설명
✅ 장점유연성런타임에 행동 변경 및 확장 가능, 유지보수성 우수
캡슐화객체 내부 구현 숨김, 결합도 낮음
재사용성기능 단위별로 객체 조합, 다양한 조합 가능
테스트 용이성각 컴포넌트 단위 테스트 가능
⚠ 단점구현 복잡성설계가 복잡해질 수 있음, 객체 조합 관리 필요
코드 길이단순한 경우 상속보다 코드가 길어질 수 있음
성능 오버헤드객체 참조 및 위임 (Delegation) 으로 인한 오버헤드 발생 가능

상속의 문제점

합성 우선 상속 원칙이 등장한 이유는 상속이 가진 여러 문제점 때문이다.

  1. 강한 결합도:
    상속은 부모 클래스와 자식 클래스 사이에 강한 결합을 만든다. 부모 클래스의 변경이 모든 자식 클래스에 영향을 미친다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    class Animal {
        void breathe() {
            System.out.println("Breathing");
        }
    
        void eat() {
            System.out.println("Eating normally");
        }
    }
    
    class Dog extends Animal {
        void bark() {
            System.out.println("Woof");
        }
    }
    
    // 나중에 Animal 클래스의 eat() 메서드가 변경됨
    class Animal {
        void breathe() {
            System.out.println("Breathing");
        }
    
        void eat() {
            validateFood(); // 새로운 검증 메서드 추가
            System.out.println("Eating with validation");
        }
    
        private void validateFood() {
            // 검증 로직
        }
    }
    
    • 이 변경은 Dog 클래스의 동작에도 영향을 미친다. Dog 클래스의 개발자는 이러한 변경을 예상하지 못했을 수 있다.
  2. 취약한 베이스 클래스 문제:
    부모 클래스의 변경이 자식 클래스를 망가뜨릴 수 있는 상황을 취약한 베이스 클래스 문제 라고 한다. 이것은 상속의 주요 문제점 중 하나이다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    class Stack {
        protected List<Object> elements = new ArrayList<>();
    
        public void push(Object item) {
            elements.add(item);
        }
    
        public Object pop() {
            if (elements.isEmpty()) {
                throw new EmptyStackException();
            }
            return elements.remove(elements.size() - 1);
        }
    
        public int size() {
            return elements.size();
        }
    }
    
    // Stack을 확장하는 MyStack
    class MyStack extends Stack {
        public void pushAll(Collection<?> items) {
            for (Object item : items) {
                push(item);
            }
        }
    }
    
    // 나중에 Stack 클래스가 변경됨
    class Stack {
        // 변경: elements를 private으로 변경
        private List<Object> elements = new ArrayList<>();
    
        // 나머지 메서드는 동일
    }
    
    • 이 변경으로 인해 MyStack 클래스의 pushAll 메서드는 더 이상 작동하지 않게 된다.
  3. 다중 상속의 제한:
    많은 언어 (Java, C# 등) 는 단일 상속만 지원한다. 이는 여러 소스에서 기능을 상속받아야 할 경우 심각한 제한이 된다.

  4. 계층 구조의 경직성:
    상속은 컴파일 타임에 결정되므로, 런타임에 객체의 행동을 변경하기 어렵다. 이는 설계의 유연성을 제한한다.

도전 과제 및 해결책

분류도전 과제설명해결책
구조적 문제다중 상속 문제 (Diamond Problem)여러 클래스가 같은 상위 클래스를 상속받을 때 메서드 호출이 모호해짐인터페이스 기반 설계, 가상 상속 (Virtual Inheritance), 컴포지션 기반 설계 전환
설계 문제클래스 폭발 (Class Explosion)기능 조합이 많아질수록 클래스 수가 지수적으로 증가런타임 컴포지션 활용, 팩토리 패턴 적용, 설정 기반 동적 구성
코드 품질보일러플레이트 코드위임 메서드, 생성자 코드 등 반복되는 코드가 많아짐Python __getattr__, Kotlin 위임, 코드 생성 도구, 추상화 레벨 조정
설계 복잡성객체 관계 복잡화조합된 객체 간 관계가 많아져 설계 이해와 유지보수 어려움명확한 인터페이스 설계, UML/문서화, 설계 패턴 활용 (예: 전략 패턴, 퍼사드 패턴 등)
운영 문제객체 생성/관리 어려움조합 객체의 수명 주기와 생성 방식 관리가 어려움DI(Dependency Injection) 프레임워크 활용 (Spring, NestJS 등), 생명주기 관리 자동화 도구
성능 문제성능 저하 가능성조합 계층이 깊어질수록 메서드 호출 스택 증가 및 처리 지연 발생필요한 조합만 유지, 병목 부분은 수동 최적화, 캐싱 및 Lazy Initialization 적용
유지보수 문제테스트 복잡성조합된 객체 간 단위 테스트 분리가 어려울 수 있음의존성 분리를 통한 테스트 더블 (Mock/Stub) 활용, 단일 책임 설계

분류에 따른 종류 및 유형

컴포지션 종류

종류설명특징예시
집합 (Aggregation)부분이 전체와 독립적으로 존재약한 관계, 공유 가능대학교 - 학생
합성 (Composition)부분이 전체에 종속적으로 존재강한 관계, 생명주기 동일자동차 - 엔진
연관 (Association)객체 간 사용 관계독립적 존재, 참조 관계사람 - 직업

패턴 유형

유형패턴목적컴포지션 활용 방식
구조적 패턴Adapter, Bridge, Decorator인터페이스 적응 및 확장래핑과 위임
행동 패턴Strategy, Observer, Command알고리즘 및 행동 교체행동 객체 조합
생성 패턴Builder, Abstract Factory복잡한 객체 생성컴포넌트 조립

구현 방식

유형설명
인터페이스 기반 컴포지션인터페이스로 행동 정의, 구현체 주입
객체 참조 기반 컴포지션객체를 멤버 변수로 포함

실무 적용 예시

분야적용 사례컴포지션 활용 방식이점
웹 프레임워크Spring Framework DI, 미들웨어 구성객체 조합, 의존성 주입 (Dependency Injection)느슨한 결합, 테스트 용이성, 유지보수 용이
로깅 시스템Java Logging, Python loggingHandler, Filter, Formatter 의 동적 조합유연한 로그 처리 파이프라인 구성
UI 프레임워크React 컴포넌트 시스템재사용 가능한 컴포넌트의 합성 및 데코레이터 패턴모듈화, 유지보수 용이, 확장성 높음
게임 엔진Unity 의 GameObject-Component 구조GameObject 에 컴포넌트 단위 기능을 조합기능 단위 분리, 동적 조합, 재사용성
데이터베이스 ORMHibernate, JPA 의 연관 매핑 구조Entity 간의 조합 및 위임객체 - 관계 매핑 유연성
마이크로서비스API Gateway, Service Mesh 구성서비스 단위 조합 및 위임확장성, 서비스 독립성, 탄력적 구조
결제 시스템전략 패턴 기반 결제 수단 선택 시스템PaymentStrategy 인터페이스로 동적 조합다양한 결제 방식 유연 대응
웹 요청 처리Express.js, Django 미들웨어 체인 구성요청 처리 흐름을 함수형 컴포넌트로 조합책임 분리, 확장성
프론트엔드 UI 확장데코레이터 패턴 기반 기능 확장 (ex. Tooltip)기본 UI 에 기능을 조합하여 동적으로 확장코드 재사용, 유연한 UI 동작
도메인 객체 설계사용자, 주문, 상품 등의 도메인 조합독립 도메인 객체를 조합하여 기능을 구성높은 응집도, 결합도 낮춤

활용 사례

사례 1: 온라인 쇼핑몰의 결제 시스템

시나리오: 온라인 쇼핑몰의 결제 시스템에 다양한 결제 수단을 지원.
시스템 구성:

구성 다이어그램:

classDiagram
    class PaymentStrategy {
        <<interface>>
        +pay(amount: float): void
    }

    class CreditCardPayment {
        +pay(amount: float): void
    }

    class PayPalPayment {
        +pay(amount: float): void
    }

    class ShoppingCart {
        -paymentStrategy: PaymentStrategy
        +setPaymentStrategy(strategy: PaymentStrategy): void
        +checkout(amount: float): void
    }

    PaymentStrategy <|-- CreditCardPayment
    PaymentStrategy <|-- PayPalPayment
    ShoppingCart --> PaymentStrategy

워크플로우:

  1. 사용자가 결제 수단을 선택한다.
  2. 선택된 결제 수단에 해당하는 PaymentStrategy 구현체가 ShoppingCart 에 주입된다.
  3. ShoppingCart 는 주입된 PaymentStrategy 를 이용하여 pay(amount) 메서드를 호출하고, 해당 구현체의 로직에 따라 결제가 처리된다.
  4. 결제 결과에 따라 후속 처리 (주문 상태 변경, 알림 발송 등) 를 수행한다.

Workflow 다이어그램:

sequenceDiagram
    participant User
    participant ShoppingCart
    participant PaymentStrategy
    participant PayPalPayment
    participant CreditCardPayment

    User->>ShoppingCart: setPaymentStrategy(PayPalPayment)
    User->>ShoppingCart: checkout(100.00)
    ShoppingCart->>PaymentStrategy: pay(100.00)
    PaymentStrategy->>PayPalPayment: pay(100.00)
    PayPalPayment-->>ShoppingCart: 결제 성공
    ShoppingCart-->>User: 주문 완료 알림

담당 역할

구성 요소역할 설명
PaymentStrategy다양한 결제 방식의 공통 인터페이스 제공
CreditCardPayment신용카드 결제 기능 구현
PayPalPayment페이팔 결제 기능 구현
ShoppingCart사용자가 선택한 전략 객체를 조합하여 결제 처리 수행

사례 2: 게임 캐릭터 시스템

시나리오: 캐릭터는 점프, 공격, 방어 등 다양한 능력을 가질 수 있음. 각 능력을 컴포넌트로 구현하고, 캐릭터 객체에 필요한 능력을 조합하여 부여.

시스템 구성 및 다이어그램:

classDiagram
    class Character {
        +addAbility(Ability)
        +useAbility()
    }
    class Ability {
        +execute()
    }
    class JumpAbility {
        +execute()
    }
    class AttackAbility {
        +execute()
    }
    Character --> Ability : has-a
    Ability <|-- JumpAbility
    Ability <|-- AttackAbility

Workflow:

  1. 캐릭터 객체 생성
  2. 점프, 공격 등 능력 (Ability) 컴포넌트 생성
  3. 캐릭터에 필요한 능력 추가
  4. 캐릭터가 상황에 따라 능력 사용

역할:

사례 3: 로깅 시스템 구현

시나리오: 대규모 웹 애플리케이션에서 다양한 레벨의 로그를 여러 출력 대상 (콘솔, 파일, 데이터베이스, 이메일) 으로 전송하고, 각각에 다른 필터링 규칙을 적용해야 하는 상황

시스템 구성:

graph TB
    subgraph "클라이언트 애플리케이션"
        App[Web Application]
    end
    
    subgraph "로깅 시스템"
        Logger[Logger]
        FilterChain[Filter Chain]
        HandlerManager[Handler Manager]
    end
    
    subgraph "필터 컴포넌트"
        LevelFilter[Level Filter]
        TextFilter[Text Filter]
        TimeFilter[Time Filter]
    end
    
    subgraph "핸들러 컴포넌트"
        ConsoleHandler[Console Handler]
        FileHandler[File Handler]
        DBHandler[Database Handler]
        EmailHandler[Email Handler]
    end
    
    subgraph "포맷터 컴포넌트"
        SimpleFormatter[Simple Formatter]
        JSONFormatter[JSON Formatter]
        XMLFormatter[XML Formatter]
    end
    
    App --> Logger
    Logger --> FilterChain
    Logger --> HandlerManager
    FilterChain --> LevelFilter
    FilterChain --> TextFilter
    FilterChain --> TimeFilter
    HandlerManager --> ConsoleHandler
    HandlerManager --> FileHandler
    HandlerManager --> DBHandler
    HandlerManager --> EmailHandler
    ConsoleHandler --> SimpleFormatter
    FileHandler --> JSONFormatter
    DBHandler --> JSONFormatter
    EmailHandler --> XMLFormatter
    
    style App fill:#e3f2fd
    style Logger fill:#f3e5f5
    style FilterChain fill:#f3e5f5
    style HandlerManager fill:#f3e5f5
    style LevelFilter fill:#e8f5e8
    style TextFilter fill:#e8f5e8
    style TimeFilter fill:#e8f5e8
    style ConsoleHandler fill:#fff3e0
    style FileHandler fill:#fff3e0
    style DBHandler fill:#fff3e0
    style EmailHandler fill:#fff3e0

활용 사례 Workflow:

sequenceDiagram
    participant App as Application
    participant Logger as Logger
    participant FC as FilterChain
    participant HM as HandlerManager
    participant CH as ConsoleHandler
    participant FH as FileHandler
    participant EH as EmailHandler
    
    App->>Logger: log("ERROR: Database connection failed")
    Logger->>FC: filter(message)
    FC->>FC: LevelFilter.filter(ERROR)
    FC->>FC: TextFilter.filter("Database")
    FC->>Logger: return true (passed)
    Logger->>HM: handle(message)
    HM->>CH: handle(message)
    CH->>CH: format & output to console
    HM->>FH: handle(message)
    FH->>FH: format & write to file
    HM->>EH: handle(message)
    EH->>EH: format & send email alert
    Logger->>App: return success

컴포지션의 역할:

  1. 유연한 구성:

    • 런타임에 필터와 핸들러 조합 변경 가능
    • 새로운 출력 형식 추가 시 기존 코드 수정 불필요
  2. 독립적 테스트:

    • 각 컴포넌트를 Mock 객체로 대체하여 단위 테스트
    • 통합 테스트에서 실제 구현체 조합
  3. 확장성:

    • 새로운 필터 유형 추가 (예: 사용자별 필터)
    • 새로운 출력 대상 추가 (예: Slack, Teams)

실무에서 효과적으로 적용하기 위한 고려사항 및 주의할 점

구분항목설명권장사항
설계 단계인터페이스 정의기능 단위로 인터페이스를 분리하여 각 객체의 책임을 명확히 함SRP(단일 책임 원칙) 기반 설계, 공통 동작을 추상화하여 전략 객체 설계
책임 분리조합된 객체에 과도한 책임이 몰리는 것을 방지한 객체가 하나의 역할만 수행하도록 분리 (SoC: Separation of Concerns)
설계 복잡성 관리조합 구조가 복잡해지면 이해와 유지보수가 어려워짐설계 문서화, UML 다이어그램 활용, 리뷰 문화 도입
구현 단계객체 수명 주기 관리동적으로 조합되는 객체의 생성/소멸 및 공유 범위 명확히 관리 필요DI(Dependency Injection) 프레임워크 활용, Singleton/Lifecycle 적용
코드 중복 방지위임 메서드 등에서 반복 코드 발생 가능성 존재Python: __getattr__, Kotlin: 위임 키워드, Java: Lombok 사용
성능 관리메소드 호출 오버헤드컴포지션으로 인해 호출 계층이 증가하면서 미세한 성능 저하 발생 가능성능 민감한 로직은 직접 구현 (Flat Delegation), Hot Path 는 단순화
메모리 사용 최적화불필요한 객체 다수 생성 시 메모리 과다 사용 가능Singleton, 객체 풀링, Lazy Initialization 적용
테스트/운영테스트 용이성 확보각 조합 단위 객체에 대한 테스트가 어려울 수 있음DI 기반 Mock 객체 주입, 단위 테스트 시나리오 설계
변경 대응력객체 간 조합 관계가 많을수록 변경에 의한 영향도 증가인터페이스 기반 설계, 버전 관리 전략 도입, Contract 기반 개발 도입
코드 가독성조합된 객체 간의 흐름이 명확하지 않으면 유지보수가 어려움가독성 중심의 설계, 주석 또는 문서화를 통한 의도 전달
  1. 기능 단위 분리와 인터페이스화를 선행하지 않으면 컴포지션의 이점이 퇴색된다.
  2. 조합 대상의 책임이 명확하지 않거나 너무 많을 경우 구조가 오히려 상속보다 더 복잡해진다.
  3. 의존성 주입 없이 직접 객체 생성 시, 객체 수명 및 테스트 통제가 어려워질 수 있다.
  4. 설계 문서와 코드 간 괴리가 생기지 않도록, 초기 설계와 실제 구현을 지속적으로 동기화해야 한다.
  5. 성능 저하 요소(과도한 위임, 깊은 객체 참조 등) 는 반드시 사전 프로파일링과 모니터링으로 검증해야 한다.

최적화하기 위한 고려사항 및 주의할 점

카테고리항목설명권장사항
메서드 호출호출 계층 최소화조합이 깊어질수록 호출 체인이 길어지고 스택 오버헤드 증가 가능핵심 기능만 조합, 불필요한 위임 제거, 인라인 최적화 또는 함수 캐싱 활용
메서드 호출위임 오버헤드위임 방식이 많아질 경우 메서드 호출 체인으로 인한 성능 저하 발생위임은 핵심 로직에 최소화, 중첩 조합 대신 단일 목적 함수로 분리
객체 생성초기화 비용전략 객체나 조합 객체가 복잡한 초기화 과정을 가지면 성능 저하 요인Lazy Initialization(지연 초기화), Singleton 또는 프로토타입 패턴 활용
객체 생성객체 과다 생성조합된 구조에서 반복적으로 객체 생성 시 GC 부담 증가Factory 패턴 활용, 객체 풀링 적용 (특히 I/O, 네트워크 객체 등)
메모리메모리 사용 최적화상태를 유지하는 전략 객체나 중첩 조합은 메모리 사용량을 증가시킬 수 있음Stateless 설계 우선, 약한 참조 (weakref, WeakMap) 활용, 불필요한 참조 제거
런타임 동작Reflection 및 동적 바인딩Reflection, eval, Proxy 등 런타임 해석 기반 조합은 CPU 오버헤드 유발컴파일 타임 검증 가능한 언어 구조 활용 (Java interface, TypeScript 타입 등)
동시성스레드 안전성 확보조합 객체 간 상태 공유 시 동시성 이슈 발생 가능Immutable 객체 설계, 필요한 경우 최소 범위의 동기화 적용
관찰 및 대응성능 병목 진단 및 모니터링조합 구조는 문제 발생 위치 추적이 어려움APM, 프로파일러 도구 활용 (예: Py-Spy, JFR, Chrome DevTools), 병목 로그화

주제와 관련하여 주목할 내용

주제항목설명
함수형 프로그래밍고차 함수 (Higher-Order Functions)함수를 조합하여 새로운 기능 구현
마이크로서비스서비스 컴포지션 패턴여러 서비스를 조합하여 복잡한 비즈니스 로직 구현
리액티브 프로그래밍스트림 컴포지션데이터 스트림을 조합하여 복잡한 데이터 플로우 구성
도메인 주도 설계애그리게이트 패턴도메인 객체들을 조합하여 비즈니스 규칙 구현
클라우드 아키텍처서버리스 컴포지션함수들을 조합하여 애플리케이션 구성
AI/ML파이프라인 컴포지션데이터 전처리, 모델 훈련, 예측을 조합한 ML 파이프라인

하위 주제로 분류해서 추가적으로 학습해야 할 내용

카테고리주제간략한 설명
객체지향 설계 원칙SOLID 원칙객체 지향 프로그래밍의 5 가지 핵심 원칙
디자인 패턴전략 패턴동작을 캡슐화하여 런타임에 교체 가능하게 하는 패턴
디자인 패턴데코레이터 패턴기존 기능을 변경하지 않고 기능을 확장할 수 있는 패턴
테스트 전략테스트 더블 (Mock, Stub 등)외부 의존성을 대체하여 유닛 테스트를 용이하게 함
DI 프레임워크Spring DI / NestJS DI구성 기반 설계의 실제 구현을 돕는 프레임워크
리팩토링 기법상속 → 조합 전환기존 상속 기반 구조를 조합 기반으로 리팩토링하는 기법
동시성 패턴Actor 모델액터들의 조합을 통한 동시성 시스템 구축

관련 분야별 추가 학습 내용

관련 분야주제설명
소프트웨어 아키텍처헥사고날 아키텍처포트와 어댑터를 통한 비즈니스 로직과 외부 시스템 분리
데이터베이스 설계Repository 패턴데이터 액세스 로직의 추상화와 조합
웹 개발미들웨어 패턴HTTP 요청 처리 과정에서 기능들의 조합
게임 개발Component-Entity 시스템게임 객체를 컴포넌트들의 조합으로 구성
DevOpsInfrastructure as Code인프라 구성요소들의 선언적 조합
분산 시스템사가 패턴 (Saga Pattern)분산 트랜잭션을 작은 트랜잭션들의 조합으로 구현

용어 정리

객체지향 설계 핵심 개념

용어설명
컴포지션 (Composition)기능을 객체 간 조합으로 구성하는 방식
상속 (Inheritance)부모 클래스의 속성을 자식 클래스가 물려받는 구조
Delegation (위임)책임을 다른 객체에 위임하여 수행하는 구조
캡슐화 (Encapsulation)객체의 내부 상태를 외부에서 숨기고 인터페이스만 노출
인터페이스 (Interface)기능 명세를 정의하는 추상적 계약
덕 타이핑 (Duck Typing)객체의 타입보다 ’ 행동 ’ 을 기준으로 간주하는 방식 (동적 언어에 많음)
인터페이스 기반 설계구현보다 인터페이스를 중심으로 설계하여 유연성과 결합도 최소화 확보

객체지향 설계 원칙

원칙설명
SOLID객체지향 5 대 원칙 (SRP, OCP, LSP, ISP, DIP) 의 집합적 용어
OCP (Open-Closed Principle)확장에는 열려 있고 변경에는 닫혀 있어야 한다
Liskov Substitution Principle자식 클래스는 부모 클래스를 대체할 수 있어야 한다

디자인 패턴

패턴설명
전략 패턴 (Strategy Pattern)알고리즘을 객체화하고 런타임에 교체 가능하게 하는 패턴
데코레이터 패턴 (Decorator Pattern)기존 객체에 기능을 동적으로 확장하는 패턴
의존성 주입 (Dependency Injection)외부에서 의존 객체를 주입받아 결합도를 낮추는 구조
Mixin다중 클래스에 공통 기능을 주입하기 위한 재사용 기술 (주로 다중 상속 대안)

소프트웨어 품질 속성 (결합도/응집도)

용어설명
결합도 (Coupling)클래스/모듈 간의 의존성 정도
응집도 (Cohesion)클래스/모듈 내부 요소의 목적 집중성
Loose Coupling (느슨한 결합)낮은 결합도를 유지하여 유연한 변경 대응 구조
Tight Coupling (강한 결합)높은 결합도로 인해 변경 전파가 크고 유지보수가 어려운 구조

객체지향 구조 문제 및 한계

문제설명
Diamond Problem다중 상속으로 인한 메서드 충돌 모호성 문제
Fragile Base Class Problem부모 클래스 변경 시 자식 클래스가 예기치 않게 깨지는 현상

성능 및 최적화 관련 기법

기법설명
Lazy Initialization실제 사용할 때까지 객체 생성을 지연시키는 기법
객체 풀링 (Object Pooling)반복 객체 생성을 방지하고 재사용하여 성능 향상
프로파일링 (Profiling)실행 중인 애플리케이션의 성능 병목/자원 사용 분석 도구

참고 및 출처