트레이트 vs 상속
Coddy PHP 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 91개 중 55번째.
이제 트레이트(traits)가 어떻게 작동하는지 이해하셨으니, 트레이트와 상속(inheritance)을 각각 언제 사용해야 하는지 명확히 해보겠습니다. 둘 다 코드 재사용을 가능하게 하지만, 서로 다른 목적을 수행하며 뚜렷한 특징을 가지고 있습니다.
상속은 "is-a" 관계를 설정합니다. Animal을 확장하는 Dog 클래스는 개가 동물임을 의미합니다. 자식 클래스는 부모의 정체성을 상속받으며 부모 타입이 요구되는 곳 어디에서나 사용될 수 있습니다.
트레이트(Traits)는 "has-a" 기능을 제공합니다. 클래스가 Loggable 트레이트를 사용할 때, 해당 클래스는 로깅 기능을 가집니다. 하지만 클래스 자체가 로거(logger)가 되는 것은 아닙니다. 트레이트는 클래스의 본질을 바꾸지 않으면서 동작을 추가합니다.
<?php
// 상속: Dog는 Animal입니다
class Animal {
public function breathe(): void {
echo "Breathing";
}
}
class Dog extends Animal {
public function bark(): void {
echo "Woof!";
}
}
// 트레이트: Dog는 로깅 기능을 가집니다
trait Loggable {
public function log(string $msg): void {
echo "Log: $msg";
}
}
class Cat extends Animal {
use Loggable;
}
기억해야 할 주요 차이점:
| 측면 | 상속 (Inheritance) | 트레이트 (Traits) |
|---|---|---|
| 관계 | is-a | has-a 기능 |
| 제한 | 단일 부모만 허용 | 다중 트레이트 허용 |
| 타입 검사 | instanceof와 함께 작동 | 타입 정체성 없음 |
| 가장 적합한 경우 | 계층적 관계 | 관련 없는 클래스 간의 공유된 동작 |
클래스가 공통된 정체성을 공유할 때 상속을 사용하세요. 서로 관련 없는 클래스들이 동일한 계층 구조에 속하지 않으면서 동일한 기능이 필요할 때 트레이트(traits)를 사용하세요.
챌린지
쉬움상속과 트레이트(trait)를 언제 사용해야 하는지 보여주는 차량 시스템을 구축해 보겠습니다. 상속을 통해 공통된 정체성을 공유하는 차량 계층 구조를 만들고, 트레이트를 통해 횡단 관심사(cross-cutting capabilities) 기능을 추가할 것입니다.
코드는 다음 네 개의 파일로 구성됩니다:
Trackable.php— GPS 추적 기능을 제공하는Trackable이라는 트레이트를 생성합니다. 이 트레이트는 다음을 포함해야 합니다:- 프라이빗(private) 속성
$location(문자열) - 현재 위치를 저장하는
setLocation(string $location)메서드 "Location: [location]"을 반환하는getLocation()메서드
- 프라이빗(private) 속성
Vehicle.php— "is-a" 관계를 설정하는 기본Vehicle클래스를 생성합니다. 트레이트 파일을 포함하세요. 생성자 프로모션(constructor promotion)을 사용하여 보호된(protected)$brand속성을 정의합니다."Vehicle: [brand]"를 반환하는describe()메서드를 추가합니다. 우리 함대의 모든 차량은 GPS를 가질 것이므로 이 클래스는Trackable트레이트를 사용해야 합니다.Car.php—Vehicle을 상속받는Car클래스를 생성합니다. Vehicle 파일을 포함하세요. 생성자 프로모션을 사용하여 브랜드와 프라이빗$doors(정수) 속성을 받습니다. 브랜드와 함께 부모 생성자를 호출합니다."Car: [brand] with [doors] doors"를 반환하도록describe()메서드를 오버라이드합니다.main.php— Car 파일을 포함하세요. 브랜드 이름, 문 개수, 위치 문자열의 세 가지 입력을 받게 됩니다.Car인스턴스를 생성하고 위치를 설정한 후 다음 두 줄을 출력합니다:describe()호출 결과getLocation()호출 결과
여기서 상속과 트레이트가 어떻게 함께 작동하는지 주목하세요: Car는 Vehicle의 일종(is a)이며(상속은 정체성을 확립함), Vehicle은 추적 기능(has)을 가집니다(트레이트는 동작을 추가함). instanceof Vehicle을 사용하여 어떤 객체가 차량인지 확인할 수 있지만, 확인할 수 있는 "Trackable" 타입은 없습니다. 이것이 이 두 방식의 핵심적인 차이점입니다.
치트 시트
"is-a" 관계에는 상속(inheritance)을 사용하고, "has-a" 기능에는 트레이트(traits)를 사용하세요.
상속은 정체성을 확립합니다. 자식 클래스는 부모 클래스의 한 유형(is a)이며, 부모 타입이 필요한 모든 곳에서 사용될 수 있습니다.
트레이트는 클래스의 정체성을 바꾸지 않고 동작을 추가합니다. 클래스는 기능을 가지고(has) 있지만, 그 자체가 되지는 않습니다.
<?php
// 상속: Dog는 Animal입니다 (IS an Animal)
class Animal {
public function breathe(): void {
echo "Breathing";
}
}
class Dog extends Animal {
public function bark(): void {
echo "Woof!";
}
}
// 트레이트: Cat은 로깅 기능을 가지고 있습니다 (HAS logging capability)
trait Loggable {
public function log(string $msg): void {
echo "Log: $msg";
}
}
class Cat extends Animal {
use Loggable;
}
| 측면 | 상속 | 트레이트 |
|---|---|---|
| 관계 | is-a | has-a 기능 |
| 제한 | 단일 부모만 가능 | 여러 트레이트 허용 |
| 타입 체크 | instanceof와 함께 작동 | 타입 정체성 없음 |
| 용도 | 계층적 관계 | 서로 관련 없는 클래스 간의 공유 동작 |
클래스가 공통된 정체성을 공유하는 계층적 관계에는 상속을 사용하세요. 서로 관련 없는 클래스 간에 공유되는 동작에는 트레이트를 사용하세요.
직접 해보기
<?php
// 메인 파일 - Car 인스턴스 생성 및 테스트
// TODO: Car.php 파일 포함
// 입력 읽기
$brand = trim(fgets(STDIN));
$doors = intval(trim(fgets(STDIN)));
$location = trim(fgets(STDIN));
// TODO: brand와 doors를 사용하여 Car 인스턴스 생성
// TODO: 자동차의 위치 설정
// TODO: describe() 결과 출력
// TODO: getLocation() 결과 출력
?>이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8매직 메서드
매직 메서드 소개__toString 및 __debugInfo__get, __set, __isset, __unset__call 및 __callStatic__clone 및 객체 복제__serialize 및 __unserialize요약 - 커스텀 컬렉션