Menu
Coddy logo textTech

합성 vs 상속

Coddy PHP 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 91개 중 56번째.

이전 장에서 여러분은 트레이트(traits)가 "has-a" 기능을 제공하는 반면, 상속(inheritance)은 "is-a" 관계를 설정한다는 것을 배웠습니다. 컴포지션(Composition)은 복잡한 객체를 더 단순한 객체들로 구성하고, 이를 상속받는 대신 속성(properties)으로 저장함으로써 "has-a" 개념을 한 단계 더 발전시킵니다.

상속을 사용하면, CarVehicle을 확장할 수 있습니다. 합성을 사용하면, CarEngine 객체를 포함합니다:

<?php
class Engine {
    public function start(): string {
        return "Engine started";
    }
}

class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine();
    }
    
    public function start(): string {
        return $this->engine->start();
    }
}

$car = new Car();
echo $car->start();

출력:

Engine started

Car 클래스는 Engine으로부터 상속받지 않습니다 - 대신 엔진을 소유합니다. 이러한 접근 방식은 상당한 이점을 제공합니다: 엔진을 다른 구현체로 교체할 수 있고, 클래스 간의 결합도가 낮게 유지되며, 깊은 상속 구조가 만드는 경직된 계층 구조를 피할 수 있습니다.

OOP에서 흔히 권장되는 지침은 "상속보다는 구성을 지향하라(favor composition over inheritance)."입니다. 상속은 진정한 타입 계층 구조(Dog는 실제로 Animal의 일종인 경우)에는 잘 작동하지만, 독립적인 부분들로부터 기능을 조립할 때는 구성이 빛을 발합니다. 여러 객체를 결합하고, 런타임에 이를 교체하며, 컴포넌트를 독립적으로 테스트할 수 있습니다.

다음 레슨에서는 객체가 의존성을 받는 방식을 제어함으로써 컴포지션(composition)을 직접적으로 기반으로 하는 의존성 주입(dependency injection)에 대해 알아봅니다.

challenge icon

챌린지

쉬움

상속을 사용하는 대신 별도의 컴포넌트 객체들로 Computer를 조립하는 컴포지션(Composition)을 사용하여 컴퓨터 시스템을 구축해 보겠습니다. 이는 컴포지션이 어떻게 더 단순하고 독립적인 부분들로부터 복잡한 객체를 만들 수 있게 해주는지 보여줍니다.

코드를 세 개의 파일로 구성합니다:

  • Processor.php — CPU를 나타내는 Processor 클래스를 생성합니다. 생성자 프로모션(constructor promotion)을 사용하여 private $model (string)과 private $cores (integer)를 정의하세요. "[model] ([cores] cores)"를 반환하는 getSpecs() 메서드를 추가하세요.
  • Memory.php — RAM을 나타내는 Memory 클래스를 생성합니다. 생성자 프로모션(constructor promotion)을 사용하여 private $size (GB를 나타내는 integer)를 정의하세요. "[size]GB RAM"을 반환하는 getCapacity() 메서드를 추가하세요.
  • main.php — 두 컴포넌트 파일을 포함하고 컴포지션을 사용하는 Computer 클래스를 생성합니다. Computer는 다음을 수행해야 합니다:
    • Processor 객체와 Memory 객체를 private 속성으로 저장합니다.
    • 생성자에서 프로세서 모델, 코어 수, 메모리 크기를 인자로 받습니다.
    • 생성 과정 중에 내부적으로 컴포넌트 객체들을 생성합니다.
    • "Computer with [processor specs] and [memory capacity]"를 반환하는 describe() 메서드를 가집니다.

    프로세서 모델, 코어 수, GB 단위의 메모리 크기라는 세 가지 입력을 받게 됩니다. 이 사양으로 Computer를 생성하고 describe()를 호출한 결과를 출력하세요.

ComputerProcessorMemory를 확장(extend)하지 않고, 그것들을 포함(contains)하고 있다는 점에 주목하세요. 컴포지션을 통한 이러한 "has-a" 관계는 각 컴포넌트를 독립적이고 재사용 가능하게 유지하면서, Computer가 필요할 때 각 부품에 작업을 위임할 수 있도록 합니다.

치트 시트

컴포지션(Composition)은 복잡한 객체를 상속받는 대신 속성으로 저장하여 더 단순한 객체들로부터 구축합니다. 이는 한 클래스가 다른 클래스의 인스턴스를 포함하는 "has-a" 관계를 생성합니다.

기본적인 컴포지션 예시:

<?php
class Engine {
    public function start(): string {
        return "Engine started";
    }
}

class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine();
    }
    
    public function start(): string {
        return $this->engine->start();
    }
}

Car 클래스는 Engine으로부터 상속받지 않고, 엔진을 소유합니다. 이는 클래스 간의 결합도를 낮게 유지하고 경직된 상속 계층 구조를 피하게 해줍니다.

상속보다는 컴포지션을 지향하세요: 상속은 진정한 타입 계층 구조(DogAnimal이다)에는 잘 작동하지만, 독립적인 부품들로부터 기능을 조립할 때는 컴포지션이 빛을 발합니다. 여러 객체를 결합하거나, 런타임에 객체를 교체하고, 컴포넌트를 개별적으로 테스트할 수 있습니다.

직접 해보기

<?php
require_once 'Processor.php';
require_once 'Memory.php';

class Computer
{
    // TODO: Processor 및 Memory 객체를 저장할 private 속성을 정의합니다.

    // TODO: 생성자는 프로세서 모델, 코어 수, 메모리 크기를 인자로 받아야 하며
    // 내부적으로 컴포넌트 객체들을 생성해야 합니다.
    public function __construct()
    {
        // TODO: Processor 및 Memory 객체를 생성하고 저장합니다.
    }

    // TODO: "Computer with [processor specs] and [memory capacity]"를
    // 반환하는 describe() 메서드를 구현합니다.
    public function describe(): string
    {
        // TODO: 컴포넌트 객체에 위임하고 형식화된 문자열을 반환합니다.
    }
}

// 입력 읽기
$processorModel = trim(fgets(STDIN));
$cores = intval(fgets(STDIN));
$memorySize = intval(fgets(STDIN));

// TODO: 주어진 사양으로 Computer를 생성하고 describe()의 결과를 출력합니다.

?>
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨