로그인 회원가입
모두인포
전체IT경제사회생활스포츠시사여행연애

PHP 상속(inheritance)

lmkfox · 2025-08-29 · 조회 0
PHP 상속(inheritance)
1) 상속이란 무엇인가 - 기존 클래스의 필드/메소드를 물려받아 새 클래스를 만드는 기법입니다. - 공통 로직을 재사용하고, 하위 클래스가 동작을 확장/변경(오버라이드) 하도록 합니다. - 문법: class Child extends Parent { ... } <?php declare(strict_types=1); class Animal { public function speak(): string { return '...'; } } class Dog extends Animal { public function speak(): string { return 'Woof'; } // 오버라이드 } echo (new Dog())->speak(); // "Woof" 2) 생성자와  parent::__construct() - 자식이 생성자를 정의하면 부모 생성자는 자동 호출되지 않습니다. 필요 시 명시 호출. class DbConn { public function __construct(protected PDO $pdo) {} } class UserRepo extends DbConn { public function __construct(PDO $pdo, private string $table = 'users') { parent::__construct($pdo); // 명시 호출 } } 3) 메소드 오버라이딩과 가시성 규칙 - 자식은 부모 메소드를 같은 이름으로 재정의할 수 있습니다. - 가시성은 좁힐 수 없습니다. - 부모 public → 자식도 public만 가능 - 부모 protected → 자식은 protected 또는 public - final 메소드는 오버라이드 불가, final class는 상속 자체가 불가. class Base { public function run(): void {} final public function id(): string { return 'fixed'; } } class Child extends Base { // public function id(): string {} // 오류: final 오버라이드 불가 public function run(): void { /* 확장 */ } } 4) 추상 클래스와 추상 메소드 - 추상 클래스(abstract) 는 직접 인스턴스화 불가, 추상 메소드를 자식이 반드시 구현. abstract class Notifier { abstract public function send(string $msg): void; } class EmailNotifier extends Notifier { public function send(string $msg): void { /* 이메일 전송 */ } } 5) 타입 호환성(공변/반공변) - PHP는 반환 타입 공변(covariant), 매개변수 타입 반공변(contravariant) 을 지원합니다. - 자식 메소드의 반환 타입을 더 구체적으로 할 수 있음. - 자식 메소드의 매개변수 타입을 더 넓게 받을 수 있음. class Animal {} class Dog extends Animal {} class Shelter { public function adopt(): Animal { return new Animal(); } public function register(Animal $a): void {} } class DogShelter extends Shelter { // 반환 타입 공변: Animal → Dog (더 구체적) public function adopt(): Dog { return new Dog(); } // 매개변수 반공변: Animal보다 넓힐 수 있지만 이미 최상위라 동일 유지 public function register(Animal $a): void {} } 6) 다형성(Polymorphism) 예제 abstract class Shape { abstract public function area(): float; } class Rectangle extends Shape { public function __construct(private float $w, private float $h) {} public function area(): float { return $this->w * $this->h; } } class Circle extends Shape { public function __construct(private float $r) {} public function area(): float { return M_PI * $this->r * $this->r; } } /** @param list<Shape> $shapes */ function totalArea(array $shapes): float { $sum = 0.0; foreach ($shapes as $s) $sum += $s->area(); // 같은 메시지, 다른 동작 return $sum; } echo totalArea([new Rectangle(3,4), new Circle(2)]); // 다형성 7) 정적 멤버와 늦은 정적 바인딩( self  vs  static  vs  parent ) - self:: 는 정의된 클래스 기준 고정. - static:: 은 호출한 실제 클래스 기준(늦은 정적 바인딩). 상속 구조에서 팩토리 메소드에 유용. - parent:: 는 부모 구현을 호출. class Base { public static function make(): static { // 8.0+: static 반환 타입 return new static(); } public function who(): string { return self::class; } } class Sub extends Base { public function who(): string { return static::class; } } echo get_class(Base::make()); // Base echo get_class(Sub::make()); // Sub 8) 클래스 상수 상속과  final  상수 - 자식은 부모와 동일 이름의 상수를 재정의할 수 있습니다. - PHP 8.1+에서는 final 클래스 상수를 선언해 재정의를 막을 수 있습니다. class A { public const T = 'base'; final public const VERSION = '1.0'; // 8.1+ } class B extends A { public const T = 'child'; // 허용: 재정의 // public const VERSION = 'X'; // 오류: final 상수 } 9) 프로퍼티 상속 주의 - 부모의 프로퍼티는 그대로 상속됩니다. - 같은 이름의 프로퍼티를 자식에서 다시 선언하는 것은 혼동을 부르고 타입/가시성 호환성 문제가 날 수 있으니 피하세요. - 공통 상태는 부모에 두고, 동작만 자식에서 확장하는 편이 안전합니다. 10) 접근 제어자 요약 - public 어디서나 접근 - protected 자신/자식에서만 - private 해당 클래스 내부만 - 오버라이드 시 가시성 축소 금지 규칙을 꼭 지키세요. 11) 트레이트/인터페이스와의 관계 - 트레이트: 코드 조각 재사용(다중 상속 대체 수단). 상속과는 별개. 충돌 시 insteadof, as로 조정 가능. - 인터페이스: “이 메소드들을 제공한다”는 계약. 상속 대신 다형성의 표준화에 사용. 실제로는 “추상 클래스(공통 구현) + 인터페이스(계약)” 조합이 실무에서 가장 유연합니다. 12) 언제 상속을 쓰고, 언제 조합(composition)을 쓸까 - 상속이 어울리는 경우 - “is-a” 관계가 명확하고, 상위 타입의 계약이 안정적일 때 - 공통 동작을 템플릿 메소드 패턴으로 공유하고 일부만 바꾸고 싶을 때 - 조합(컴포지션) 이 나은 경우 - 기능을 교체/확장해야 할 가능성이 클 때 (런타임에 전략 교체 등) - 상위 타입 변경이 잦아 하위 타입들이 깨질 위험이 있을 때 - 단일 책임을 유지하고 싶을 때 // 상속 대신 전략(Strategy) 조합 예시 interface PriceRule { public function apply(int $price): int; } final class TenPercentOff implements PriceRule { public function apply(int $price): int { return (int)round($price * 0.9); } } final class Cart { public function __construct(private PriceRule $rule) {} public function checkout(int $price): int { return $this->rule->apply($price); } } 13) 자주 하는 실수와 피해야 할 패턴 - 생성자에서 오버라이드 가능한 메소드 호출 자식 초기화 이전에 자식 메소드가 실행돼 버그 유발. - 너무 깊은 상속 트리 변경 비용 급증. 2~3단계 이내 권장. - 부모의 private 멤버를 자식에서 “덮어쓴다”는 오해 부모·자식 각각 별개 멤버입니다. 혼동 방지를 위해 동일 이름 재선언 지양. - 공유 상태를 static으로 남용 테스트·동시성 문제. 필요한 곳에서만 신중하게 사용. 14) 체크리스트(요약) - 상속은 공통 계약 + 확장 지점을 명확히 설계했을 때만 사용. - 오버라이드 시 가시성 축소 금지, 타입 호환성(반환 공변/매개변수 반공변) 준수. - 생성자 체인은 parent::__construct() 를 명시적으로 연결. - 공통 로직은 추상 클래스/트레이트, 계약은 인터페이스로 분리. - 가능하면 조합(컴포지션)으로 유연성 확보.

댓글

아직 댓글이 없습니다.

로그인 후 댓글을 남길 수 있습니다.