트레이트 – 14.메소드 호환성 적용

기반 클래스(base class), 트레이트와 이를 조합한 파생 클래스로 구성된 경우를 살펴보겠습니다.

기반 클래스, 트레이트, 파생 클래스(조합 클래스)로 구성된 경우

기반 클래스와 파생 클래스 간의 속성 호환성은 일반적인 상속 규칙에 따라 적용되고 있었으며, 이는 기반 클래스와 크레이트 간의 속성 호환성 규칙과 같음을 실험적으로 확인할 수 있었습니다.

호환성 규칙이 적용되는 경우를 살펴볼 때, 아래와 같이 호환이 되는 경우, 즉 정의가 동일할 경우에는 치명적인 오류(Fatal error) 없이 실행됩니다.

그러나 아래는 ‘속성 호환성 규칙’ 글의 핸들링 규칙 2, 4번에서 언급된 호환되지 않는 속성으로 치명적인 오류가 발생하는 경우를 보여줍니다.

이와 같이 호환되지 않으면, 즉 기반 클래스(base class) 및 조합 클래스(composing class)의 속성(property)이 트레이트의 메소드에 적용된 지정자(modifier)(static, public, protected, private) 또는 초기 값(initial value)과 서로 다르게 되면 치명적인 오류(Fatal error)가 발생합니다.

여기서 가시성과 관련하여 한 가지 주의할 점이 발생하는데, 기반 클래스 메소드의 가시성은 트레이트 또는 조합 클래스에서 그 수준을 증가시키는 것을 허용하고 있습니다. 그러나 위의 예제처럼 기반 클래스에서 public인 메소드를 트레이트에서 private로 가시성 수준을 감소시켜서 재정의 할 수는 없습니다.

이것은 앞에서 설명드린 바와 같이 일반적인 상속 규칙에 따라 처리되는 것으로, 객체 상속에서의 가시성 변경을 보면, 가시성 수준을 증가시킬 수는 있으나 감소시킬 수는 없습니다.

‘메소드 가시성 변경’ 글에서 설명 드리지만, as 연산자를 이용하여 트레이트 가시성을 변경할 때는 가시성 수준을 증감 모두 할 수 있도록 한 것과 비교해서 혼동할 수 있을 것 같은데, 이는 컴파일 할 때 MyHelloWorld가 어떤 순서로 구성되어지는 지를 보시면 됩니다.

클래스 MyHelloWorld 컴파일할 때 구성 순서

우선 ①번 단계로, 트레이트가 MyHelloWorld 클래스에 조합되어 한 덩어리(클래스 내에 포함)가 됩니다. 그 다음 ②번 단계로, 한 덩어리가 된 MyHelloWorld가 기반 클래스 Hello를 상속 받습니다.

결국 ①번 단계에서는 트레이트의 플래트닝 속성의 적용을 받아 MyHelloWorld 클래스가 구성되며, ②번 단계에서는 일반적인 상속 규칙에 따라 MyHelloWorld 클래스가 최종적으로 구성되어 집니다.

이러한 컴파일할 때 구성 순서를 확인하기 위하여 아래와 같이 위 예제의 트레이트에 있는 메소드를 전부 MyHelloWorld 클래스로 옮겨서 실험해 보면, 발생되는 치명적인 오류가 sayHello1, sayHello2, sayHello3 메소드 모두에서 동일하게 나타납니다.

sayHello1, sayHello2, sayHello3 메소드에서 발생하는 치명적인 오류 메시지를 트레이트에 있을 때와 클래스에 있을 때를 비교하면, 트레이트 이름과 클래스 이름을 제외하고는 아래와 같이 동일하다는 것을 확인할 수 있습니다.

sayHello1 메소드에서 발생하는 치명적인 오류 메시지는,

  • Fatal error: Access level to World::sayHello1() must be public (as in class Hello) ……
  • Fatal error: Access level to MyHelloWorld::sayHello1() must be public (as in class Hello) ……

sayHello2 메소드에서 발생하는 치명적인 오류 메시지는,

  • Fatal error: Cannot make static method Hello::sayHello2() non static in class World ……
  • Fatal error: Cannot make static method Hello::sayHello2() non static in class MyHelloWorld ……

sayHello3 메소드에서 발생하는 치명적인 오류 메시지는,

  • Fatal error: Declaration of World::sayHello3($a) must be compatible with Hello::sayHello3() ……
  • Fatal error: Declaration of MyHelloWorld::sayHello3($a) must be compatible with Hello::sayHello3() ……

이러한 결과를 비추어봤을 때, 트레이트와 Hello 기반 클래스의 속성 호환성 규칙을 적용할 때는 이들 둘을 직접 비교하는 것이 아니라, 트레이트를 조합한 MyHelloWorld 클래스와 Hello 기반 클래스 사이의 속성 호환성을 비교하는 것임을 알 수 있습니다.

트레이트와 조합 클래스 사이에 적용되는 규칙

앞 글 ‘속성 호환성 규칙’에서 설명 드린 바와 같이(아래), 트레이트와 조합 클래스 사이에는 트레이트의 속성 호환성 규칙이 적용되지 않으며, 복사·붙여넣기 메커니즘에 의해 단순히 코드가 클래스에 재배치 됩니다.

트레이트는 프로그램 내에서 인스턴스도 생성할 수 없는 클래스 종속적인 소스 파일 상에 존재하는 실제적인 코드일 뿐입니다. 트레이트는 소스 파일 상 조합 클래스에 속한 코드로서만 존재 가치가 있는 것입니다. 따라서 ① 트레이트와 기반 클래스 관계에서 트레이트는 조합 클래스를 통해서만 기반 클래스와 관계를 설정할 수 있는 것입니다.

② 트레이트와 조합 클래스 관계는 어려울 것이 없으나, 이 사이의 관계는 호환성 문제보다는 단순한 복사·붙여넣기 메커니즘이 강력히 작용하고 있으며, ③ 트레이트와 트레이트 관계는 호환성 검증에 앞서 충돌 방지 메커니즘이 또한 강력히 작용하고 있어서 호환성 검증의 의미가 애매합니다.

이를 확인하기 위하여, 맨 위에 있는 예제에서 Hello 기반 클래스에 있는 메소드를 트레이트로 옮기고, 트레이트에 있는 메소드를 조합 클래스로 옮겨서 실험해 보았습니다.

트레이트와 MyHelloWorld 클래스에 있는 sayHello1, sayHello2, sayHello3 메소드들은 상호간에 ‘속성 호환성 규칙’에 벗어나고 있으나, 이러한 메소드들이 MyHelloWorld 클래스에 조합될 때는 어떤 오류도 발생하지 않습니다.

트레이트와 조합 클래스 사이에는 지정자(static, public, protected, private)와 반환 값의 자료형을 포함한 시그니처가 어떻게 달라지든 상관없이 조합 클래스의 메소드가 트레이트의 메소드를 재정의(overriding)해 버립니다. 트레이트에서 요구하는 ‘속성 호환성 규칙’이 적용되지 않는 사이입니다.

_construct 생성자에서 발생하는 오류

PHP 5.4.0부터 5.5 초기 버전까지 살펴 보면, 트레이트와 기반 클래스에 생성자가 있고 조합 클래스에 생성자가 없는 경우에는 매개 변수의 유무와 관계없이 치명적인 오류가 발생합니다.

  • Fatal error: MyHelloWorld has colliding constructor definitions coming from traits

이 외의 경우에는 문제가 없었으며, 이러한 문제는 PHP 5.5 후기 버전에서 수정되어 이 후에는 오류가 발생하지 않습니다.

기반 클래스 트레이트 조합 클래스 실행 결과
X X X 오류 없음
O X X 오류 없음
X X O 오류 없음
O X O 오류 없음
X O X 오류 없음
O O X 치명적인 오류 발생
X O O 오류 없음
O O O 오류 없음

O: 생성자 있음, X: 생성자 없음

<PHP 5.5 초기 버전까지 발생하던 생성자 관련 오류>

답글 남기기