트레이트 – 05.속성 호환성 규칙

속성 호환성 규칙(Property Compatibility Rule)

트레이트는 상태(state) 처리를 위한 어떤 프로비저닝(provisioning)도 제공하지 않습니다. 코드 중복을 방지하는 것이 주요 목표인 유연한 코드 재사용을 위한 가벼운 메커니즘을 제공하기 위한 것입니다.

(참고) 프로비저닝(provisioning) – 최적인 것을 찾기 위해 시스템 자원을 미리 준비해 놓았다가, 요청이 들어왔을 때 해당 요청에 맞게 즉시 공급하는 절차와 행위를 의미합니다.

트레이트(Trait)는 행위 재사용(behavioral reuse)을 위한 유닛(unit)입니다. 매우 가볍고 스테이트리스(stateless)이며 매우 유연하게 구성할 수 있도록 해줍니다.

(참고) 애플리케이션을 비롯한 모든 항목의 상태(state)란 해당 시점의 상황과 품질, 즉 존재 상태를 말합니다. 상태 유지가 필요 없는 스테이트리스(stateless)와 상태 유지가 필요한 스테이트풀(stateful)로 구별할 수 있습니다.

속성(property)과 관련하여 다음과 같은 핸들링 규칙이 있습니다.

  1. 속성(property)은 정의가 다른 경우 호환되지 않는 것으로 간주됩니다. 호환되지 않는다는 것은 적용된 지정자(modifier)(static, public, protected, private) 또는 초기 값(initial value)이 다르다는 것을 의미합니다.
  2. 호환되지 않는 속성(property)으로 인해 치명적인 오류(Fatal error)가 발생합니다.
  3. 호환이 되는 경우, 즉 정의가 동일할 경우 잠재적으로 문제가 될 수 있는 문제에 대한 인식을 높이고, 속성(property) 사용을 권장하지 않는 E_STRICT 알림이 표시됩니다.
  4. 이러한 검사가 수행될 때, 모든 속성(property)이 동일하게 처리됩니다. 기반 클래스(base class) 및 조합 클래스(composing class)의 속성(property)은 트레이트의 속성(property)과 호환되어야 할 뿐만 아니라 모든 트레이트 간의 속성(property)도 호환되어야 합니다.
  5. 충돌하지 않는 속성(non-coliding property)과 비(非)호환이 아닌 속성(property which are not considered incompatible; 즉, 호환되는 속성)은 조합 클래스(composing class)에서 정의한 것과 완전히 동일하게 동작합니다.

호환성 검증 기준

핸들링 규칙 1번에 호환성을 검증할 때, 호환이 되는지 안되는 지를 구별하는 기준으로 지정자(modifier)와 초기 값(initial value)이 있습니다. 검증 대상이 무엇이냐에 따라 적용 기준(지정자, 초기 값)이 다릅니다.

메소드에서의 초기 값은 메소드 시그니처(method signature)를 의미하며, 메소드 이름, 매개변수 수량과 자료형, 반환 값 자료형(return type)의 조합으로 다른 메소드와 구분할 수 있도록 해주는 유일한 ID입니다.

지정자(modifier) static, public, protected, private 호환성 검증과 관련해서 메소드는 ‘충돌 방지 연산자’, ‘메소드 가시성 변경’, ‘메소드 호환성 적용’ 글을 보시고, 프로퍼티는 ‘트레이트 프로퍼티’ 글을 보시고, 추상 메소드는 ‘추상 트레이트 메소드’를 보시기 바랍니다.

각 요소 별로 호환성 검증하는 규칙이 복잡한 것처럼 보이나, 트레이트의 재사용 메커니즘을 이해하면 약간의 예외를 제외하고는 매우 단순합니다. 예외의 한 예로는 추상 트레이트 메소드의 가시성 지정에 있어서 PHP 8 이전 버전과의 호환성을 유지하기 위하여 일반적인 상속 규칙에서 약간 벗어나게 처리됩니다.

모든 요소에 공통으로 적용되는 개념을 이해하기 위하여, 기반 클래스(base class), 트레이트와 이를 조합한 파생 클래스로 구성된 경우를 살펴보겠습니다.

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

이러한 구성의 클래스는 컴파일할 때 아래와 같은 순서로 진행됩니다.

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

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

위에서 언급된 속성 호환성 규칙은 ①번 단계에서 적용되는 규칙입니다. ①번 단계가 끝나면 속성 호환성 규칙의 임무는 종료되며, ②번 단계로 넘어가면 일반적인 상속 규칙이 적용됩니다.

위의 예제는 단순한 구조이나 ①번 단계의 구조가 조합 트레이트, 다중 트레이트 등을 포함한 여러가지 형태로 복잡해 질 수 있습니다. 그러나 아무리 복잡한 구조라 하더라도 ①번 단계에서 트레이트가 클래스 내로 흡수되어 한 덩어리가 된 단순한 구조의 조합 클래스가 완성됩니다.

조합 클래스 외부와의 관계는 ①번 단계의 컴파일이 끝난 이후에 일어나는 일입니다. 이것은 매우 중요한 개념입니다만 이 글을 보시는 분 중에 일부는 의문을 가질 것 입니다.

핸들링 규칙 4번에 의해 기반 클래스와 트레이트가 호환되지 못하면 문제가 된다고 했습니다. 아래와 같은 경우이지요. 기반 클래스 비(非)정적(non static) 메소드를 트레이트에서 정적(static) 메소드로 재정의하려고 했으니 호환되지 못해서 치명적인 오류가 발생합니다. 사실과 다르게 마치 기반 클래스와 트레이트가 1 대 1의 동등한 입장에서의 관계를 나타내는 것 같이 표현되고 있습니다.

그리고 뒤에서 살펴볼 우선 순위(재정의 순서)에서도 ‘기반클래스 -> 트레이트 -> 조합 클래스’ 순으로 최종적으로는 조합 클래스의 메소드가 남게 됩니다. 순서로 보면 트레이트 메소드가 기반 클래스 메소드를 덮어버리고, 그 다음에 조합 클래스의 메소드가 트레이트의 메소드를 덮어버린다는 설명입니다.

독자의 이해성을 높이기 위해서 PHP 매뉴얼에서 이렇게 설명하다보니, 컴파일이 ‘①번 단계 -> ②번 단계’로 진행되는 것이 아니라 기반 클래스와 트레이트가 먼저 확인되고 그 다음에 조합 클래스로 진행된다고 생각하기 쉽습니다.

이것은 오해입니다. 호환성을 검증할 때 기반 클래스와 트레이트를 비교하는 것이 아닙니다. 먼저 트레이트를 클래스에 조합하고 그 다음에 조합 클래스와 기반 클래스를 비교하는 것입니다.

이를 확인하기 위하여 위의 예제에서 조합 클래스에 sayHello 메소드를 기반 클래스와 호환되도록 새롭게 추가하여 확인해 보시기 바랍니다. 이와 같이 수정한 아래 예제는 호환성 오류 없이 잘 수행됩니다.

분명히 기반 클래스와 트레이트의 메소드 사이에 호환되지 않았는데 기반 클래스는 이를 무시하고(?) 파생 클래스(조합 클래스) MyHelloWorld와 호환성을 검증하여 문제없다고 판단합니다. 즉 컴파일 ①번 단계가 끝나고 나서야 ②번 단계인 조합 클래스 외부와의 관계를 설정하기 시작한다는 것을 알 수 있습니다.

PHP의 트레이트는 컴파일할 때 매우 단순한 복사·붙여넣기 메커니즘에 의해 클래스 정의 내에 포함되도록 동작합니다. 이러한 개념을 가지시고 여기에 있는 글들을 보시면 이해하기가 훨씬 수월할 것입니다.

이러한 메커니즘을 이해하신 후에 핸들링 규칙 4번을 다시 보시기 바랍니다.

  1. 이러한 검사가 수행될 때, 모든 속성(property)이 동일하게 처리됩니다. 기반 클래스(base class) 및 조합 클래스(composing class)의 속성(property)은 트레이트의 속성(property)과 호환되어야 할 뿐만 아니라 모든 트레이트 간의 속성(property)도 호환되어야 합니다.
  1. For those checks, all properties are treated equal. Properties from the base class and the composing class have to be compatible with properties from traits as well as the properties between all traits have to be compatible.

먼저 이해하셔야 되는 것이 이 핸들링 규칙은 트레이트에 대한 규칙을 설명하기 위한 것이므로 설명의 중심에는 기반 클래스도 아니고 조합 클래스도 아니고 트레이트가 되어야 합니다. 트레이트를 중심에 놓고 기술한 문장입니다. ① 트레이트와 기반 클래스 관계 ② 트레이트와 조합 클래스 관계 ③ 트레이트와 트레이트 관계를 설명한 것입니다.

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

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

(참고) “클래스 종속적인 소스 파일 상에 존재하는 실제적인 코드일 뿐”과 같은 트레이트의 설계 개념에는 정적 멤버의 트레이트 로컬 스코프와 같은 약간의 예외 부분들이 존재합니다. 이 점에 대하여는 ‘트레이트 로컬 스코프’ 글에서 살펴보겠습니다.

답글 남기기