[language-agnostic] 상속보다 구성을 선호하십니까?

상속보다 구성을 선호하는 이유는 무엇입니까? 각 접근법마다 어떤 절충점이 있습니까? 구성보다 상속을 언제 선택해야합니까?



답변

상속 가능성이 높고 나중에 수정하기가 쉽지만 작성 항상 접근 방식을 사용하지 않으므로 상속보다 구성을 선호하십시오. 컴포지션을 사용하면 Dependency Injection / Setter를 사용하여 동작을 쉽게 변경할 수 있습니다. 대부분의 언어에서는 여러 유형에서 파생 할 수 없으므로 상속이 더 엄격합니다. 따라서 TypeA에서 파생되면 거위는 다소 요리됩니다.

위의 내 산 테스트는 다음과 같습니다.

  • TypeB가 TypeA가 예상되는 곳에서 TypeB를 사용할 수 있도록 TypeA의 완전한 인터페이스 (모든 공용 메소드)를 공개하려고합니까? 상속을 나타냅니다 .

    • 예를 들어 Cessna 복엽 비행기는 비행기의 전체 인터페이스를 보여줍니다. 따라서 비행기에서 파생되는 것이 적합합니다.
  • TypeB는 TypeA에 의해 노출되는 동작의 일부 / 일부만 원합니까? 구성이 필요함을 나타냅니다 .

    • 예를 들어 조류는 비행기의 비행 행동 만 필요할 수 있습니다. 이 경우 인터페이스 / 클래스 / 둘 다로 추출하여 두 클래스의 멤버로 만드는 것이 좋습니다.

업데이트 : 방금 내 대답으로 돌아 왔으며 ‘이 유형에서 상속해야합니까?’에 대한 테스트로 Barbara Liskov의 Liskov 대체 원칙에 대한 언급이 없으면 불완전한 것으로 보입니다.


답변

격리 관계 있다고 생각하십시오 . 자동차에는 “엔진”, 사람에는 “이름”등이 있습니다.

상속 관계 라고 생각하십시오 . 자동차는 “차량”, 사람은 “동물”등

나는이 접근법에 대해 아무런 신용도받지 않는다. 나는에서 똑바로했다 전체 코드의 두 번째 판 에 의해 스티브 맥코넬 , 6.3 절 .


답변

차이점을 이해하면 설명하기가 더 쉽습니다.

절차 코드

예를 들어 클래스를 사용하지 않는 PHP (특히 PHP5 이전)가 있습니다. 모든 논리는 일련의 기능으로 인코딩됩니다. 헬퍼 함수 등을 포함하는 다른 파일을 포함하고 함수에서 데이터를 전달하여 비즈니스 로직을 수행 할 수 있습니다. 애플리케이션이 커질수록 관리하기가 매우 어려울 수 있습니다. PHP5는 더 많은 객체 지향 디자인을 제공함으로써이를 해결하려고합니다.

계승

이것은 클래스 사용을 권장합니다. 상속은 OO 디자인의 세 가지 신조 중 하나입니다 (상속, 다형성, 캡슐화).

class Person {
   String Title;
   String Name;
   Int Age
}

class Employee : Person {
   Int Salary;
   String Title;
}

이것은 직장에서의 상속입니다. 직원은 “개인”이거나 개인으로부터 상속받습니다. 모든 상속 관계는 “is-a”관계입니다. 또한 Employee는 Person의 Title 속성을 숨 깁니다. 이는 Employee를 의미합니다. Title은 Person이 아닌 Employee의 제목을 반환합니다.

구성

상속보다 구성이 선호됩니다. 간단히 말하면 다음과 같습니다.

class Person {
   String Title;
   String Name;
   Int Age;

   public Person(String title, String name, String age) {
      this.Title = title;
      this.Name = name;
      this.Age = age;
   }

}

class Employee {
   Int Salary;
   private Person person;

   public Employee(Person p, Int salary) {
       this.person = p;
       this.Salary = salary;
   }
}

Person johnny = new Person ("Mr.", "John", 25);
Employee john = new Employee (johnny, 50000);

구성은 일반적으로 “있다”또는 “사용”관계입니다. 여기 Employee 클래스에는 Person이 있습니다. Person으로부터 상속받지 않고 대신 Person 오브젝트를 전달받습니다. 이것이 “Person”을 갖는 이유입니다.

상속에 대한 구성

이제 관리자 유형을 만들고 싶다고 가정 해 봅시다.

class Manager : Person, Employee {
   ...
}

그러나이 예제는 제대로 작동하지만 Person과 Employee가 모두 선언 한 경우에는 어떻게 Title됩니까? Manager.Title이 “Manager of Operations”또는 “Mr.”를 반환해야합니까? 구성에서이 모호성은 더 잘 처리됩니다.

Class Manager {
   public string Title;
   public Manager(Person p, Employee e)
   {
      this.Title = e.Title;
   }
}

Manager 개체는 직원과 사람으로 구성됩니다. 직함 행동은 직원으로부터 가져옵니다. 이 명시적인 구성은 무엇보다도 애매 모호함을 없애고 버그가 줄어 듭니다.


답변

상속을 통해 부인할 수없는 모든 혜택을 누리면 다음과 같은 단점이 있습니다.

상속의 단점 :

  1. 런타임에 수퍼 클래스에서 상속 된 구현을 변경할 수 없습니다 (물론 상속은 컴파일 타임에 정의되므로).
  2. 상속은 서브 클래스를 부모 클래스 구현의 세부 사항에 노출 시키므로 상속이 캡슐화를 깨뜨리는 경우가 종종 있습니다 (구현이 아닌 인터페이스에만 집중해야하므로 서브 클래 싱으로 재사용하는 것이 항상 바람직하지는 않습니다).
  3. 상속에 의해 제공되는 긴밀한 결합은 서브 클래스의 구현이 상위 클래스의 구현과 매우 밀접한 관계를 가지므로 부모 구현의 변경은 서브 클래스가 변경되도록합니다.
  4. 서브 클래 싱에 의한 과도한 재사용은 상속 스택을 매우 깊고 혼란스럽게 만들 수 있습니다.

반면에 객체 구성 은 런타임에 다른 객체에 대한 참조를 얻는 객체를 통해 정의됩니다. 이러한 경우 이러한 개체는 서로의 보호 된 데이터에 도달 할 수 없으며 (캡슐화 중단 없음) 서로의 인터페이스를 강제해야합니다. 이 경우에도 구현 종속성은 상속의 경우보다 훨씬 적습니다.


답변

상속보다 구성을 선호하는 또 다른 매우 실용적인 이유는 도메인 모델과 관련이 있으며 관계형 데이터베이스에 매핑해야합니다. 상속을 SQL 모델에 매핑하는 것은 실제로 어렵습니다 (항상 사용되지 않는 열 만들기, 뷰 사용 등)와 같은 모든 종류의 해킹 방법으로 끝납니다. 일부 ORML은이를 처리하려고하지만 항상 빠르게 복잡해집니다. 두 테이블 간의 외래 키 관계를 통해 컴포지션을 쉽게 모델링 할 수 있지만 상속은 훨씬 어렵습니다.


답변

간단히 말해서 나는 “상속보다 구성을 선호한다”에 동의하지만, 종종 “코카콜라보다 감자를 선호한다”고 들린다. 상속 장소와 작곡 장소가 있습니다. 차이점을 이해해야합니다. 그러면이 질문은 사라집니다. 그것이 저에게 실제로 의미하는 것은 “상속을 사용하려는 경우 다시 생각하면 기회가 필요하다는 것”입니다.

먹고 싶을 때는 코카콜라보다 감자를, 마시고 싶을 때는 감자보다 코카콜라를 선호해야합니다.

서브 클래스를 생성한다는 것은 수퍼 클래스 메소드를 호출하는 편리한 방법 이상을 의미해야합니다. 서브 클래스 “is-a”수퍼 클래스가 구조적으로나 기능적으로 모두 수퍼 클래스로 사용될 수 있고 상속 할 때는 상속을 사용해야합니다. 그렇지 않은 경우-상속이 아니라 다른 것입니다. 구성은 개체가 다른 개체로 구성되거나 관계가있는 경우입니다.

그래서 누군가가 상속이나 구성이 필요한지 알지 못하는 경우 실제 문제는 그가 마시거나 먹고 싶어하는지 모른다는 것입니다. 문제 영역에 대해 더 많이 생각하고 더 잘 이해하십시오.


답변

상속은 특히 절차 적 국가에서 오는 매우 유혹적이며 종종 기만적으로 우아해 보입니다. 내가해야 할 일은이 클래스의 기능을 다른 클래스에 추가하는 것입니다. 글쎄, 문제 중 하나는

상속은 아마도 최악의 결합 형태 일 것입니다

기본 클래스는 구현 된 세부 정보를 보호 된 멤버 형식으로 하위 클래스에 노출시켜 캡슐화를 중단합니다. 이것은 시스템을 단단하고 취약하게 만듭니다. 그러나 더 비극적 인 결함은 새로운 하위 클래스가 상속 체인의 모든 수하물과 의견을 가져 오는 것입니다.

Inheritance is Evil : DataAnnotationsModelBinder의 Epic Fail 기사 는 C #에서 이에 대한 예제를 보여줍니다. 컴포지션을 사용해야 할 때 상속을 사용하고 리팩토링 할 수있는 방법을 보여줍니다.