1387

상속에 비해 컴포지션을 선호하는 이유는 무엇입니까? 각 접근법에는 어떤 절충점이 있습니까? 작곡에 대한 상속을 언제 선택해야합니까?


  • 또한보십시오어느 클래스 디자인이 더 낫다. - maccullt
  • 그 질문에 대한 좋은 기사가 있습니다.이리. 나의 개인 의견은 "더 나은" 또는 "악화" 원리. "적절한" 및 "부적절한" 구체적인 작업을위한 디자인. 즉, 상황에 따라 상속이나 구성을 사용합니다. 목표는 더 작은 코드를 생성하고, 읽기 쉽고, 재사용하고, 최종적으로 확장하는 것입니다. - m_pGladiator
  • 하나의 문장에서 상속은 공개 메소드이고 공개 메소드를 변경하면 게시 된 API가 변경됩니다. 컴포지션이 있고 구성된 오브젝트가 변경된 경우 게시 된 API를 변경하지 않아도됩니다. - Tomer Ben David
  • 위키 피 디아에서보기상속에 대한 구성. - DavidRR

30 답변


1048

상속보다 컴포지션이 좋고 나중에 수정하기 쉽기 때문에 상속보다 컴포지션을 선호하지만 항상 구성 방식을 사용하지 마십시오.구성을 사용하면 Dependency Injection / Setters를 사용하여 신속하게 동작을 변경할 수 있습니다. 대부분의 언어가 하나 이상의 유형에서 파생되도록 허용하지 않으므로 상속이보다 엄격 해집니다. Type A에서 파생되면 거위가 다소 조리됩니다.

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

  • TypeB는 TypeA가 예상되는 곳에서 TypeB를 사용할 수 있도록 TypeA의 전체 인터페이스 (모든 공용 메소드도 포함)를 노출하고 싶습니까? 나타냅니다계승.

예 : Cessna 복엽 비행기는 더 이상 비행기의 완전한 인터페이스를 노출하지 않을 것입니다. 그래서 그것이 비행기에서 파생되기에 적합합니다.

  • TypeB는 TypeA에 의해 노출 된 행동의 일부 또는 일부만을 원하나요? 필요성을 나타냄구성.

예 : 새는 비행기의 비행 행동 만 필요할 수 있습니다. 이 경우 인터페이스 / 클래스 / 모두로 추출하여 두 클래스 모두의 구성원으로 만드는 것이 좋습니다.

최신 정보:그냥 내 대답으로 돌아 왔고 Barbara Liskov 's에 대한 언급이 없으면 불완전 해 보인다.리 스키프 대치 원리'내가이 유형을 상속 받아야 하는가?'에 대한 테스트로


  • 두 번째 예제는 Head First Design Patterns (amazon.com/First-Design-Patterns-Elisabeth-Freeman/dp/…) book :) 나는이 질문에 인터넷 검색을하는 사람에게이 책을 추천 할 것이다. - Jeshurun
  • " TypeB는 TypeA가 예상되는 곳에서 TypeB를 사용할 수 있도록 TypeB의 전체 인터페이스 (모든 공개 메소드도 포함)를 노출하고 싶습니까? " 그러나 이것이 사실이라면 TypeB는 TypeC의 완전한 인터페이스를 노출합니까? 그리고 TypeC가 아직 모델화되지 않았다면 어떨까요? - Tristan
  •                            이 기본 견해가되어야한다고 생각하는 것에 당신은 암시합니다. 대답이 '예'인 경우, 객체절대로 필요한 것상속. 그렇지 않다면 아마 안됩니다. 내 druthers가 있다면, 언어는 "이 클래스"를 참조하고 다른 클래스처럼 행동해야하지만 그것을 대체 할 수없는 클래스를 정의하는 수단을 제공하는 키워드를 제공합니다 (그러한 클래스는 모든 " 이 클래스는 자체로 대체 된 참조). - supercat
  • @Alexey - 요점은 '세스나 복엽기에서 비행기를 예기치 않은 모든 고객에게 전달할 수 있습니까?'입니다. ' 그렇다면 상속을받을 가능성이 있습니다. - Gishu
  • 사실상 상속이 내 대답이었던 사례를 생각해 보려고 애 쓰고 있습니다. 종종 집계, 구성 및 인터페이스가 더욱 세련된 솔루션을 제공한다는 사실을 알고 있습니다. 위의 예제 중 상당수는 이러한 접근법을 사용하여 더 잘 설명 될 수 있습니다. - Stuart Wakefield

357

봉쇄를~을 가지고있다.관계. 자동차에는 "엔진이 있고 사람은"이름이 있습니다.

상속을~이다.관계. 자동차 "는"차량 ","사람 "은"포유류 "등입니다.

나는이 접근법에 대해 어떤 공로도 인정하지 않는다. 나는 그것을에서 바로 잡았다.코드 완성의 두 번째 버전으로스티브 맥코넬,섹션 6.3.


  • 이것은 항상 완벽한 접근 방식은 아니며 단지 좋은 지침 일뿐입니다. Liskov Substitution Principle은 훨씬 정확합니다 (덜 실패 함). - Bill K
  • " 내 자동차에는 차량이 있습니다. " 프로그래밍 컨텍스트가 아닌 별도의 문장으로 간주하면 절대 의미가 없습니다. 그리고이 기술의 요점은. 어색하게 들리면 틀릴 수도 있습니다. - Nick Zalutskiy
  • @ 닉 물론입니다.하지만 " 내 자동차에는 자동차 행동이 있습니다 " 더 의미가 있습니다 (귀하의 'Vehicle'클래스를 'VehicleBehavior'라고 명명 할 수 있음). 그래서 당신은 당신의 결정을 " 가지고 있습니다 " vs "는" 비교, 당신은 LSP를 사용해야합니다, 그렇지 않으면 실수를 할 것입니다. - Tristan
  • "is is"대신에 "is is" "like like"라고 생각하십시오. 상속은 의미론이 아니라 행위를 물려받는 것입니다. - ybakos
  • @ybakos "" 상속을 필요로하지 않고 인터페이스를 통해 구현할 수 있습니다.위키 백과:"상속을 통한 합성의 구현은 전형적으로 시스템이 보여야 만하는 행동을 나타내는 다양한 인터페이스의 생성으로 시작된다 ... 따라서 시스템 동작은 상속없이 실현된다." - DavidRR

184

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

절차 코드

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

계승

이것은 수업의 사용을 권장합니다. 상속은 객체 지향 디자인 (상속, 다형성, 캡슐화)의 세 가지 교리 중 하나이다.

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

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

이것은 직장에서 상속됩니다. 직원은 "사람이거나 사람으로부터 상속됩니다. 모든 상속 관계는 "is-a"관계입니다. Employee는 또한 Person의 Title 속성을 어둡게하므로 Employee.Title은 해당 Person이 아닌 Employee의 Title을 반환합니다.

구성

구성은 상속보다 선호됩니다. 매우 간단하게 말하면 다음과 같습니다.

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? 관리자. 제목 "관리자 운영"또는 "씨"반환해야합니까? 구성에서이 모호성이 더 잘 처리됩니다.

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

Manager 객체는 Employee와 Person으로 구성됩니다. 제목 동작은 직원에게서 취해진 것입니다. 이 명시적인 구성은 다른 것들 사이에 모호성을 제거하고 당신은 더 적은 버그를 접하게 될 것입니다.


  • 상속의 경우 : 모호함이 없습니다. 요구 사항을 기반으로 Manager 클래스를 구현하고 있습니다. 따라서 " 운영 관리자 " 귀하의 요구 사항이 무엇인지 명시했다면, 기본 클래스의 구현만을 사용하십시오. 또한 Person을 추상 클래스로 만들 수 있으므로 다운 스트림 클래스가 Title 속성을 구현하도록 할 수 있습니다. - Raj Rao
  • "상속에 대한 구성"이라고 말할 수도 있다는 사실을 기억하는 것이 중요합니다. 그러나 그것은 "Composition always Inheritence over Composition"을 의미하지 않는다. " Is is " 상속을 의미하며 코드 재사용을 유도합니다. 직원은 사람입니다 (직원에게는 사람이 없습니다). - Raj Rao
  • 예제는 혼란 스럽습니다. 직원은 사람이므로 상속을 사용해야합니다. 기술적으로 코드에서 선언 할 수 있다고하더라도 도메인 모델에서 잘못된 관계이므로이 예제에서는 composition을 사용하지 않아야합니다. - Michael Freidgeim
  • 나는이 예에 동의하지 않는다. 직원~이다인격 : 적절한 상속 사용의 교과서 경우입니다. 나는 또한 "이슈" Title 필드의 재정의는 의미가 없습니다. Employee.Title shadows Person.Title은 프로그래밍이 좋지 않다는 신호입니다. 결국, "Mr." 및 " 운영 관리자 " 정말 사람의 동일한 측면 (소문자)을 말하는가? 나는 Employee.Title의 이름을 바꿔 직원의 Title과 JobTitle 속성을 참조 할 수 있습니다. 둘 다 실제 생활에서 의미가 있습니다. 또한 관리자에게는 이유가 없습니다 (계속 ...). - Radon Rosborough
  • (... 계속) Person과 Employee 모두로부터 상속받습니다. 결국 Employee는 Person으로부터 상속받습니다. 사람이 Manager와 Agent 일 수있는 좀 더 복잡한 모델에서는 다중 상속을 사용할 수 있지만 (신중하게!) 많은 환경에서 Manager (Employees 포함)에서 추상 Role 클래스를 갖는 것이 바람직합니다 s / he 관리) 및 에이전트 (계약 및 기타 정보 포함)가 상속합니다. 그런 다음 직원은 여러 역할을 가진 사람입니다. 따라서 구성과 상속이 모두 올바르게 사용됩니다. - Radon Rosborough

98

상속에 의해 제공되는 부인할 수없는 모든 혜택을 통해 여기에 몇 가지 단점이 있습니다.

상속의 단점 :

  1. 런타임에는 슈퍼 클래스에서 상속 된 구현을 변경할 수 없습니다 (상속은 컴파일 타임에 정의되기 때문에 분명히).
  2. 상속은 하위 클래스를 상위 클래스의 구현에 대한 세부 정보로 노출하므로 상속으로 인해 캡슐화가 중단된다고합니다 (구현이 아닌 인터페이스에만 집중해야하므로 하위 클래스로 다시 사용하는 것이 항상 바람직한 것은 아닙니다).
  3. 상속에 의해 제공되는 단단한 결합은 서브 클래스의 구현을 수퍼 클래스의 구현과 매우 밀접하게 만든다. 부모 구현의 변경으로 인해 하위 클래스가 변경되게된다.
  4. 하위 클래스에 의한 과도한 재사용은 상속 스택을 매우 깊고 혼란스럽게 만들 수 있습니다.

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


  • 이것은 내 의견으로는 더 좋은 대답 중 하나입니다. 저는 경험상 작문 측면에서 문제를 다시 생각하려고하면 작고 단순하며 자급하고 재사용이 가능한 수업으로 이어지는 경향이 있다는 점을 추가 할 것입니다. 보다 명확하고, 작고,보다 집중된 책임 범위를 가지고 있습니다. 종종 이것은 의존성 주입이나 조롱 (테스트에서)과 같은 것들에 대한 필요성이 적다는 것을 의미합니다. 작은 컴포넌트는 대개 자체적으로 설 수 있습니다. 그냥 내 경험. YMMV :-) - mindplay.dk
  • 이 게시물의 마지막 단락은 실제로 나를 위해 클릭했습니다. 고맙습니다. - Salx
  • 최선의 답은 손을 내밀다. - bertie

77

상속에 비해 컴포지션을 선호하는 또 다른 매우 실용적인 이유는 도메인 모델과 관계형 데이터베이스에 매핑해야하기 때문입니다. 상속을 SQL 모델에 매핑하는 것은 정말 어렵습니다 (항상 사용되지 않는 열 만들기,보기 사용 등 모든 종류의 해킹 해결 방법이 필요합니다). 일부 ORML은이를 처리하려고 시도하지만 항상 복잡해진다. 구성은 두 테이블 간의 외래 키 관계를 통해 쉽게 모델링 될 수 있지만 상속은 훨씬 어렵습니다.


69

간단히 말해서 나는 "상속에 대한 구성을 선호한다"와 동의 할 것이지만, 종종 "코카콜라에 비해 감자를 좋아한다"는 말처럼 들립니다. 상속 장소와 작곡 장소가 있습니다. 차이점을 이해해야합니다. 그러면이 질문은 사라질 것입니다. 내게 정말로 의미하는 것은 "당신이 상속을 사용한다면 다시 생각해보십시오, 기회는 당신에게 구성이 필요합니다"입니다.

당신은 먹고 싶을 때 코카콜라에 감자를, 음료를 마실 때 감자에 코카콜라를 선호해야합니다.

서브 클래스를 생성하는 것은 수퍼 클래스 메소드를 호출하는 편리한 방법 이상을 의미해야합니다. 수퍼 클래스로 사용할 수 있고 그것을 사용하려고 할 때 "is-a"수퍼 클래스를 구조적 및 기능적으로 하위 클래스로 만들려면 상속을 사용해야합니다. 그것이 사실이 아니라면 - 상속이 아니라 다른 것입니다. 구성은 객체가 다른 객체로 구성되거나 객체와 어떤 관계가있는 경우입니다.

따라서 저에게 누군가 상속이나 작곡이 필요한지 모르는 것처럼 보이는 경우, 실제로 문제는 그가 마시고 먹고 싶은지 알 수 없다는 것입니다. 문제 영역에 대해 더 많이 생각하고 더 잘 이해하십시오.


  • 적절한 작업에 적합한 도구. 망치는 렌치보다 두근 거리는 것이 좋을 수 있지만 렌치를 "열등한 망치"로 보는 것은 아닙니다. 상속은 객체가 수퍼 클래스 객체로 동작하기 위해 하위 클래스에 추가되는 것이 필요할 때 유용 할 수 있습니다. 예를 들어, 기본 클래스InternalCombustionEngine파생 된 클래스로GasolineEngine. 후자는 기본 클래스에서 부족한 점화 플러그와 같은 것을 추가하지만InternalCombustionEngine점화 플러그가 사용되게 할 것입니다. - supercat

54

상속은 특히 절차 적 토지에서 유래하는 것을 유혹하며 흔히 사기성으로 보입니다. 다른 클래스에이 기능을 추가하는 것뿐입니다. 음, 문제 중 하나는

상속은 아마도 여러분이 가질 수있는 최악의 형태의 결합입니다.

기본 클래스는 구현 된 세부 사항을 보호 된 멤버의 형태로 하위 클래스에 노출시켜 캡슐화를 중단합니다. 이로 인해 시스템이 단단하고 약해집니다. 그러나 더 비극적 인 결점은 새로운 하위 클래스가 모든 유산과 상속 사슬의 의견을 가져다 준다는 것입니다.

기사,상속은 악이다 : DataAnnotationsModelBinder의 서사시C #에서이 예제를 살펴 봅니다. 그것은 구성이 사용되어 져야하고 그것이 어떻게 리팩토링 될 수 있었는지 상속의 사용을 보여줍니다.


  • 상속은 좋지도 좋지도 않으며 단지 구성의 특별한 경우에 불과합니다. 실제로, 서브 클래스가 수퍼 클래스와 유사한 기능을 구현하는 경우. 제안 된 하위 클래스가 다시 구현되지는 않지만~을 사용하여수퍼 클래스의 기능을 사용하면 상속을 잘못 사용했습니다. 그것은 프로그래머의 실수입니다. 상속에 대한 반성이 아닙니다. - iPherian

39

Java 또는 C #에서는 객체가 인스턴스화되면 객체의 유형을 변경할 수 없습니다.

따라서 개체를 다른 개체로 표시해야하거나 개체 상태 나 조건에 따라 다르게 동작해야하는 경우에는구성: 인용하다상태계략디자인 패턴.

객체가 같은 유형이어야한다면, 다음을 사용하십시오.계승또는 인터페이스를 구현할 수 있습니다.


  • +1 대부분의 상황에서 그 상속이 점점 적게 발견되었습니다. 필자는 공유 / 상속 된 인터페이스와 객체의 구성을 선호합니다 .... 아니면 집합이라고 부릅니까? EE 학위가 있습니까? - kenny
  • 나는 이것이 "상속을 통한 구성"이 가장 일반적인 시나리오라고 믿는다. 둘 다 이론적으로 적합 할 수 있기 때문에 적용됩니다. 예를 들어, 마케팅 시스템에서Client. 그런 다음 새로운 개념의PreferredClient나중에 팝업. 할까요PreferredClient상속하다Client? 선호하는 고객은 ' 클라이언트 afterall, 안돼? 음, 그렇게 빠르지는 않습니다 ... 당신이 말했듯이, 객체는 런타임에 클래스를 변경할 수 없습니다. 어떻게 모델링하겠습니까?client.makePreferred()조작? 아마도 해답은 누락 된 개념의 구성을 사용하는 것입니다.Account혹시? - plalx
  • 다른 유형의Client클래스의 개념을 캡슐화 한 것일 수도 있습니다.Account그것은StandardAccount또는PreferredAccount... - plalx

29

개인적으로 나는 상속에 비해 항상 구성을 선호하는 법을 배웠습니다. 컴포지션으로 해결할 수없는 상속으로 해결할 수있는 프로그램 적 문제는 없습니다. 경우에 따라 인터페이스 (Java) 또는 프로토콜 (Obj-C)을 사용해야 할 수도 있습니다. C ++은 그런 것을 모르기 때문에 추상 기본 클래스를 사용해야합니다. 즉, C ++에서 상속을 완전히 제거 할 수 없습니다.

컴포지션은 종종 더 논리적이며 더 우수한 추상화, 더 나은 캡슐화, 더 큰 코드 재사용 (특히 대규모 프로젝트의 경우)을 제공하며 코드에서 아무 곳이나 격리 된 변경을 수행했기 때문에 먼 거리에서 아무 것도 부러 뜨릴 가능성이 적습니다. 또한 "단일 책임 원칙"는 종종"수업을 변경하는 데는 한 가지 이상의 이유가 없어야합니다."라는 말은 모든 클래스가 특정 목적을 위해 존재하며 그 목적과 직접적으로 관련이있는 메소드를 가지고 있어야한다는 것을 의미합니다. 또한 매우 얕은 상속 트리를 사용하면 프로젝트가 실제로 시작될 때도 개요를 유지하는 것이 훨씬 쉬워집니다 많은 사람들은 상속이 우리를 대표한다고 생각합니다.현실 세계꽤 잘,하지만 그것은 진실이 아닙니다. 현실 세계는 상속보다 훨씬 더 많은 구성을 사용합니다. 손에 넣을 수있는 거의 모든 실제 세계 물체는 다른보다 작은 실제 세계 물체로 구성되었습니다.

그러나 조성의 단점이 있습니다. 상속을 완전히 건너 뛰고 구성에만 초점을두면 상속을 사용한 경우 필요하지 않은 몇 가지 추가 코드 줄을 작성해야한다는 것을 알게됩니다. 때때로 당신은 또한 반복적으로 반복해야하며 이것은건조한 원리(DRY = 반복하지 마십시오). 또한 컴포지션은 종종 위임을 요구하며 메서드는이 호출을 둘러싼 다른 코드가없는 다른 객체의 다른 메서드를 호출하기 만합니다. 이러한 "double method calls"(쉽게 3 배 또는 4 배의 메서드 호출로 확장 될 수 있으며 그보다 훨씬 더 멀리있을 수 있음)는 부모의 메서드를 단순히 상속받는 상속보다 훨씬 성능이 떨어집니다. 상속 된 메서드를 호출하는 것은 상속되지 않은 메서드를 호출하는 것과 똑같은 속도가 될 수도 있고 약간 느릴 수도 있지만 대개 두 번의 연속 메서드 호출보다 훨씬 빠릅니다.

대부분의 OO 언어가 다중 상속을 허용하지 않는다는 것을 알았을 것입니다. 다중 상속이 실제로 당신에게 무언가를 살 수있는 몇 가지 경우가 있지만, 이는 규칙보다 다소 예외적 인 경우입니다. "다중 상속이이 문제를 해결하는 데 정말로 멋진 기능이 될 것"이라고 생각하는 상황에 처할 때마다 일반적으로 상속을 다시 생각해야합니다. 두 번 더 추가 코드 줄이 필요할 수도 있기 때문입니다 , 구성에 기반한 솔루션은 일반적으로 훨씬 더 우아하고 유연하며 미래의 증거가 될 것입니다.

상속은 정말 멋진 기능이지만, 지난 2 년 동안 남용 된 것 같습니다. 사람들은 상속을 실제로 손톱, 나사, 아니면 완전히 다른 무언가인지에 상관없이 모든 것을 못 박는 하나의 망치로 간주했습니다.


29

여기서 만족스러운 답변을 찾지 못했기 때문에 새로 작성했습니다.

왜 "취하다구성 상속 ", 우리는 먼저이 단축 된 관용구에서 생략 된 가정을 되 찾을 필요가있다.

상속의 두 가지 이점이 있습니다.하위 유형 지정 및 서브 클래 싱

  1. 서브 타이핑유형 (인터페이스) 서명, 즉 API 집합을 따르는 것을 의미하며, 하위 유형 다형성을 달성하기 위해 서명의 일부를 대체 할 수 있습니다.

  2. 하위 클래스메소드 구현의 암시 적 재사용을 의미합니다.

두 가지 이점을 통해 상속을 수행하는 두 가지 목적이 있습니다. 하위 유형 지정 지향 및 코드 재사용 지향입니다.

코드 재사용이바닥하위 클래스 화는 자신이 필요로하는 것 이상을 제공 할 수 있습니다. 즉, 상위 클래스의 일부 공용 메소드는 하위 클래스에 대해별로 의미가 없습니다. 이 경우, 상속에 비해 composition을 선호하는 대신, composition은요구 한. 이것은 "is-a"대 "has-a"개념의 출처이기도합니다.

따라서 하위 유형 지정이 목적 일 때, 즉 나중에 다형성 방식으로 새 클래스를 사용하는 경우에만 상속 또는 구성을 선택하는 문제에 직면하게됩니다. 이것은 논의 된 단축 관용구에서 생략된다는 가정이다.

하위 유형은 유형 서명을 따르는 것인데, 이는 컴포지션이 항상 해당 유형의 API를 적게 노출 함을 의미합니다. 이제 트레이드 오프가 시작됩니다.

  1. 상속은 오버라이드되지 않으면 직접 코드를 재사용 할 수있는 반면 컴포지션은 위임의 단순한 작업 일지라도 모든 API를 다시 코딩해야합니다.

  2. 상속은 간단합니다.열린 재귀내부 다형성 사이트를 통해this, 즉 오버라이드 메소드 호출 (또는 심지어유형) 다른 멤버 함수에서 공개 또는 비공개 (비록낙담 한). 공개 재귀가 될 수 있습니다조성을 통해 시뮬레이션 됨, 그러나 그것은 여분의 노력이 필요하며 항상 실행 가능하지는 않습니다 (?). 이대답중복 된 질문에 비슷한 이야기.

  3. 상속 노출보호 된회원. 이렇게하면 부모 클래스의 캡슐화가 중단되고 하위 클래스에서 사용되는 경우 하위와 상위 간의 다른 종속성이 도입됩니다.

  4. 컴포지션은 컨트롤의 반전에 잘 맞으며, 종속성은 다음과 같이 동적으로 주입 될 수 있습니다.데코레이터 패턴프록시 패턴.

  5. 작곡에는 다음과 같은 이점이있다.조합 자 중심의프로그래밍, 즉복합 패턴.

  6. 작곡 직후인터페이스 프로그래밍.

  7. 작곡에는 쉬운 이점이 있습니다.다중 상속.

위의 트레이드 오프를 염두에두고 우리는취하다상속 이상의 구성. 그러나 밀접한 관련 클래스의 경우, 즉 암시 적 코드 재사용이 실제로 이익을 창출하거나 개방 재귀의 마법력이 요구되는 경우, 상속이 선택됩니다.


  • 좋은 대답, 더 많은 +1 가치가 있습니다. - markus
  • 여기서 만족스러운 답변을 찾지 못했지만 그 답을 썼습니다. - leokhorn

20

나의 일반적인 경험 법칙 :상속을 사용하기 전에 구성이 더 합리적인지 고려하십시오.

이유:서브 클래 싱은 일반적으로 실수를하지 않고도 변경, 유지 및 확장하기가 더 어려워 진 복잡성과 연결성을 의미합니다.

훨씬 더 완전하고 구체적인Tim Boudreau의 답변태양의 :

내가보기에 상속을 사용하는 데 공통적 인 문제는 다음과 같습니다.

  • 무고한 행위로 인해 예상치 못한 결과가 발생할 수 있습니다.- 이것의 고전적인 예는 수퍼 클래스에서 오버라이드 할 수있는 메소드를 호출하는 것입니다   constructor. 서브 클래스의 인스턴스 필드가   초기화 됨. 완벽한 세상에서 아무도 그렇게하지 않을 것입니다. 이것은   완벽한 세계가 아닙니다.
  • 서브 클래스가 메소드 호출의 순서와 그와 같은 가정에 대해 유혹하는 유혹을 제공합니다.- 그러한 가정은   수퍼 클래스가 시간이 지남에 따라 발전한다면 안정적이어야한다. 또한보십시오내 토스터기   커피 포트 유추.
  • 클래스가 무거워진다.- 수퍼 클래스가 생성자에서 수행중인 작업 또는 사용중인 메모리의 양을 반드시 알 필요는 없습니다.   쓰다. 무고한 가벼운 객체를 만들면   생각보다 훨씬 비싸고, 시간이 지남에 따라 바뀔 수 있습니다.   수퍼 클래스가 진화한다.
  • 하위 클래스의 폭발을 조장합니다.. 클래스 로딩 비용은 시간이 걸리고 클래스가 많을수록 메모리 비용이 많이 든다. 당신이되기 전까지는 문제가되지 않을 수도 있습니다.   NetBeans 규모의 앱을 다루고 있지만, 실제로는   예를 들어 메뉴가 느린 문제는 첫 번째 디스플레이   메뉴가 대량의 클래스 로딩을 유발했다. 우리는 다음으로 이동하여이 문제를 해결했습니다.   더 많은 선언적 구문 및 기타 기법을 사용하지만 그로 인해   고친다.
  • 나중에 상황을 변경하는 것이 더 힘들어집니다.- 클래스를 public으로 만든 경우, 수퍼 클래스를 바꾼다는 것은 하위 클래스를 깰 것이고,   일단 코드를 공개하면 결혼하게됩니다.   에. 따라서 실제 기능을 변경하지 않는 경우   수퍼 클래스를 사용하면 나중에 변경할 수있는 자유가 훨씬 늘어납니다.   사용하는 것이 아니라 필요한 것을 확장하십시오. 예를 들어,   JPanel을 서브 클래 싱하는 것은 일반적으로 잘못되었습니다. 서브 클래스가   대중에게 어딘가에, 당신은 그 결정을 다시 방문 할 수있는 기회를 얻지 못합니다. 만약   JComponent getThePanel ()로서 액세스 할 수 있습니다 만, 아직 할 수 있습니다 (힌트 :   API로 구성 요소의 모델을 노출).
  • 객체 계층 구조는 확장되지 않습니다 (나중에 스케일을 적용하는 것이 미리 계획하는 것보다 훨씬 어렵습니다)- 이것은 고전적인 "너무 많은 층"입니다.   문제. 아래에서이 부분을 살펴보고 AskTheOracle 패턴이   그것을 해결하십시오 (OOP 순수 주의자를 화나게 할지도 모르다).

...

당신이 상속을 허용한다면, 무엇을해야 할지를 제 견해로 삼으십시오.   소금 한 알을 먹으면 :

  • 상수를 제외한 모든 필드를 노출시키지 마십시오.
  • 방법은 추상 또는 최종
  • 수퍼 클래스 생성자에서 아무 메소드도 호출하지 않습니다.

...

이 모든 것은 큰 프로젝트보다 작은 프로젝트에 적게 적용됩니다.   공개 수업보다 개인 수업에


17

상속은 매우 강력하지만 강제 할 수는 없습니다 (참조 :원 - 타원 문제). 진정한 "is-a"하위 유형 관계를 완전히 확신 할 수 없다면, 작곡을하는 것이 가장 좋습니다.


14

상속은 하위 클래스와 수퍼 클래스간에 강력한 관계를 만듭니다. 서브 클래스는 수퍼 클래스의 구현 세부 사항을 알고 있어야합니다. 수퍼 클래스를 만드는 일은 어떻게 확장 할 수 있을지 생각해야 할 때 훨씬 더 어렵습니다. 클래스 불변량을 신중하게 문서화하고 오버라이드 가능한 메서드가 내부적으로 사용하는 다른 메서드를 설명해야합니다.

상속은 실제로 계층 구조가 is-a-relationship을 나타내는 경우 유용합니다. 이것은 Open-Closed Principle과 관련이 있으며 클래스는 수정을 위해 닫히고 확장은 개방되어야한다고 명시합니다. 그렇게하면 다형성을 가질 수 있습니다. 슈퍼 타입과 그 메소드를 다루는 일반적인 메소드를 가지지 만, 다이나믹 디스패치를 통해 서브 클래스의 메소드가 호출된다. 이는 유연하며 간접 참조를 생성하는 데 도움이됩니다. 간접 참조는 소프트웨어에서 필수적입니다 (구현 세부 정보가 적다).

하지만 상속은 쉽게 남용되지만 클래스 간 딱딱한 종속성으로 인해 복잡성이 추가로 발생합니다. 또한 프로그램 실행 중에 어떤 일이 일어나는지 이해하는 것은 레이어와 동적 메서드 호출 선택으로 인해 상당히 어려워집니다.

작곡을 기본값으로 사용할 것을 제안합니다. 더 모듈화되어 있으며 후기 바인딩의 이점을 제공합니다 (동적으로 구성 요소를 변경할 수 있음). 또한 개별적으로 테스트하는 것이 더 쉽습니다. 그리고 당신이 수업에서 방법을 사용해야하는 경우, 당신은 특정 형태 (Liskov 대체 원리)가되어서는 안됩니다.


  • 상속이 다형성을 달성하는 유일한 방법은 아니라는 점에 주목할 가치가 있습니다. 데코레이터 패턴은 컴포지션을 통해 다형성의 모양을 제공합니다. - BitMask777
  • @ BitMask777 : 하위 유형 다형성은 한 종류의 다형성 일뿐입니다. 다른 유형은 매개 변수형 다형성 일 수 있습니다. 상속 할 필요가 없습니다. 또한 더 중요한 것은 상속에 대해 말할 때 하나는 계급 상속을 의미합니다. .i.e. 여러 클래스에 대한 공통 인터페이스를 사용하여 하위 유형 다형성을 가질 수 있으며 상속 문제를 해결할 필요가 없습니다. - egaga
  • @engaga : 나는 당신의 의견을 해석했다.Inheritance is sometimes useful... That way you can have polymorphism상속과 다형성의 개념을 하드 링크하는 것으로서 (문맥에 따라 가정 된 하위 유형 지정). 내 의견은 당신이 논평 한 내용을 지적하기위한 것이 었습니다. 상속은 다형성을 구현하는 유일한 방법이 아니며 실제로 구성과 상속 사이를 결정할 때 반드시 결정 요소는 아닙니다. - BitMask777

13

좀 봐야 해.Liskov 대체 원리삼촌 밥의고체수업 설계의 원칙. :)


13

상속에 비해 컴포지션을 선호하는 이유는 무엇입니까?

다른 답변을 참조하십시오.

작곡에 대한 상속을 언제 선택해야합니까?

문장이 나올 때마다"바는 푸 (Foo)이고 바 (Bar)는 푸 (Foo)가 할 수있는 모든 것을 할 수 있습니다"말이된다.

평범한 지혜에 의하면, "바가 푸 (Foo)"라는 문장이 의미가있는 경우 상속을 선택하는 것이 적절하다는 좋은 힌트라고합니다. 예를 들어, 개는 동물이므로 개 클래스를 Animal에서 상속받는 것은 아마도 좋은 디자인 일 것입니다.

불행히도이 간단한 "is-a"테스트는 신뢰할 만하지 않습니다. 그만큼원 - 타원 문제원이 타원 인 경우에도 Ellipse에서 Circle 클래스를 상속받는 것은 좋지 않습니다. 타원에서는 수행 할 수 있지만 서클에서는 수행 할 수없는 작업이 있기 때문입니다. 예를 들어 타원은 늘릴 수 있지만 원은 할 수 없습니다. 그래서 네가 가질 수있는 동안ellipse.stretch(), 너는 가질 수 없어.circle.stretch().

이것이 더 나은 테스트가 "바는 푸 (Foo)입니다.바는 Foo가 할 수있는 모든 일을 할 수 있습니다."is-a"테스트는 다형성 사용에 필요한 조건 일 뿐이며 일반적으로 Foo의 모든 getter가 Bar에서 의미가 있음을 의미합니다. 추가 "can-do-everything"테스트는 Foo가 다형 적으로 사용될 수 있음을 의미합니다. 테스트는 Foo의 모든 setter도 Bar에서 의미가 있음을 의미합니다.이 추가 테스트는 일반적으로 클래스 Bar가 Foo 일 때 실패하지만 일부 제약 조건을 추가합니다.이 경우 Foo는 상속을 사용할 수 없기 때문에 상속을 사용하지 않아야합니다. 즉, 상속은 속성을 공유하는 것이 아니라 기능을 공유하는 것입니다. 파생 클래스는 다형 적으로 사용됩니다.넓히다기본 클래스의 기능을 제한하지 않습니다.

이것은리 스키프 대치 원리:

기본 클래스에 대한 포인터 또는 참조를 사용하는 함수는 파생 클래스의 객체를 알지 못해도 사용할 수 있어야합니다.


  • 원 - 타원 및 사각 - 사각형 시나리오는 좋지 않은 예입니다. 서브 클래스는 슈퍼 클래스보다 항상 복잡하므로 문제가 고안되었습니다. 이 문제는 관계를 뒤집어서 해결됩니다. 타원은 원에서 파생되고 사각형은 사각형에서 파생됩니다. 이러한 시나리오에서 구성을 사용하는 것은 매우 어리 석습니다. - Fuzzy Logic
  • @FuzzyLogic 동의했다. 그러나 실제로, 나의 지위는 결코이 경우에 구성을 사용하는 것을 옹호하지 않는다. 나는 원 - 타원 문제가 왜 "is-a"인 이유의 훌륭한 예라고 만 말했습니다. Circle이 Ellipse에서 파생되어야한다고 결론 지을 수있는 좋은 테스트는 아닙니다. 실제로 Circle은 LSP 위반으로 인해 Ellipse에서 파생되지 않아야한다고 결론을 내린 후에는 관계를 뒤집거나 구성을 사용하거나 템플릿 클래스를 사용하거나 추가 클래스 또는 도우미와 관련된보다 복잡한 디자인을 사용할 수 있습니다 기능 등 ... 결정은 분명히 사례별로 취해 져야합니다. - Boris
  • @FuzzyLogic 그리고 Circle-Ellipse의 특정한 경우에 대해 내가 옹호 할 것이 무엇인지 궁금한 점이 있다면 Circle 클래스를 구현하지 않을 것을 제안합니다. 관계를 뒤집을 때의 문제는 그것이 LSP에도 위배된다는 것입니다.computeArea(Circle* c) { return pi * square(c->radius()); }. Ellipse가 전달되면 분명히 손상됩니다 (radius ()는 무엇을 의미합니까?). 타원은 원이 아니므로 원에서 파생되지 않아야합니다. - Boris
  • computeArea(Circle *c) { return pi * width * height / 4.0; }이제는 일반적인 것입니다. - Fuzzy Logic
  • @FuzzyLogic 나는 동의하지 않는다 : 이것은 Circle 클래스가 파생 클래스 Ellipse의 존재를 예상 했으므로 제공된다는 것을 알았다.width()height()? 이제 라이브러리 사용자가 " EggShape "라는 다른 클래스를 만들면 어떻게 될까요? " 서클 "에서 파생되어야합니까? 당연히 아니지. 난형은 원이 아니며 타원도 원이 아니므로 LSP를 깨기 때문에 Circle에서 파생되지 않아야합니다. Circle * 클래스에서 작업을 수행하는 메서드는 원이 무엇인지에 대해 강력한 가정을 수행하며 이러한 가정을 위반하면 버그가 발생할 가능성이 큽니다. - Boris

12

항공기에 엔진과 날개의 두 부분 만 있다고 가정합니다.

그런 다음 항공기 클래스를 설계하는 두 가지 방법이 있습니다.

Class Aircraft extends Engine{
  var wings;
}

이제 항공기는 고정 날개를 가지고 시작할 수 있습니다.

비행 중에 회전 날개로 바꾸십시오. 본질적으로

날개가 달린 엔진. 하지만 내가 바꿀 수 있다면 어떨까요?

비행 중에도 엔진?

기본 클래스Enginemutator를 노출 시켜서

속성 또는 다시 디자인Aircraft같이:

Class Aircraft {
  var wings;
  var engine;
}

이제 비행 중에도 엔진을 교체 할 수 있습니다.


  • 게시물로 인해 이전에는 고려하지 않은 점이 나타납니다. 무기와 같은 여러 부분이있는 기계 물체의 유추를 계속하려면 일반적으로 일련 번호가 표시된 부분과 일련 번호가 고려됩니다 전체적으로 총기류가되도록 (권총의 경우 일반적으로 프레임이됩니다). 하나는 다른 모든 부품을 대체 할 수 있지만 여전히 동일한 무기를 가지고있을 수 있지만 프레임이 균열되어 교체해야하는 경우 원래 프레임에서 다른 프레임으로 새 프레임을 조립 한 결과 새로운 총이됩니다. 참고 사항 ... - supercat
  • ... 총의 여러 부분에 일련 번호가 표시되어 있다고해서 총이 여러 개의 ID를 가질 수있는 것은 아닙니다. 프레임의 일련 번호 만 총을 식별합니다. 다른 부품의 일련 번호는 해당 부품이 조립되도록 제조 된 총을 식별하며, 이는 지정된 시간에 조립 된 총이 아닐 수 있습니다. - supercat

9

"상속에 대한 구성을 선호"는 디자인 원칙입니다.이 원칙은 적합하지 않을 때 상속을 남용하지 말라는 의미입니다.

두 엔티티간에 실제 계층 적 관계가 없으면 상속을 사용하지 말고 대신 composition을 사용하십시오. 작문은 "HAS A"관계를 나타냅니다.

예 : 자동차에는 바퀴, 차체, 엔진 등이 있습니다. 그러나 여기에서 상속을 받으면 바퀴 달린 자동차가됩니다. 이는 잘못된 것입니다.

자세한 설명은 다음을 참조하십시오.상속보다 구성을 선호한다.


  • 보통 악의 핵심은 단순히 "자동차는 바퀴 다"는 것이 아닙니다. " 자동차는 4 륜구동, 스티어링 휠, 창문, 몸체, 좌석 등으로 무언가가 있습니다 ... 좋은 차가 가져야한다고 상상할 수있는 모든 것. " 보통 이것은 모든 다른 차를위한 전능 한 신 아버지 차인 BaseCar로 끝납니다. 일반적인 것이 아닌 차가 필요할 때까지 모든 것이 잘 작동합니다 (예 : 3 개의 바퀴가 달린 차 또는 스티어링 휠이없는 자동 차. 그래야만 모든 자동차에 가능한 모든 기능을 적용하는 것이 그다지 좋은 생각이 아닐 수도 있습니다. - JustAMartin
  • 송곳니와 개에 관한 것과 같은 이유로 좋은 대답은 아닙니다. 자동차 문제는 구성과 함께 더 솔직하게 해결 될 수 있습니다. BWM은 BMW 대신 자동차를 구현합니다. - markus

6

"복사"/ 기본 클래스의 API를 공개하려는 경우 상속을 사용합니다. 기능 만 "복사"하려면 위임을 사용하십시오.

그 중 하나의 예 :리스트에서 스택을 생성하려고합니다. 스택에는 팝, 푸시 및 픽이 있습니다. 스택에서 push_back, push_front, removeAt 등의 기능을 원하지 않는다면 상속을 사용해서는 안됩니다.


6

이 두 가지 방법은 서로 잘 살며 서로를 실제로 지원할 수 있습니다.

작곡은 단지 모듈 방식으로 진행됩니다 : 부모 클래스와 비슷한 인터페이스를 만들고, 새로운 객체를 만들고, 호출을 위임합니다. 이 물체가 서로를 알 필요가없는 경우 구성을 사용하는 것이 안전하고 쉽습니다. 여기에는 너무 많은 가능성이 있습니다.

그러나 어떤 이유로 든 부모 클래스가 경험이 부족한 프로그래머를 위해 "자식 클래스"에서 제공하는 함수에 액세스해야한다면 상속을 사용하기에 좋은 장소 인 것처럼 보일 수 있습니다. 부모 클래스는 자신 만의 추상 "foo ()"를 호출 할 수 있습니다.이 클래스는 하위 클래스로 덮어 쓰여진 다음 추상 기준에 값을 제공 할 수 있습니다.

좋은 생각 인 것 같지만 많은 경우에 클래스에 foo ()를 구현하는 객체를 주거나 (또는 foo ()를 수동으로 제공하는 값을 설정하는 것), 기본 클래스에서 새 클래스를 상속하는 것보다 낫습니다. 지정할 함수 foo ().

왜?

상속은 정보 이동에 대한 부적절한 방법이기 때문에.

그 구성은 다음과 같이 실질적인면을 가지고 있습니다. 관계가 바뀔 수 있습니다 : "부모 클래스"또는 "추상 작업자"는 특정 인터페이스를 구현하는 특정 "자식"객체를 집계 할 수 있습니다 +임의의 자식은 다른 유형의 부모 내부에서 설정 될 수 있으며 그 유형을 허용합니다. 개체 수에는 제한이 없습니다. 예를 들어 MergeSort 또는 QuickSort는 추상 비교 인터페이스를 구현하는 개체 목록을 정렬 할 수 있습니다. 또는 "foo ()"를 구현하는 객체 그룹과 "foo ()"를 갖는 객체를 사용할 수있는 객체 그룹을 함께 재생할 수 있습니다.

상속을 사용하는 세 가지 실제 이유를 생각해 볼 수 있습니다.

  1. 많은 수업이 있습니다.동일한 인터페이스그리고 그 (것)들을 쓰는 시간을 절약하고 싶다
  2. 각 객체에 대해 동일한 기본 클래스를 사용해야합니다.
  3. 어쨌든 공개 될 수없는 개인 변수를 수정해야합니다.

이것이 사실이라면 상속을 사용해야 할 것입니다.

이유 1을 사용할 때 나쁘지는 않습니다. 객체에 단단한 인터페이스를 갖는 것은 매우 좋은 일입니다. 이 인터페이스가 단순하고 변경되지 않으면 컴포지션 또는 상속을 사용하여 문제를 해결할 수 있습니다. 보통 상속은 여기에서 아주 효과적입니다.

그 이유가 2 번이라면 조금 까다 롭습니다. 정말로 동일한 기본 클래스 만 사용해야합니까? 일반적으로 같은 기본 클래스를 사용하는 것만으로는 충분하지 않지만 프레임 워크의 요구 사항 일 수 있습니다. 이러한 고려 사항은 피할 수없는 디자인 고려 사항입니다.

그러나 개인 변수 인 사례 3을 사용하려는 경우 문제가 발생할 수 있습니다.전역 변수가 안전하지 않다고 생각하면 상속을 사용하여 안전하지 않은 개인 변수에 액세스하는 것을 고려해야합니다. 글로벌 변수가 모두 나쁜 것은 아닐 것입니다. 데이터베이스는 본질적으로 글로벌 변수 세트입니다. 그러나 당신이 그것을 다룰 수 있다면, 그것은 꽤 괜찮습니다.


5

이외에도 고려해야 할 사항이 있습니다. 객체가 통과해야하는 상속의 "깊이"도 고려해야합니다. 5 단계 또는 6 단계의 상속 수준을 초과하는 경우 예기치 않은 캐스팅 및 복싱 / 언 박싱 문제가 발생할 수 있으며이 경우 개체를 대신 작성하는 것이 좋습니다.


5

이것을 이해하는 간단한 방법은 클래스의 객체가 동일한 객체를 가질 필요가있을 때 상속을 사용해야한다는 것입니다인터페이스그 결과 부모 클래스의 객체 (상향 캐스트)로 취급 될 수 있습니다. 또한 파생 클래스 객체에 대한 함수 호출은 코드의 모든 부분에서 동일하게 유지되지만 호출 할 특정 메소드는 런타임에 결정됩니다 (즉, 하위 수준이행다르다면, 상위 레벨인터페이스동일하게 유지됨).

새로운 클래스가 동일한 인터페이스를 가질 필요가 없을 때, 즉 해당 클래스의 사용자가 알 필요가없는 클래스 구현의 특정 측면을 숨길 때 컴포지션을 사용해야합니다. 구성이 지원의 방식에 더 있습니다.캡슐화(즉, 구현을 숨김), 상속은추출(즉, 무언가의 간단한 표현을 제공합니다.이 경우에는같은내부가 다른 여러 유형의 인터페이스).


  • +1 인터페이스에 대한 언급. 이 방법을 사용하여 기존 클래스를 숨기고 컴포지션에 사용 된 객체를 조롱하여 새로운 클래스를 제대로 테스트 할 수있게 만듭니다. 이를 위해서는 새 객체의 소유자가 대신 후보 부모 클래스를 전달해야합니다. - Kell

5

당신이~이다두 클래스 간의 관계 (개는 송곳니), 당신은 상속을 위해갑니다.

반면에 당신이 가지고있을 때가지고있다또는 두 클래스 (학생이 코스를 가짐) 또는 (교사 학습 코스) 사이에 형용사 관계를 선택한 경우 구성을 선택했습니다.


  • 당신은 상속과 유산을 말했습니다. 상속 및 구성을 의미하지 않습니까? - trevorKirkby
  • 아니요. 송곳 인터페이스를 정의하고 모든 개가 구현하도록하면 더 많은 SOLID 코드로 끝납니다. - markus

4

서브 타이핑은 적절하고 더 강력합니다.불변식이 열거 될 수있다., 확장 성을 위해 함수 합성을 사용하십시오.


4

@Pavel에 동의합니다. 그가 말하길, 작곡의 장소가 있고 상속받을 곳이 있습니다.

귀하의 대답이 이러한 질문에 긍정적 인 것이라면 상속을 사용해야한다고 생각합니다.

  • 당신의 클래스는 다형성의 혜택을받는 구조의 일부입니까? 예를 들어, draw ()라는 메서드를 선언하는 Shape 클래스가 있다면 Circle 및 Square 클래스가 Shape의 하위 클래스가되어야하므로 클라이언트 클래스는 특정 하위 클래스가 아닌 Shape에 종속됩니다.
  • 클래스가 다른 클래스에서 정의한 상위 수준의 상호 작용을 다시 사용해야합니까? 그만큼템플릿 방법디자인 패턴은 상속없이 구현할 수 없습니다. 모든 확장 가능한 프레임 워크가이 패턴을 사용한다고 생각합니다.

그러나 의도적으로 코드를 다시 사용하려는 의도가있는 경우 컴포지션이 가장 좋은 디자인 선택입니다.


3

내가 들었던 엄지 손가락의 규칙은 상속이 그것의 "a-a"관계와 구성에 사용될 때 사용해야한다는 것입니다. 그것으로도 복잡성을 많이 없애기 때문에 항상 작곡쪽으로 기울어 져야한다고 생각합니다.


2

상속은 코드 재사용을위한 매우 강력한 마카니즘입니다. 그러나 제대로 사용되어야합니다. 하위 클래스가 부모 클래스의 하위 유형이기도 한 경우 상속이 올바르게 사용된다고 말할 수 있습니다. 위에서 언급했듯이 Liskov Substitution Principle이 핵심 요소입니다.

서브 클래스는 서브 타입과 동일하지 않습니다. 하위 유형이 아닌 하위 클래스를 만들 수 있습니다 (컴포지션을 사용해야하는 경우). 하위 유형이 무엇인지 이해하려면 유형이 무엇인지에 대한 설명을 시작하십시오.

숫자 5가 integer 유형이라고하면 5가 가능한 값 집합에 속한다는 것을 알 수 있습니다 (예를 들어 Java 기본 유형에 가능한 값 참조). 또한 우리는 더하기와 빼기와 같은 값에 대해 수행 할 수있는 유효한 방법이 있음을 설명합니다. 마지막으로 우리는 항상 만족되는 속성 집합이 있다고 말하고 있습니다. 예를 들어 값 3과 5를 추가하면 결과로 8이 생깁니다.

또 다른 예를 들어, 추상 데이터 유형 인 정수 세트 및 정수 목록에 대해 생각해 볼 수있는 값은 정수로 제한됩니다. 둘 다 add (newValue) 및 size ()와 같은 메소드 세트를 지원합니다. 그리고 그들은 둘 다 서로 다른 프로퍼티 (클래스 불변)를 가지며, Set는 중복을 허락합니다. 물론 List는 중복을 허용합니다 (물론 두 프로퍼티 모두 만족합니다).

하위 유형은 상위 유형 (또는 상위 유형)이라고하는 다른 유형과의 관계가있는 유형이기도합니다. 하위 유형은 상위 유형의 기능 (값, 메소드 및 특성)을 충족시켜야합니다. 관계는 상위 유형이 예상되는 모든 컨텍스트에서 실행 동작에 영향을 미치지 않고 하위 유형으로 대체 할 수 있음을 의미합니다. 제가 말하는 것을 보여주기위한 몇 가지 코드를 보도록하겠습니다. 정수리스트를 작성한다고 가정합니다.

class List {
  data = new Array();

  Integer size() {
    return data.length;
  }

  add(Integer anInteger) {
    data[data.length] = anInteger;
  }
}

그런 다음 정수 목록을 정수 목록의 하위 클래스로 씁니다.

class Set, inheriting from: List {
  add(Integer anInteger) {
     if (data.notContains(anInteger)) {
       super.add(anInteger);
     }
  }
}

정수 클래스의 집합은 List of Integers의 하위 클래스이지만 List 클래스의 모든 기능을 만족하지 못하기 때문에 하위 유형이 아닙니다. 값과 메서드의 서명은 만족되지만 속성은 그렇지 않습니다. add (Integer) 메서드의 동작은 부모 형식의 속성을 유지하지 않고 명확하게 변경되었습니다. 수업 시간에 고객의 관점에서 생각해보십시오. 그들은 정수 목록이 예상되는 정수 집합을받을 수 있습니다. 클라이언트는 값을 추가하고 List에 값이 이미있는 경우에도 그 값을 List에 추가하려고 할 수 있습니다. 그러나 가치가 존재한다면 그녀는 그 행동을 얻지 못할 것입니다. 그녀를 위해 큰 깜짝 선물!

이것은 상속을 부적절하게 사용하는 고전적인 예입니다. 이 경우 구성을 사용하십시오.

(단편 :상속을 제대로 사용한다.).


2

구성 v / s 상속은 광범위한 주제입니다. 모든 것이 시스템의 설계에 달려 있다고 생각할 때 더 나은 점에 대한 진정한 대답은 없습니다.

일반적으로 객체 간의 관계 유형은 더 나은 정보를 제공하여 그 중 하나를 선택합니다.

관계 유형이 "IS-A"관계이면 상속이 더 나은 접근 방법입니다. 그렇지 않으면 관계 유형이 "HAS-A"관계이고 구성이 더 잘 접근합니다.

그것은 엔티티 관계에 전적으로 의존합니다.


2

새로운 프로그래머를위한 다른 관점에서이 질문을 해결하려면 다음을 수행하십시오.

상속은 종종 객체 지향 프로그래밍을 배울 때 조기에 가르쳐지기 때문에 일반적인 문제에 대한 쉬운 해결책으로 간주됩니다.

모든 공통 기능이 필요한 세 가지 클래스가 있습니다. 그래서 내가   기본 클래스를 작성하고 모든 클래스에서 상속 받도록 설정하면됩니다.   모든 기능이 있으며 한 번에 유지 관리 만하면됩니다.   장소.

훌륭하게 들리지만 실제로는 결코 작동하지 않습니다. 몇 가지 이유 중 하나가 있습니다.

  • 우리는 우리가 수업에 갖기를 바라는 몇 가지 다른 기능이 있음을 발견합니다. 클래스에 기능을 추가하는 방법이 상속을 통해 이루어지는 경우, 상속받은 모든 클래스가 해당 기능을 필요로하지는 않더라도 기존 기본 클래스에 추가 할 것인지 결정해야합니다. 다른 기본 클래스를 만들지 않습니까? 그러나 다른 기본 클래스에서 이미 상속받은 클래스는 어떻게됩니까?
  • 우리는 기본 클래스에서 상속받은 클래스 중 단 하나의 클래스에서 기본 클래스가 조금 다르게 동작하기를 바랍니다. 이제 우리는 기본 클래스로 돌아가서 가상 메소드를 추가하거나 심지어 더 나쁜 코드를 작성합니다. "유형 A를 상속 받았다면이 방법을 사용하십시오. 그러나 유형 B를 상속 받았다면 . " 그것은 여러 가지 이유로 나쁘다. 하나는 기본 클래스를 변경할 때마다 모든 상속 된 클래스를 효과적으로 변경한다는 것입니다. 그래서 우리는 클래스 A에서 약간 다른 행동이 필요하기 때문에 클래스 A, B, C 및 D를 실제로 변경하고 있습니다. 우리가 생각하는대로 조심스럽게, 클래스들 중 하나를 깨뜨릴 수 있습니다 수업.
  • 우리는 왜이 모든 클래스가 서로 상속되도록하기로 결정했는지 알지만, 코드를 유지해야하는 다른 사람에게는 이해가되지 않을 수도 있습니다. 우리는 그 (것)들을 어려운 선택으로 강제 할지도 모른다 - 나가 진짜로 추악하고 지저분한 무언가를 나가 필요로하는 변화를 만들기 위하여 하는가 (이전 총알 점을보십시오) 나는 다만 이것의 낱단을 다시 쓴다.

결국 우리는 코드를 어려운 매듭에 묶어두고 "멋진, 상속에 대해 배웠고 지금은 그것을 사용했습니다"라고 말하면서는 아무런 이점도 얻지 못합니다. 그것은 우리가 모두 해냈기 때문에 겸손하게하기위한 것이 아닙니다. 그러나 아무도 우리에게하지 말라고했기 때문에 우리 모두 그것을했습니다.

누군가가 "상속에 대한 상속 구성"을 나에게 설명하자마자, 상속을 사용하여 클래스간에 기능을 공유하려고 할 때마다 그 사실이 실제로 잘 작동하지 않는다는 것을 깨달았습니다.

해독제는단일 책임 원칙. 그것을 제약으로 생각하십시오. 내 수업절대로 필요한 것한 가지해라. 나는절대로 필요한 것내 수업에 어떻게 든 그 일을 설명하는 이름을 줄 수 있어야합니다. (모든 것에 예외가 있지만 절대 규칙은 때때로 우리가 배울 때 더 좋습니다.)ObjectBaseThatContainsVariousFunctionsNeededByDifferentClasses. 내가 필요한 고유 한 기능이 무엇이든간에 자체 클래스에 있어야하며 해당 기능을 필요로하는 다른 클래스는 해당 클래스에 의존 할 수 있습니다.아니그것으로부터 상속 받는다.

지나치게 단순화 될 위험에 처해있는 것은 복합적입니다. 여러 클래스가 함께 작동합니다. 일단 우리가 습관을 형성하면 상속을 사용하는 것보다 훨씬 더 유연하고 유지 보수가 용이하며 테스트 할 수 있다는 것을 알게됩니다.


  • 클래스가 여러 기본 클래스를 사용할 수없는 것은 상속에 대한 반영이 아니라 특정 언어의 기능 부족에 대한 반성입니다. - iPherian
  • 이 답변을 작성한 이래로 나는이 포스트" 아저씨 밥 " 능력 부족을 해결합니다. 필자는 다중 상속을 허용하는 언어를 사용하지 않았습니다. 그러나 되돌아 보면, 문제는 "언어 불가지론 자 (language agnostic)"라는 태그가 붙여져있다. 내 대답은 C #으로 가정합니다. 나는 내 시야를 넓힐 필요가있다. - Scott Hannen

1

많은 사람들이 말했듯이, 나는 먼저 수표를 가지고 시작할 것인데, "is-a"관계가 있는지 여부. 그것이 존재한다면 나는 보통 다음을 점검한다.

기본 클래스를 인스턴스화 할 수 있는지 여부입니다. 즉, 기본 클래스가 추상이 아닌지 여부입니다. 그것이 비 추상적 일 수있는 경우에 나는 보통 구성을 선호한다.

예 : 1. 회계사이다종업원. 그러나 전 할겁니다아니Employee 객체를 인스턴스화 할 수 있기 때문에 상속을 사용하십시오.

예 : 2. 도서~이다.SellingItem. SellingItem은 인스턴스화 될 수 없습니다. 이는 추상적 개념입니다. 그러므로 나는 inheritacne을 사용할 것이다. SellingItem은추상 기본 클래스 (또는 인터페이스C #에서)

이 접근법에 대해 어떻게 생각하십니까?

또한 @anon 응답을 지원합니다.왜 상속을 사용합니까?

상속을 사용하는 주요 이유는 구성의 한 형태가 아니기 때문에 다형성 (polymorphic) 동작을 얻을 수 있습니다. 다형성이 필요 없다면 상속을 사용해서는 안됩니다.

@ 마티유. ~에서 말한다.https://softwareengineering.stackexchange.com/questions/12439/code-smell-inheritance-abuse/12448#comment303759_12448

상속 문제는 두 가지 직교 목적으로 사용할 수 있다는 것입니다.

인터페이스 (다형성 용)

구현 (코드 재사용)

참고

  1. 어느 클래스 디자인이 더 나은가요?
  2. 상속 vs. 집계


  • 기본 클래스가 추상 클래스 인 이유를 모르겠습니다. ' LSP : 푸들 객체가 전달되면 Dogs에서 작동하는 모든 함수가 작동합니까? 그렇다면 푸들은 개를 대신 할 수 있으므로 개를 상속받을 수 있습니다. - Gishu
  • @ Gishu 고마워. 나는 확실히 LSP를 들여다 볼 것이다. 그러나 그 전에 "기본 클래스가 추상이 될 수없는 상속이 적절한 예제"를 제공해 주실 수 있습니까? 내 생각에 기본 클래스가 추상 클래스 인 경우에만 상속이 적용됩니다. 기본 클래스를 별도로 인스턴스화해야하는 경우 상속을 위해 이동하지 마십시오. 즉, Accountant가 Employee 임에도 불구하고 상속을 사용하지 마십시오. - Lijo
  • 최근에 WCF를 읽었습니다. .net 프레임 워크의 예제는 대기열이 ThreadPool 스레드에서 작동하는 SynchronizationContext (기본 + 인스턴스화 가능)입니다. 파생물에는 WinFormsSyncContext (UI 스레드에 대기열) 및 DispatcherSyncContext (WPF Dispatcher에 대한 대기열) - Gishu
  • @ Gishu 고마워. 그러나 은행 도메인, HR 도메인, 소매 도메인 또는 다른 널리 사용되는 도메인을 기반으로 시나리오를 제공 할 수 있다면 더 유용 할 것입니다. - Lijo
  • 죄송합니다. 이러한 도메인에 익숙하지 않습니다. 이전 예제가 너무 둔한 경우 Winforms / WPF의 Control 클래스가 또 다른 예입니다. 기본 / 제네릭 컨트롤을 인스턴스화 할 수 있습니다. Derivations에는 Listboxes, Textboxes 등이 포함됩니다. 이제 생각해 보겠습니다. Decorator Design 패턴은 IMHO와 유용한 예제입니다. 데코레이터는 비 추상적 인 객체에서 래핑 / 장식하려고합니다. - Gishu

1

작곡이 선호 되더라도, 나는계승및의 죄수구성.

상속의 장점 :

  1. 그것은 논리적 "IS A "관계. 만약트럭두 종류의차량(기본 클래스), 하위 클래스IS기본 클래스.

    자동차는 자동차입니다.

    트럭은 차량입니다.

  2. 상속을 사용하면 기능을 정의 / 수정 / 확장 할 수 있습니다.

    1. 기본 클래스는 구현을 제공하지 않으며 하위 클래스는 완전한 메소드 (abstract)를 재정의해야 함 =>계약을 구현할 수 있습니다.
    2. 기본 클래스는 기본 구현을 제공하고 하위 클래스는 동작을 변경할 수 있습니다 =>계약을 다시 정의 할 수 있습니다.
    3. 하위 클래스는 super.methodName ()을 첫 번째 명령문으로 호출하여 기본 클래스 구현에 확장을 추가합니다. =>계약을 연장 할 수 있습니다.
    4. 기본 클래스는 알고리즘의 구조를 정의하고 하위 클래스는 algorithm =>의 일부를 오버라이드합니다.구현할 수 있습니다.Template_method기본 클래스 스켈레톤을 변경하지 않고

작곡 단점 :

  1. 상속에서 기본 클래스 메서드를 구현하지 않아도 하위 클래스에서 직접 기본 클래스 메서드를 호출 할 수 있습니다.IS관계. 컴포지션을 사용하는 경우 컨테이너 클래스에 메소드를 추가하여 포함 된 클래스 API를 노출해야합니다.

예 : 만약포함하다차량그리고 만약 당신이,에서 정의 된차량, 당신의 코드는 다음과 같을 것이다.

class Vehicle{
     protected double getPrice(){
          // return price
     }
} 

class Car{
     Vehicle vehicle;
     protected double getPrice(){
          return vehicle.getPrice();
     }
} 


  • 질문에 답변이 없다고 생각합니다. - almanegra
  • OP 질문을 다시 볼 수 있습니다. 나는 각 접근법에 대해 어떤 절충점이 존재 하는가? - Ravindra babu
  • 언급 한 바와 같이, 당신은 "구성의 상속과 단점에 대한 찬성"에 대해서만 말하며, 각각의 접근법에 대한 절충이나 서로를 사용해야하는 경우가 아닙니다 - almanegra
  • 장단점은 상속의 프로가 구성의 단점이고 구성의 단점이 상속의 프로이기 때문에 절충점을 제공합니다. - Ravindra babu

연결된 질문


관련된 질문

최근 질문