상속에 비해 컴포지션을 선호하는 이유는 무엇입니까? 각 접근법에는 어떤 절충점이 있습니까? 작곡에 대한 상속을 언제 선택해야합니까?
상속보다 컴포지션이 좋고 나중에 수정하기 쉽기 때문에 상속보다 컴포지션을 선호하지만 항상 구성 방식을 사용하지 마십시오.구성을 사용하면 Dependency Injection / Setters를 사용하여 신속하게 동작을 변경할 수 있습니다. 대부분의 언어가 하나 이상의 유형에서 파생되도록 허용하지 않으므로 상속이보다 엄격 해집니다. Type A에서 파생되면 거위가 다소 조리됩니다.
위 산성 테스트는 다음과 같습니다.
예 : Cessna 복엽 비행기는 더 이상 비행기의 완전한 인터페이스를 노출하지 않을 것입니다. 그래서 그것이 비행기에서 파생되기에 적합합니다.
예 : 새는 비행기의 비행 행동 만 필요할 수 있습니다. 이 경우 인터페이스 / 클래스 / 모두로 추출하여 두 클래스 모두의 구성원으로 만드는 것이 좋습니다.
최신 정보:그냥 내 대답으로 돌아 왔고 Barbara Liskov 's에 대한 언급이 없으면 불완전 해 보인다.리 스키프 대치 원리'내가이 유형을 상속 받아야 하는가?'에 대한 테스트로
봉쇄를~을 가지고있다.관계. 자동차에는 "엔진이 있고 사람은"이름이 있습니다.
상속을~이다.관계. 자동차 "는"차량 ","사람 "은"포유류 "등입니다.
나는이 접근법에 대해 어떤 공로도 인정하지 않는다. 나는 그것을에서 바로 잡았다.코드 완성의 두 번째 버전으로스티브 맥코넬,섹션 6.3.
차이점을 이해한다면 설명하기가 더 쉽습니다.
예를 들어 클래스를 사용하지 않는 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으로 구성됩니다. 제목 동작은 직원에게서 취해진 것입니다. 이 명시적인 구성은 다른 것들 사이에 모호성을 제거하고 당신은 더 적은 버그를 접하게 될 것입니다.
상속에 의해 제공되는 부인할 수없는 모든 혜택을 통해 여기에 몇 가지 단점이 있습니다.
상속의 단점 :
반면에오브젝트 구성런타임에 다른 객체에 대한 참조를 가져 오는 객체를 통해 정의됩니다. 이러한 경우 이러한 객체는 서로의 보호 된 데이터 (캡슐화 중단 없음)에 절대 도달 할 수 없으며 서로의 인터페이스를 존중해야합니다. 그리고이 경우에도 구현 종속성은 상속의 경우보다 훨씬 적습니다.
상속에 비해 컴포지션을 선호하는 또 다른 매우 실용적인 이유는 도메인 모델과 관계형 데이터베이스에 매핑해야하기 때문입니다. 상속을 SQL 모델에 매핑하는 것은 정말 어렵습니다 (항상 사용되지 않는 열 만들기,보기 사용 등 모든 종류의 해킹 해결 방법이 필요합니다). 일부 ORML은이를 처리하려고 시도하지만 항상 복잡해진다. 구성은 두 테이블 간의 외래 키 관계를 통해 쉽게 모델링 될 수 있지만 상속은 훨씬 어렵습니다.
간단히 말해서 나는 "상속에 대한 구성을 선호한다"와 동의 할 것이지만, 종종 "코카콜라에 비해 감자를 좋아한다"는 말처럼 들립니다. 상속 장소와 작곡 장소가 있습니다. 차이점을 이해해야합니다. 그러면이 질문은 사라질 것입니다. 내게 정말로 의미하는 것은 "당신이 상속을 사용한다면 다시 생각해보십시오, 기회는 당신에게 구성이 필요합니다"입니다.
당신은 먹고 싶을 때 코카콜라에 감자를, 음료를 마실 때 감자에 코카콜라를 선호해야합니다.
서브 클래스를 생성하는 것은 수퍼 클래스 메소드를 호출하는 편리한 방법 이상을 의미해야합니다. 수퍼 클래스로 사용할 수 있고 그것을 사용하려고 할 때 "is-a"수퍼 클래스를 구조적 및 기능적으로 하위 클래스로 만들려면 상속을 사용해야합니다. 그것이 사실이 아니라면 - 상속이 아니라 다른 것입니다. 구성은 객체가 다른 객체로 구성되거나 객체와 어떤 관계가있는 경우입니다.
따라서 저에게 누군가 상속이나 작곡이 필요한지 모르는 것처럼 보이는 경우, 실제로 문제는 그가 마시고 먹고 싶은지 알 수 없다는 것입니다. 문제 영역에 대해 더 많이 생각하고 더 잘 이해하십시오.
InternalCombustionEngine
파생 된 클래스로GasolineEngine
. 후자는 기본 클래스에서 부족한 점화 플러그와 같은 것을 추가하지만InternalCombustionEngine
점화 플러그가 사용되게 할 것입니다. - supercat
상속은 특히 절차 적 토지에서 유래하는 것을 유혹하며 흔히 사기성으로 보입니다. 다른 클래스에이 기능을 추가하는 것뿐입니다. 음, 문제 중 하나는
기본 클래스는 구현 된 세부 사항을 보호 된 멤버의 형태로 하위 클래스에 노출시켜 캡슐화를 중단합니다. 이로 인해 시스템이 단단하고 약해집니다. 그러나 더 비극적 인 결점은 새로운 하위 클래스가 모든 유산과 상속 사슬의 의견을 가져다 준다는 것입니다.
기사,상속은 악이다 : DataAnnotationsModelBinder의 서사시C #에서이 예제를 살펴 봅니다. 그것은 구성이 사용되어 져야하고 그것이 어떻게 리팩토링 될 수 있었는지 상속의 사용을 보여줍니다.
Java 또는 C #에서는 객체가 인스턴스화되면 객체의 유형을 변경할 수 없습니다.
따라서 개체를 다른 개체로 표시해야하거나 개체 상태 나 조건에 따라 다르게 동작해야하는 경우에는구성: 인용하다상태과계략디자인 패턴.
객체가 같은 유형이어야한다면, 다음을 사용하십시오.계승또는 인터페이스를 구현할 수 있습니다.
Client
. 그런 다음 새로운 개념의PreferredClient
나중에 팝업. 할까요PreferredClient
상속하다Client
? 선호하는 고객은 ' 클라이언트 afterall, 안돼? 음, 그렇게 빠르지는 않습니다 ... 당신이 말했듯이, 객체는 런타임에 클래스를 변경할 수 없습니다. 어떻게 모델링하겠습니까?client.makePreferred()
조작? 아마도 해답은 누락 된 개념의 구성을 사용하는 것입니다.Account
혹시? - plalxClient
클래스의 개념을 캡슐화 한 것일 수도 있습니다.Account
그것은StandardAccount
또는PreferredAccount
... - plalx
개인적으로 나는 상속에 비해 항상 구성을 선호하는 법을 배웠습니다. 컴포지션으로 해결할 수없는 상속으로 해결할 수있는 프로그램 적 문제는 없습니다. 경우에 따라 인터페이스 (Java) 또는 프로토콜 (Obj-C)을 사용해야 할 수도 있습니다. C ++은 그런 것을 모르기 때문에 추상 기본 클래스를 사용해야합니다. 즉, C ++에서 상속을 완전히 제거 할 수 없습니다.
컴포지션은 종종 더 논리적이며 더 우수한 추상화, 더 나은 캡슐화, 더 큰 코드 재사용 (특히 대규모 프로젝트의 경우)을 제공하며 코드에서 아무 곳이나 격리 된 변경을 수행했기 때문에 먼 거리에서 아무 것도 부러 뜨릴 가능성이 적습니다. 또한 "단일 책임 원칙"는 종종"수업을 변경하는 데는 한 가지 이상의 이유가 없어야합니다."라는 말은 모든 클래스가 특정 목적을 위해 존재하며 그 목적과 직접적으로 관련이있는 메소드를 가지고 있어야한다는 것을 의미합니다. 또한 매우 얕은 상속 트리를 사용하면 프로젝트가 실제로 시작될 때도 개요를 유지하는 것이 훨씬 쉬워집니다 많은 사람들은 상속이 우리를 대표한다고 생각합니다.현실 세계꽤 잘,하지만 그것은 진실이 아닙니다. 현실 세계는 상속보다 훨씬 더 많은 구성을 사용합니다. 손에 넣을 수있는 거의 모든 실제 세계 물체는 다른보다 작은 실제 세계 물체로 구성되었습니다.
그러나 조성의 단점이 있습니다. 상속을 완전히 건너 뛰고 구성에만 초점을두면 상속을 사용한 경우 필요하지 않은 몇 가지 추가 코드 줄을 작성해야한다는 것을 알게됩니다. 때때로 당신은 또한 반복적으로 반복해야하며 이것은건조한 원리(DRY = 반복하지 마십시오). 또한 컴포지션은 종종 위임을 요구하며 메서드는이 호출을 둘러싼 다른 코드가없는 다른 객체의 다른 메서드를 호출하기 만합니다. 이러한 "double method calls"(쉽게 3 배 또는 4 배의 메서드 호출로 확장 될 수 있으며 그보다 훨씬 더 멀리있을 수 있음)는 부모의 메서드를 단순히 상속받는 상속보다 훨씬 성능이 떨어집니다. 상속 된 메서드를 호출하는 것은 상속되지 않은 메서드를 호출하는 것과 똑같은 속도가 될 수도 있고 약간 느릴 수도 있지만 대개 두 번의 연속 메서드 호출보다 훨씬 빠릅니다.
대부분의 OO 언어가 다중 상속을 허용하지 않는다는 것을 알았을 것입니다. 다중 상속이 실제로 당신에게 무언가를 살 수있는 몇 가지 경우가 있지만, 이는 규칙보다 다소 예외적 인 경우입니다. "다중 상속이이 문제를 해결하는 데 정말로 멋진 기능이 될 것"이라고 생각하는 상황에 처할 때마다 일반적으로 상속을 다시 생각해야합니다. 두 번 더 추가 코드 줄이 필요할 수도 있기 때문입니다 , 구성에 기반한 솔루션은 일반적으로 훨씬 더 우아하고 유연하며 미래의 증거가 될 것입니다.
상속은 정말 멋진 기능이지만, 지난 2 년 동안 남용 된 것 같습니다. 사람들은 상속을 실제로 손톱, 나사, 아니면 완전히 다른 무언가인지에 상관없이 모든 것을 못 박는 하나의 망치로 간주했습니다.
여기서 만족스러운 답변을 찾지 못했기 때문에 새로 작성했습니다.
왜 "취하다구성 상속 ", 우리는 먼저이 단축 된 관용구에서 생략 된 가정을 되 찾을 필요가있다.
상속의 두 가지 이점이 있습니다.하위 유형 지정 및 서브 클래 싱
서브 타이핑유형 (인터페이스) 서명, 즉 API 집합을 따르는 것을 의미하며, 하위 유형 다형성을 달성하기 위해 서명의 일부를 대체 할 수 있습니다.
하위 클래스메소드 구현의 암시 적 재사용을 의미합니다.
두 가지 이점을 통해 상속을 수행하는 두 가지 목적이 있습니다. 하위 유형 지정 지향 및 코드 재사용 지향입니다.
코드 재사용이바닥하위 클래스 화는 자신이 필요로하는 것 이상을 제공 할 수 있습니다. 즉, 상위 클래스의 일부 공용 메소드는 하위 클래스에 대해별로 의미가 없습니다. 이 경우, 상속에 비해 composition을 선호하는 대신, composition은요구 한. 이것은 "is-a"대 "has-a"개념의 출처이기도합니다.
따라서 하위 유형 지정이 목적 일 때, 즉 나중에 다형성 방식으로 새 클래스를 사용하는 경우에만 상속 또는 구성을 선택하는 문제에 직면하게됩니다. 이것은 논의 된 단축 관용구에서 생략된다는 가정이다.
하위 유형은 유형 서명을 따르는 것인데, 이는 컴포지션이 항상 해당 유형의 API를 적게 노출 함을 의미합니다. 이제 트레이드 오프가 시작됩니다.
상속은 오버라이드되지 않으면 직접 코드를 재사용 할 수있는 반면 컴포지션은 위임의 단순한 작업 일지라도 모든 API를 다시 코딩해야합니다.
상속은 간단합니다.열린 재귀내부 다형성 사이트를 통해this
, 즉 오버라이드 메소드 호출 (또는 심지어유형) 다른 멤버 함수에서 공개 또는 비공개 (비록낙담 한). 공개 재귀가 될 수 있습니다조성을 통해 시뮬레이션 됨, 그러나 그것은 여분의 노력이 필요하며 항상 실행 가능하지는 않습니다 (?). 이대답중복 된 질문에 비슷한 이야기.
상속 노출보호 된회원. 이렇게하면 부모 클래스의 캡슐화가 중단되고 하위 클래스에서 사용되는 경우 하위와 상위 간의 다른 종속성이 도입됩니다.
컴포지션은 컨트롤의 반전에 잘 맞으며, 종속성은 다음과 같이 동적으로 주입 될 수 있습니다.데코레이터 패턴과프록시 패턴.
작곡 직후인터페이스 프로그래밍.
작곡에는 쉬운 이점이 있습니다.다중 상속.
위의 트레이드 오프를 염두에두고 우리는취하다상속 이상의 구성. 그러나 밀접한 관련 클래스의 경우, 즉 암시 적 코드 재사용이 실제로 이익을 창출하거나 개방 재귀의 마법력이 요구되는 경우, 상속이 선택됩니다.
나의 일반적인 경험 법칙 :상속을 사용하기 전에 구성이 더 합리적인지 고려하십시오.
이유:서브 클래 싱은 일반적으로 실수를하지 않고도 변경, 유지 및 확장하기가 더 어려워 진 복잡성과 연결성을 의미합니다.
훨씬 더 완전하고 구체적인Tim Boudreau의 답변태양의 :
내가보기에 상속을 사용하는 데 공통적 인 문제는 다음과 같습니다.
- 무고한 행위로 인해 예상치 못한 결과가 발생할 수 있습니다.- 이것의 고전적인 예는 수퍼 클래스에서 오버라이드 할 수있는 메소드를 호출하는 것입니다 constructor. 서브 클래스의 인스턴스 필드가 초기화 됨. 완벽한 세상에서 아무도 그렇게하지 않을 것입니다. 이것은 완벽한 세계가 아닙니다.
- 서브 클래스가 메소드 호출의 순서와 그와 같은 가정에 대해 유혹하는 유혹을 제공합니다.- 그러한 가정은 수퍼 클래스가 시간이 지남에 따라 발전한다면 안정적이어야한다. 또한보십시오내 토스터기 커피 포트 유추.
- 클래스가 무거워진다.- 수퍼 클래스가 생성자에서 수행중인 작업 또는 사용중인 메모리의 양을 반드시 알 필요는 없습니다. 쓰다. 무고한 가벼운 객체를 만들면 생각보다 훨씬 비싸고, 시간이 지남에 따라 바뀔 수 있습니다. 수퍼 클래스가 진화한다.
- 하위 클래스의 폭발을 조장합니다.. 클래스 로딩 비용은 시간이 걸리고 클래스가 많을수록 메모리 비용이 많이 든다. 당신이되기 전까지는 문제가되지 않을 수도 있습니다. NetBeans 규모의 앱을 다루고 있지만, 실제로는 예를 들어 메뉴가 느린 문제는 첫 번째 디스플레이 메뉴가 대량의 클래스 로딩을 유발했다. 우리는 다음으로 이동하여이 문제를 해결했습니다. 더 많은 선언적 구문 및 기타 기법을 사용하지만 그로 인해 고친다.
- 나중에 상황을 변경하는 것이 더 힘들어집니다.- 클래스를 public으로 만든 경우, 수퍼 클래스를 바꾼다는 것은 하위 클래스를 깰 것이고, 일단 코드를 공개하면 결혼하게됩니다. 에. 따라서 실제 기능을 변경하지 않는 경우 수퍼 클래스를 사용하면 나중에 변경할 수있는 자유가 훨씬 늘어납니다. 사용하는 것이 아니라 필요한 것을 확장하십시오. 예를 들어, JPanel을 서브 클래 싱하는 것은 일반적으로 잘못되었습니다. 서브 클래스가 대중에게 어딘가에, 당신은 그 결정을 다시 방문 할 수있는 기회를 얻지 못합니다. 만약 JComponent getThePanel ()로서 액세스 할 수 있습니다 만, 아직 할 수 있습니다 (힌트 : API로 구성 요소의 모델을 노출).
- 객체 계층 구조는 확장되지 않습니다 (나중에 스케일을 적용하는 것이 미리 계획하는 것보다 훨씬 어렵습니다)- 이것은 고전적인 "너무 많은 층"입니다. 문제. 아래에서이 부분을 살펴보고 AskTheOracle 패턴이 그것을 해결하십시오 (OOP 순수 주의자를 화나게 할지도 모르다).
...
당신이 상속을 허용한다면, 무엇을해야 할지를 제 견해로 삼으십시오. 소금 한 알을 먹으면 :
- 상수를 제외한 모든 필드를 노출시키지 마십시오.
- 방법은 추상 또는 최종
- 수퍼 클래스 생성자에서 아무 메소드도 호출하지 않습니다.
...
이 모든 것은 큰 프로젝트보다 작은 프로젝트에 적게 적용됩니다. 공개 수업보다 개인 수업에
상속은 하위 클래스와 수퍼 클래스간에 강력한 관계를 만듭니다. 서브 클래스는 수퍼 클래스의 구현 세부 사항을 알고 있어야합니다. 수퍼 클래스를 만드는 일은 어떻게 확장 할 수 있을지 생각해야 할 때 훨씬 더 어렵습니다. 클래스 불변량을 신중하게 문서화하고 오버라이드 가능한 메서드가 내부적으로 사용하는 다른 메서드를 설명해야합니다.
상속은 실제로 계층 구조가 is-a-relationship을 나타내는 경우 유용합니다. 이것은 Open-Closed Principle과 관련이 있으며 클래스는 수정을 위해 닫히고 확장은 개방되어야한다고 명시합니다. 그렇게하면 다형성을 가질 수 있습니다. 슈퍼 타입과 그 메소드를 다루는 일반적인 메소드를 가지지 만, 다이나믹 디스패치를 통해 서브 클래스의 메소드가 호출된다. 이는 유연하며 간접 참조를 생성하는 데 도움이됩니다. 간접 참조는 소프트웨어에서 필수적입니다 (구현 세부 정보가 적다).
하지만 상속은 쉽게 남용되지만 클래스 간 딱딱한 종속성으로 인해 복잡성이 추가로 발생합니다. 또한 프로그램 실행 중에 어떤 일이 일어나는지 이해하는 것은 레이어와 동적 메서드 호출 선택으로 인해 상당히 어려워집니다.
작곡을 기본값으로 사용할 것을 제안합니다. 더 모듈화되어 있으며 후기 바인딩의 이점을 제공합니다 (동적으로 구성 요소를 변경할 수 있음). 또한 개별적으로 테스트하는 것이 더 쉽습니다. 그리고 당신이 수업에서 방법을 사용해야하는 경우, 당신은 특정 형태 (Liskov 대체 원리)가되어서는 안됩니다.
Inheritance is sometimes useful... That way you can have polymorphism
상속과 다형성의 개념을 하드 링크하는 것으로서 (문맥에 따라 가정 된 하위 유형 지정). 내 의견은 당신이 논평 한 내용을 지적하기위한 것이 었습니다. 상속은 다형성을 구현하는 유일한 방법이 아니며 실제로 구성과 상속 사이를 결정할 때 반드시 결정 요소는 아닙니다. - BitMask777
다른 답변을 참조하십시오.
문장이 나올 때마다"바는 푸 (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는 상속을 사용할 수 없기 때문에 상속을 사용하지 않아야합니다. 즉, 상속은 속성을 공유하는 것이 아니라 기능을 공유하는 것입니다. 파생 클래스는 다형 적으로 사용됩니다.넓히다기본 클래스의 기능을 제한하지 않습니다.
이것은리 스키프 대치 원리:
기본 클래스에 대한 포인터 또는 참조를 사용하는 함수는 파생 클래스의 객체를 알지 못해도 사용할 수 있어야합니다.
computeArea(Circle* c) { return pi * square(c->radius()); }
. Ellipse가 전달되면 분명히 손상됩니다 (radius ()는 무엇을 의미합니까?). 타원은 원이 아니므로 원에서 파생되지 않아야합니다. - BoriscomputeArea(Circle *c) { return pi * width * height / 4.0; }
이제는 일반적인 것입니다. - Fuzzy Logicwidth()
과height()
? 이제 라이브러리 사용자가 " EggShape "라는 다른 클래스를 만들면 어떻게 될까요? " 서클 "에서 파생되어야합니까? 당연히 아니지. 난형은 원이 아니며 타원도 원이 아니므로 LSP를 깨기 때문에 Circle에서 파생되지 않아야합니다. Circle * 클래스에서 작업을 수행하는 메서드는 원이 무엇인지에 대해 강력한 가정을 수행하며 이러한 가정을 위반하면 버그가 발생할 가능성이 큽니다. - Boris
항공기에 엔진과 날개의 두 부분 만 있다고 가정합니다.
그런 다음 항공기 클래스를 설계하는 두 가지 방법이 있습니다.
Class Aircraft extends Engine{
var wings;
}
이제 항공기는 고정 날개를 가지고 시작할 수 있습니다.
비행 중에 회전 날개로 바꾸십시오. 본질적으로
날개가 달린 엔진. 하지만 내가 바꿀 수 있다면 어떨까요?
비행 중에도 엔진?
기본 클래스Engine
mutator를 노출 시켜서
속성 또는 다시 디자인Aircraft
같이:
Class Aircraft {
var wings;
var engine;
}
이제 비행 중에도 엔진을 교체 할 수 있습니다.
"상속에 대한 구성을 선호"는 디자인 원칙입니다.이 원칙은 적합하지 않을 때 상속을 남용하지 말라는 의미입니다.
두 엔티티간에 실제 계층 적 관계가 없으면 상속을 사용하지 말고 대신 composition을 사용하십시오. 작문은 "HAS A"관계를 나타냅니다.
예 : 자동차에는 바퀴, 차체, 엔진 등이 있습니다. 그러나 여기에서 상속을 받으면 바퀴 달린 자동차가됩니다. 이는 잘못된 것입니다.
자세한 설명은 다음을 참조하십시오.상속보다 구성을 선호한다.
"복사"/ 기본 클래스의 API를 공개하려는 경우 상속을 사용합니다. 기능 만 "복사"하려면 위임을 사용하십시오.
그 중 하나의 예 :리스트에서 스택을 생성하려고합니다. 스택에는 팝, 푸시 및 픽이 있습니다. 스택에서 push_back, push_front, removeAt 등의 기능을 원하지 않는다면 상속을 사용해서는 안됩니다.
이 두 가지 방법은 서로 잘 살며 서로를 실제로 지원할 수 있습니다.
작곡은 단지 모듈 방식으로 진행됩니다 : 부모 클래스와 비슷한 인터페이스를 만들고, 새로운 객체를 만들고, 호출을 위임합니다. 이 물체가 서로를 알 필요가없는 경우 구성을 사용하는 것이 안전하고 쉽습니다. 여기에는 너무 많은 가능성이 있습니다.
그러나 어떤 이유로 든 부모 클래스가 경험이 부족한 프로그래머를 위해 "자식 클래스"에서 제공하는 함수에 액세스해야한다면 상속을 사용하기에 좋은 장소 인 것처럼 보일 수 있습니다. 부모 클래스는 자신 만의 추상 "foo ()"를 호출 할 수 있습니다.이 클래스는 하위 클래스로 덮어 쓰여진 다음 추상 기준에 값을 제공 할 수 있습니다.
좋은 생각 인 것 같지만 많은 경우에 클래스에 foo ()를 구현하는 객체를 주거나 (또는 foo ()를 수동으로 제공하는 값을 설정하는 것), 기본 클래스에서 새 클래스를 상속하는 것보다 낫습니다. 지정할 함수 foo ().
왜?
상속은 정보 이동에 대한 부적절한 방법이기 때문에.
그 구성은 다음과 같이 실질적인면을 가지고 있습니다. 관계가 바뀔 수 있습니다 : "부모 클래스"또는 "추상 작업자"는 특정 인터페이스를 구현하는 특정 "자식"객체를 집계 할 수 있습니다 +임의의 자식은 다른 유형의 부모 내부에서 설정 될 수 있으며 그 유형을 허용합니다. 개체 수에는 제한이 없습니다. 예를 들어 MergeSort 또는 QuickSort는 추상 비교 인터페이스를 구현하는 개체 목록을 정렬 할 수 있습니다. 또는 "foo ()"를 구현하는 객체 그룹과 "foo ()"를 갖는 객체를 사용할 수있는 객체 그룹을 함께 재생할 수 있습니다.
상속을 사용하는 세 가지 실제 이유를 생각해 볼 수 있습니다.
이것이 사실이라면 상속을 사용해야 할 것입니다.
이유 1을 사용할 때 나쁘지는 않습니다. 객체에 단단한 인터페이스를 갖는 것은 매우 좋은 일입니다. 이 인터페이스가 단순하고 변경되지 않으면 컴포지션 또는 상속을 사용하여 문제를 해결할 수 있습니다. 보통 상속은 여기에서 아주 효과적입니다.
그 이유가 2 번이라면 조금 까다 롭습니다. 정말로 동일한 기본 클래스 만 사용해야합니까? 일반적으로 같은 기본 클래스를 사용하는 것만으로는 충분하지 않지만 프레임 워크의 요구 사항 일 수 있습니다. 이러한 고려 사항은 피할 수없는 디자인 고려 사항입니다.
그러나 개인 변수 인 사례 3을 사용하려는 경우 문제가 발생할 수 있습니다.전역 변수가 안전하지 않다고 생각하면 상속을 사용하여 안전하지 않은 개인 변수에 액세스하는 것을 고려해야합니다. 글로벌 변수가 모두 나쁜 것은 아닐 것입니다. 데이터베이스는 본질적으로 글로벌 변수 세트입니다. 그러나 당신이 그것을 다룰 수 있다면, 그것은 꽤 괜찮습니다.
이외에도 고려해야 할 사항이 있습니다. 객체가 통과해야하는 상속의 "깊이"도 고려해야합니다. 5 단계 또는 6 단계의 상속 수준을 초과하는 경우 예기치 않은 캐스팅 및 복싱 / 언 박싱 문제가 발생할 수 있으며이 경우 개체를 대신 작성하는 것이 좋습니다.
이것을 이해하는 간단한 방법은 클래스의 객체가 동일한 객체를 가질 필요가있을 때 상속을 사용해야한다는 것입니다인터페이스그 결과 부모 클래스의 객체 (상향 캐스트)로 취급 될 수 있습니다. 또한 파생 클래스 객체에 대한 함수 호출은 코드의 모든 부분에서 동일하게 유지되지만 호출 할 특정 메소드는 런타임에 결정됩니다 (즉, 하위 수준이행다르다면, 상위 레벨인터페이스동일하게 유지됨).
새로운 클래스가 동일한 인터페이스를 가질 필요가 없을 때, 즉 해당 클래스의 사용자가 알 필요가없는 클래스 구현의 특정 측면을 숨길 때 컴포지션을 사용해야합니다. 구성이 지원의 방식에 더 있습니다.캡슐화(즉, 구현을 숨김), 상속은추출(즉, 무언가의 간단한 표현을 제공합니다.이 경우에는같은내부가 다른 여러 유형의 인터페이스).
당신이~이다두 클래스 간의 관계 (개는 송곳니), 당신은 상속을 위해갑니다.
반면에 당신이 가지고있을 때가지고있다또는 두 클래스 (학생이 코스를 가짐) 또는 (교사 학습 코스) 사이에 형용사 관계를 선택한 경우 구성을 선택했습니다.
@Pavel에 동의합니다. 그가 말하길, 작곡의 장소가 있고 상속받을 곳이 있습니다.
귀하의 대답이 이러한 질문에 긍정적 인 것이라면 상속을 사용해야한다고 생각합니다.
그러나 의도적으로 코드를 다시 사용하려는 의도가있는 경우 컴포지션이 가장 좋은 디자인 선택입니다.
내가 들었던 엄지 손가락의 규칙은 상속이 그것의 "a-a"관계와 구성에 사용될 때 사용해야한다는 것입니다. 그것으로도 복잡성을 많이 없애기 때문에 항상 작곡쪽으로 기울어 져야한다고 생각합니다.
상속은 코드 재사용을위한 매우 강력한 마카니즘입니다. 그러나 제대로 사용되어야합니다. 하위 클래스가 부모 클래스의 하위 유형이기도 한 경우 상속이 올바르게 사용된다고 말할 수 있습니다. 위에서 언급했듯이 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에 추가하려고 할 수 있습니다. 그러나 가치가 존재한다면 그녀는 그 행동을 얻지 못할 것입니다. 그녀를 위해 큰 깜짝 선물!
이것은 상속을 부적절하게 사용하는 고전적인 예입니다. 이 경우 구성을 사용하십시오.
(단편 :상속을 제대로 사용한다.).
구성 v / s 상속은 광범위한 주제입니다. 모든 것이 시스템의 설계에 달려 있다고 생각할 때 더 나은 점에 대한 진정한 대답은 없습니다.
일반적으로 객체 간의 관계 유형은 더 나은 정보를 제공하여 그 중 하나를 선택합니다.
관계 유형이 "IS-A"관계이면 상속이 더 나은 접근 방법입니다. 그렇지 않으면 관계 유형이 "HAS-A"관계이고 구성이 더 잘 접근합니다.
그것은 엔티티 관계에 전적으로 의존합니다.
새로운 프로그래머를위한 다른 관점에서이 질문을 해결하려면 다음을 수행하십시오.
상속은 종종 객체 지향 프로그래밍을 배울 때 조기에 가르쳐지기 때문에 일반적인 문제에 대한 쉬운 해결책으로 간주됩니다.
모든 공통 기능이 필요한 세 가지 클래스가 있습니다. 그래서 내가 기본 클래스를 작성하고 모든 클래스에서 상속 받도록 설정하면됩니다. 모든 기능이 있으며 한 번에 유지 관리 만하면됩니다. 장소.
훌륭하게 들리지만 실제로는 결코 작동하지 않습니다. 몇 가지 이유 중 하나가 있습니다.
결국 우리는 코드를 어려운 매듭에 묶어두고 "멋진, 상속에 대해 배웠고 지금은 그것을 사용했습니다"라고 말하면서는 아무런 이점도 얻지 못합니다. 그것은 우리가 모두 해냈기 때문에 겸손하게하기위한 것이 아닙니다. 그러나 아무도 우리에게하지 말라고했기 때문에 우리 모두 그것을했습니다.
누군가가 "상속에 대한 상속 구성"을 나에게 설명하자마자, 상속을 사용하여 클래스간에 기능을 공유하려고 할 때마다 그 사실이 실제로 잘 작동하지 않는다는 것을 깨달았습니다.
해독제는단일 책임 원칙. 그것을 제약으로 생각하십시오. 내 수업절대로 필요한 것한 가지해라. 나는절대로 필요한 것내 수업에 어떻게 든 그 일을 설명하는 이름을 줄 수 있어야합니다. (모든 것에 예외가 있지만 절대 규칙은 때때로 우리가 배울 때 더 좋습니다.)ObjectBaseThatContainsVariousFunctionsNeededByDifferentClasses
. 내가 필요한 고유 한 기능이 무엇이든간에 자체 클래스에 있어야하며 해당 기능을 필요로하는 다른 클래스는 해당 클래스에 의존 할 수 있습니다.아니그것으로부터 상속 받는다.
지나치게 단순화 될 위험에 처해있는 것은 복합적입니다. 여러 클래스가 함께 작동합니다. 일단 우리가 습관을 형성하면 상속을 사용하는 것보다 훨씬 더 유연하고 유지 보수가 용이하며 테스트 할 수 있다는 것을 알게됩니다.
많은 사람들이 말했듯이, 나는 먼저 수표를 가지고 시작할 것인데, "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
상속 문제는 두 가지 직교 목적으로 사용할 수 있다는 것입니다.
인터페이스 (다형성 용)
구현 (코드 재사용)
참고
작곡이 선호 되더라도, 나는계승및의 죄수구성.
상속의 장점 :
그것은 논리적 "IS A "관계. 만약차과트럭두 종류의차량(기본 클래스), 하위 클래스IS기본 클래스.
즉
자동차는 자동차입니다.
트럭은 차량입니다.
상속을 사용하면 기능을 정의 / 수정 / 확장 할 수 있습니다.
작곡 단점 :
예 : 만약차포함하다차량그리고 만약 당신이차,에서 정의 된차량, 당신의 코드는 다음과 같을 것이다.
class Vehicle{
protected double getPrice(){
// return price
}
}
class Car{
Vehicle vehicle;
protected double getPrice(){
return vehicle.getPrice();
}
}