상속에 대한 구성

Composition over inheritance
이 도표는 동물의 파리와 건전한 행동을 어떻게 상속설계원칙보다 구성을 사용하여 유연하게 설계할 수 있는지를 보여준다.[1]

객체지향적 프로그래밍(OOP)에서 상속(또는 복합재활용 원칙)에 대한 구성은 기본 또는 상위 계층에서 상속하는 것이 아니라 클래스가 구성(원하는 기능성을 구현하는 다른 클래스의 인스턴스를 포함함)에 의해 다형적 행동과 코드 재사용을 달성해야 한다는 원칙이다.[2]이것은 영향력 있는 책 디자인 패턴(1994)에서와 같이 OOP의 자주 언급되는 원칙이다.[3]

기본 사항

상속보다 구성의 구현은 일반적으로 시스템이 보여야 하는 행동을 나타내는 다양한 인터페이스를 만드는 것으로 시작한다.인터페이스는 다형성 행동을 가능하게 한다.식별된 인터페이스를 구현하는 클래스가 구축되어 필요에 따라 비즈니스 도메인 클래스에 추가된다.따라서 시스템 행동은 상속 없이 실현된다.

사실 비즈니스 도메인 클래스는 상속이 전혀 없는 기본 클래스일 수 있다.원하는 동작 인터페이스를 구현하는 다른 클래스를 제공하여 시스템 동작의 대체 구현을 달성한다.인터페이스에 대한 참조를 포함하는 클래스는 인터페이스 구현을 지원할 수 있다. 즉, 실행 시간까지 지연될 수 있는 선택이다.

상속

C++의 예는 다음과 같다.

계급의 오브젝트 { 공중의:     가상의 공허하게 하다 갱신하다() {         // no-op     }      가상의 공허하게 하다 그림그리다() {         // no-op     }      가상의 공허하게 하다 충돌하다(오브젝트 물건들[]) {         // no-op     } };  계급의 볼 수 있다 : 공중의 오브젝트 {     모델* 본을 뜨다;  공중의:     가상의 공허하게 하다 그림그리다() 무효로 하다 {         // 이 물체의 위치에 모델을 그리기 위한 코드     } };  계급의 고체 : 공중의 오브젝트 { 공중의:     가상의 공허하게 하다 충돌하다(오브젝트 물건들[]) 무효로 하다 {         // 다른 물체와의 충돌에 대한 확인 및 대응 코드     } };  계급의 가동성 : 공중의 오브젝트 { 공중의:     가상의 공허하게 하다 갱신하다() 무효로 하다 {         // 이 개체의 위치를 업데이트하는 코드     } }; 

그렇다면 다음과 같은 구체적인 수업도 있다고 가정합시다.

  • 계급의Player- 어느 것이Solid,Movable그리고Visible
  • 계급의Cloud- 어느 것이Movable그리고Visible, 그러나 아니다.Solid
  • 계급의Building- 어느 것이Solid그리고Visible, 그러나 아니다.Movable
  • 계급의Trap- 어느 것이Solid, 그러나 둘 다 아니다.Visible아닌Movable

다중 상속은 다이아몬드 문제로 이어질 수 있으므로 신중하게 실행하지 않으면 위험하다는 점에 유의하십시오.이를 피하는 한 가지 해결책은 다음과 같은 클래스를 만드는 것이다.VisibleAndSolid,VisibleAndMovable,VisibleAndSolidAndMovable필요한 모든 조합에 대해 , 등, 비록 이것이 많은 양의 반복 코드로 이어지지만.C++는 가상 상속을 허용함으로써 다중 상속의 다이아몬드 문제를 해결한다.

구성 및 인터페이스

이 절의 C++ 예는 코드 재사용과 다형성을 달성하기 위해 구성과 인터페이스를 사용하는 원리를 설명한다.인터페이스를 선언하는 전용 키워드가 없는 C++ 언어 때문에, 다음 C++ 예는 "순수한 추상적 베이스 클래스로부터의 상속"을 사용한다.대부분의 경우, 이것은 기능적으로 자바와 C#와 같은 다른 언어로 제공되는 인터페이스와 동등하다.

명명된 추상 클래스를 소개VisibilityDelegate, 하위 클래스 포함NotVisible그리고Visible, 객체를 그리는 수단을 제공한다.

계급의 VisibilityDelegate { 공중의:     가상의 공허하게 하다 그림그리다() = 0; };  계급의 볼 수 없음 : 공중의 VisibilityDelegate { 공중의:     가상의 공허하게 하다 그림그리다() 무효로 하다 {         // no-op     } };  계급의 볼 수 있다 : 공중의 VisibilityDelegate { 공중의:     가상의 공허하게 하다 그림그리다() 무효로 하다 {         // 이 물체의 위치에 모델을 그리기 위한 코드     } }; 

명명된 추상 클래스를 소개UpdateDelegate, 하위 클래스 포함NotMovable그리고Movable, 물체를 이동하는 수단을 제공한다.

계급의 UpdateDelegate { 공중의:     가상의 공허하게 하다 갱신하다() = 0; };  계급의 NotMovable : 공중의 UpdateDelegate { 공중의:     가상의 공허하게 하다 갱신하다() 무효로 하다 {         // no-op     } };  계급의 가동성 : 공중의 UpdateDelegate { 공중의:     가상의 공허하게 하다 갱신하다() 무효로 하다 {         // 이 개체의 위치를 업데이트하는 코드     } }; 

명명된 추상 클래스를 소개CollisionDelegate, 하위 클래스 포함NotSolid그리고Solid, 물체와 충돌하는 수단을 제공한다.

계급의 콜러레이블데레게이트 { 공중의:     가상의 공허하게 하다 충돌하다(오브젝트 물건들[]) = 0; };  계급의 NotSolid : 공중의 콜러레이블데레게이트 { 공중의:     가상의 공허하게 하다 충돌하다(오브젝트 물건들[]) 무효로 하다 {         // no-op     } };  계급의 고체 : 공중의 콜러레이블데레게이트 { 공중의:     가상의 공허하게 하다 충돌하다(오브젝트 물건들[]) 무효로 하다 {         // 다른 물체와의 충돌에 대한 확인 및 대응 코드     } }; 

마지막으로, 이름붙인 수업을 소개한다.Object가시성을 제어하기 위해 구성원과 함께(사용:VisibilityDelegate)), 이동 가능성(사용UpdateDelegate) 및 견고성(a 사용)CollisionDelegate이 세분류는 회원에게 위임하는 방법을 가지고 있다(예:update()간단히 …의 방법을 호출하다.UpdateDelegate:

계급의 오브젝트 {     VisibilityDelegate* _v;     UpdateDelegate* u;     콜러레이블데레게이트* _c;  공중의:     오브젝트(VisibilityDelegate* v, UpdateDelegate* u, 콜러레이블데레게이트* c)         : _v(v)         , u(u)         , _c(c)     {}      공허하게 하다 갱신하다() {         u->갱신하다();     }      공허하게 하다 그림그리다() {         _v->그림그리다();     }      공허하게 하다 충돌하다(오브젝트 물건들[]) {         _c->충돌하다(물건들);     } }; 

그러면 구체적인 수업은 다음과 같이 보일 것이다.

계급의 플레이어 : 공중의 오브젝트 { 공중의:     플레이어()         : 오브젝트(새로운 볼 수 있다(), 새로운 가동성(), 새로운 고체())     {}      // ... };  계급의 연기 : 공중의 오브젝트 { 공중의:     연기()         : 오브젝트(새로운 볼 수 있다(), 새로운 가동성(), 새로운 NotSolid())     {}      // ... }; 

혜택들

상속보다 구성을 선호하는 것은 설계에 더 높은 유연성을 부여하는 설계원리다.다양한 구성 요소에서 비즈니스 도메인 클래스를 만드는 것이 그들 사이의 공통점을 찾으려 애쓰는 것보다 더 자연스럽다.예를 들어, 가속 페달과 스티어링 휠은 거의 공통적인 특성을 공유하지 않지만, 두 가지 모두 자동차에서 중요한 구성 요소들이다.그들이 할 수 있는 것과 그들이 자동차에 이득이 되는 데 어떻게 사용될 수 있는지는 쉽게 정의된다.구성 또한 가족 구성원의 기이함에 덜 취약하기 때문에 장기적으로 보다 안정적인 사업영역을 제공한다.즉, 물체가 할 수 있는 것(HAS-A)을 확장하는 것(IS-A)보다 구성하는 것이 낫다.[1]

초기 설계는 상속을 통해 비즈니스-도메인 계층 간 행태를 분산하기 위한 계층적 관계를 형성하는 대신 별도의 인터페이스에서 시스템 객체 행동을 식별함으로써 단순화된다.이 접근법은 미래 요구사항의 변경을 더 쉽게 수용하며 그렇지 않으면 상속모형의 사업영역 세분류에 대한 완전한 구조조정이 필요하다.또한 여러 세대를 포함하는 상속 기반 모델의 비교적 사소한 변경과 관련된 문제를 방지한다.구성 관계는 런타임에 변경될 수 있으므로 유연성이 높은 반면, 하위 형식 관계는 정적이며 여러 언어로 재컴파일해야 한다.

특히[4] 바둑과 러스트[citation needed] 같은 몇몇 언어들은 오로지 활자 구성을 사용한다.

단점

상속 대신 구성을 사용하는 것의 한 가지 일반적인 단점은 개별 구성요소에 의해 제공되는 방법이 전달 방법일 뿐일지라도 파생 유형으로 구현되어야 할 수 있다는 것이다(이는 대부분의 프로그래밍 언어에서는 사실이지만 전부는 아니다; 단점 방지 참조).이와는 대조적으로, 상속은 모든 기본 클래스의 방법을 파생 클래스 내에서 재실행하도록 요구하지 않는다.오히려 파생 클래스는 기본 클래스 방법과 다른 동작을 갖는 방법만 구현(오버라이드)하면 된다.이것은 기본 클래스가 기본 동작을 제공하는 많은 방법을 포함하고 그 중 몇 가지 방법만 파생 클래스 내에서 재정의하면 되는 경우 프로그래밍 작업량이 현저히 감소할 수 있다.

예를 들어, 아래 C# 코드에서, 변수와 방법은Employee기본 클래스는 에 의해 상속됨HourlyEmployee그리고SalariedEmployee파생 하위 클래스오직 더Pay()각 파생 서브클래스별로 방법을 구현(전문)할 필요가 있다.다른 방법은 기본 클래스 자체에 의해 구현되며, 파생된 모든 하위 클래스에 의해 공유된다. 하위 클래스 정의에서 다시 구현(오버라이드)하거나 언급할 필요가 없다.

// 기본 클래스 공중의 추상적 계급의 직원 {     // 속성     보호받는 끈을 매다 이름 { 얻다; 세트; }     보호받는 인트로 아이디 { 얻다; 세트; }     보호받는 십진법의 페이레이트 { 얻다; 세트; }     보호받는 인트로 작업 시간 { 얻다; }      // 현재 급여 기간에 대한 급여 지급     공중의 추상적 십진법의 지불하다(); }  // 파생 하위 클래스 공중의 계급의 매시간 직원 : 직원 {     // 현재 급여 기간에 대한 급여 지급     공중의 무효로 하다 십진법의 지불하다()     {         // 작업 시간(시간)         돌아오다 작업 시간 * 페이레이트;     } }  // 파생 하위 클래스 공중의 계급의 샐러리맨 : 직원 {     // 현재 급여 기간에 대한 급여 지급     공중의 무효로 하다 십진법의 지불하다()     {         // 급여율은 시간당 급여가 아닌 연봉이다.         돌아오다 작업 시간 * 페이레이트 / 2087;     } } 

결점 방지

이러한 단점은 특성, 혼합물, (유형) 내장 또는 프로토콜 확장을 사용하여 방지할 수 있다.

일부 언어는 이를 완화하기 위한 구체적인 수단을 제공한다.

  • C#는 본체와 인터페이스 멤버를 정의할 수 있는 8.0 버전 이후 기본 인터페이스 방법을 제공한다.[5]
  • D는 형식 내에서 모든 방법 및 포함된 다른 형식의 구성원을 전달할 수 있는 명시적인 "이것의 별칭" 선언을 제공한다.[6]
  • 다트는 공유할 수 있는 기본 구현이 포함된 믹스인을 제공한다.
  • Go type 임베딩은 전달 방법이 필요하지 않다.[7]
  • Java는 버전 8 이후 기본 인터페이스 방법을 제공한다.Lombok[8] 프로젝트에서 다음을 사용하여 위임을 지원@Delegate위임된 필드에서 모든 메서드의 이름 및 유형을 복사하고 유지하는 대신 필드 주석.[9]
  • 줄리아 매크로는 포워딩 방법을 만드는 데 사용될 수 있다.Lazy.jlTypeDelegation.jl과 같은 몇 가지 구현이 존재한다.
  • 코틀린은 언어 구문에 위임 패턴을 포함한다.[10]
  • PHP특성을 지원한다.
  • 라쿠는 a를 제공한다.handles메서드 전달을 용이하게 하는 키워드.
  • 녹은 기본 구현과 함께 특성을 제공한다.
  • 스칼라(버전 3)는 객체의 선택된 멤버에 대한 별칭을 정의하는 "내보내기" 절을 제공한다.[11]
  • 신속한 확장은 개별 타입의 구현 내에서가 아니라 프로토콜 자체에 대한 프로토콜의 기본 구현을 정의하는 데 사용될 수 있다.[12]

경험적 연구

93개의 오픈 소스 Java 프로그램(규모가 다양한)에 대한 2013년 연구에서는 다음과 같은 결과가 나왔다.

상속을 구성(...)으로 대체할 수 있는 큰 기회는 없지만, 그 기회는 상당하다(상속[상속] 사용의 2%는 내부 재사용일 뿐이며, 추가 22%는 외부 또는 내부 재사용일 뿐이다).우리의 결과는 (최소한 오픈소스 자바 소프트웨어에서) 상속 남용을 걱정할 필요가 없다는 것을 암시하지만, 그들은 구성의 사용과 상속의 사용에 관한 문제를 강조한다.구성을 사용할 수 있을 때 상속을 사용하는 것과 관련하여 상당한 비용이 든다면, 우리의 결과는 우려할 만한 원인이 있음을 시사한다.

Tempero et al., "What programmers do with inheritance in Java"[13]

참고 항목

참조

  1. ^ a b Freeman, Eric; Robson, Elisabeth; Sierra, Kathy; Bates, Bert (2004). Head First Design Patterns. O'Reilly. p. 23. ISBN 978-0-596-00712-6.
  2. ^ Knoernschild, Kirk (2002). Java Design - Objects, UML, and Process: 1.1.5 Composite Reuse Principle (CRP). Addison-Wesley Inc. ISBN 9780201750447. Retrieved 2012-05-29.
  3. ^ Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. p. 20. ISBN 0-201-63361-2. OCLC 31171684.
  4. ^ Pike, Rob (2012-06-25). "Less is exponentially more". Retrieved 2016-10-01.
  5. ^ "What's new in C# 8.0". Microsoft Docs. Microsoft. Retrieved 2019-02-20.
  6. ^ "Alias This". D Language Reference. Retrieved 2019-06-15.
  7. ^ "(Type) Embedding". The Go Programming Language Documentation. Retrieved 2019-05-10.
  8. ^ https://projectlombok.org
  9. ^ "@Delegate". Project Lombok. Retrieved 2018-07-11.
  10. ^ "Delegated Properties". Kotlin Reference. JetBrains. Retrieved 2018-07-11.
  11. ^ "Export Clauses". Scala Documentation. Retrieved 2021-10-06.
  12. ^ "Protocols". The Swift Programming Language. Apple Inc. Retrieved 2018-07-11.
  13. ^ Tempero, Ewan; Yang, Hong Yul; Noble, James (2013). What programmers do with inheritance in Java (PDF). ECOOP 2013–Object-Oriented Programming. Lecture Notes in Computer Science. Vol. 7920. pp. 577–601. doi:10.1007/978-3-642-39038-8_24. ISBN 978-3-642-39038-8.