상속 vs. 집계 [닫기]


151

객체 지향 시스템에서 코드를 가장 잘 확장, 향상 및 재사용하는 방법에 대한 두 가지 생각 학교가 있습니다.

  1. 상속 : 서브 클래스를 생성하여 클래스의 기능을 확장합니다. 서브 클래스에서 수퍼 클래스 멤버를 대체하여 새로운 기능을 제공하십시오. 수퍼 클래스가 특정 인터페이스를 원하지만 구현에 대해 무시할 때 서브 클래스가 "공란을 채우도록"추상 / 가상 메소드를 작성하십시오.

  2. 집계 : 다른 클래스를 가져 와서 새 클래스로 결합하여 새로운 기능을 만듭니다. 다른 코드와의 상호 운용성을 위해이 새로운 클래스에 공통 인터페이스를 연결하십시오.

각각의 이점, 비용 및 결과는 무엇입니까? 다른 대안이 있습니까?

이 토론이 정기적으로 이루어 지지만 스택 오버플로에 대해서는 아직 논의되지 않았다고 생각합니다 (일부 관련된 토론이 있음). 좋은 Google 검색 결과가 놀랍게도 부족합니다.


9
좋은 질문입니다, 불행히도 지금은 시간이 충분하지 않습니다.
Toon Krijthe

4
좋은 답변은 더 빠른 답변보다 낫습니다 ... 나는 내 자신의 질문을보고 있으므로 최소한 당신을 투표 할 것입니다 :-P
Craig Walker

답변:


181

어느 것이 가장 좋은 것이 아니라 무엇을 사용해야할지는 중요합니다.

'정상적인'경우에는 간단한 질문만으로 상속 또는 집계가 필요한지 알 수 있습니다.

  • 새 클래스 원래 클래스와 다소 차이가있는 경우 상속을 사용하십시오. 새 클래스는 이제 원래 클래스의 서브 클래스입니다.
  • 새 클래스 에 원래 클래스 있어야합니다 . 집계를 사용하십시오. 새 클래스에는 이제 원래 클래스가 멤버로 있습니다.

그러나 큰 회색 영역이 있습니다. 따라서 몇 가지 다른 트릭이 필요합니다.

  • 상속을 사용했거나 인터페이스를 사용할 계획이지만 인터페이스의 일부만 사용하거나 상관 관계를 논리적으로 유지하기 위해 많은 기능을 재정의해야합니다. 그런 다음 집계를 사용해야 함을 나타내는 큰 불쾌한 냄새가납니다.
  • 집계를 사용했거나 사용하려는 경우 거의 모든 기능을 복사해야합니다. 그런 다음 상속의 방향을 가리키는 냄새가납니다.

짧게 자르려면 비논리적 상황을 피하기 위해 인터페이스의 일부를 사용하지 않거나 변경해야하는 경우 집계를 사용해야합니다. 주요 변경없이 거의 모든 기능이 필요한 경우 상속 만 사용하면됩니다. 의심스러운 경우 집계를 사용하십시오.

원래 클래스의 기능 중 일부가 필요한 클래스가있는 경우 다른 가능성은 원래 클래스를 루트 클래스와 하위 클래스로 분할하는 것입니다. 그리고 새로운 클래스가 루트 클래스에서 상속되도록합니다. 그러나 비논리적 인 분리를 만들지 말고 조심해야합니다.

예제를 추가하자. 'Eat', 'Walk', 'Bark', 'Play'와 같은 메소드가있는 'Dog'클래스가 있습니다.

class Dog
  Eat;
  Walk;
  Bark;
  Play;
end;

이제 'Cat'클래스가 필요합니다.이 클래스에는 'Eat', 'Walk', 'Purr'및 'Play'가 필요합니다. 먼저 먼저 Dog에서 확장 해보십시오.

class Cat is Dog
  Purr; 
end;

알았어.하지만 기다려. 이 고양이는 짖을 수 있습니다. 그리고 짖는 고양이는 우주의 원리를 위반합니다. 따라서 Bark 메서드를 재정 의하여 아무것도하지 않도록해야합니다.

class Cat is Dog
  Purr; 
  Bark = null;
end;

좋아, 이것은 효과가 있지만 나쁜 냄새가 난다. 따라서 집계를 시도하십시오.

class Cat
  has Dog;
  Eat = Dog.Eat;
  Walk = Dog.Walk;
  Play = Dog.Play;
  Purr;
end;

좋아, 이거 좋다 이 고양이는 더 이상 짖지 않으며 심지어 침묵하지 않습니다. 그러나 여전히 내부 개가 필요합니다. 솔루션 3 번을 시도해보십시오.

class Pet
  Eat;
  Walk;
  Play;
end;

class Dog is Pet
  Bark;
end;

class Cat is Pet
  Purr;
end;

이것은 훨씬 더 깨끗합니다. 내부 개가 없습니다. 고양이와 개는 같은 수준입니다. 모델을 확장하기 위해 다른 애완 동물을 소개 할 수도 있습니다. 물고기 나 걷지 않는 것이 아니라면. 이 경우 다시 리팩토링해야합니다. 그러나 그것은 다른 시간을위한 것입니다.


5
"클래스의 거의 모든 기능 재사용"은 실제로 상속을 선호하는 시간입니다. 내가 정말로 원하는 것은 "특정 방법에 대해이 집계 된 개체에 위임"이라고 쉽게 말할 수있는 언어입니다. 그것은 두 세계의 최고입니다.
Craig Walker

다른 언어에 대해서는 잘 모르겠지만 델파이에는 멤버가 인터페이스의 일부를 구현할 수있는 메커니즘이 있습니다.
Toon Krijthe

2
목표 C는 기능이 필요한지 아니면 선택적인지를 결정할 수있는 프로토콜을 사용합니다.
cantfindaname88

1
실제적인 예를 들어 큰 대답. 호출 된 메서드를 사용하여 Pet 클래스를 디자인하고 makeSound각 하위 클래스가 고유 한 종류의 사운드를 구현하도록합니다. 그러면 애완 동물이 많은 상황에서 도움이됩니다 for_each pet in the list of pets, pet.makeSound.
blongho


27

차이는 일반적으로 "is a"와 "has a"의 차이로 표현됩니다. "is a"관계인 상속은 Liskov 대체 원칙에 잘 요약되어 있습니다. "a"관계인 집계는 그저 집계 개체에 집계 된 개체 중 하나 있음을 나타냅니다 .

C ++의 개인 상속은 "비록 노출 된"멤버 객체의 집계로도 모델링 될 수있는 "의 측면에서 구현 됨"관계를 나타냅니다.


또한, 당신이 대답 해야하는 바로 그 질문에 차이가 잘 요약되어 있습니다. 번역을 시작하기 전에 사람들이 먼저 질문을 읽고 싶습니다. 엘리트 클럽 회원이되기 위해 디자인 패턴을 맹목적으로 홍보합니까?
Val

나는이 방법을 좋아하지 않는다, 그것은 구성에 대한 더
알리레자 마니 칼 릴리

15

가장 일반적인 주장은 다음과 같습니다.

모든 객체 지향 시스템에서 모든 클래스에는 두 부분이 있습니다.

  1. 인터페이스 : 개체의 "공공의 얼굴". 이것은 다른 세계에 발표하는 일련의 기능입니다. 많은 언어에서이 세트는 "클래스"로 잘 정의되어 있습니다. 일반적으로 이것들은 객체의 메소드 서명이지만 언어마다 조금씩 다릅니다.

  2. 그것의 구현 : 객체가 인터페이스를 만족시키고 기능을 제공하기 위해 수행하는 "무대 뒤에서"일. 이것은 일반적으로 객체의 코드 및 멤버 데이터입니다.

OOP의 기본 원칙 중 하나는 구현이 클래스 내에 캡슐화되어 있다는 것입니다 . 외부인이 볼 수있는 유일한 것은 인터페이스입니다.

서브 클래스는 서브 클래스에서 상속 될 때 일반적으로 구현과 인터페이스를 모두 상속 합니다 . 이것은 결국 클래스에 대한 제약으로 두 가지를 모두 받아 들여야 한다는 것을 의미합니다 .

집계를 사용하면 구현 또는 인터페이스 중 하나 또는 둘 다를 선택할 수 있지만 어느 쪽도 강요하지는 않습니다. 객체의 기능은 객체 자체에 달려 있습니다. 다른 객체를 원하는대로 연기 할 수 있지만 궁극적으로 자체 책임이 있습니다. 내 경험상 이것은 더 유연한 시스템, 즉 수정하기 쉬운 시스템으로 이어집니다.

따라서 객체 지향 소프트웨어를 개발할 때마다 거의 항상 상속보다 집계를 선호합니다.


추상 클래스를 사용하여 인터페이스를 정의 한 다음 각 구체적인 구현 클래스에 대해 직접 상속을 사용한다고 생각합니다 (매우 얕습니다). 모든 집계는 구체적으로 구현됩니다.
orcmid

이보다 더 많은 인터페이스가 필요한 경우 기본 수준의 추상 인터페이스의 인터페이스에서 메소드를 통해 해당 인터페이스를 반환하고 헹굼을 반복하십시오.
orcmid

이 시나리오에서 집계가 더 낫다고 생각하십니까? 책은 SellingItem이고, DigitalDisc는 SelliingItem입니다 codereview.stackexchange.com/questions/14077/…
LCJ

11

나는 "Is a"vs "Has a"에 대한 대답을했습니다 : 어느 것이 더 낫습니까? .

기본적으로 나는 다른 사람들과 동의합니다. 파생 클래스 동일한 데이터를 포함 하기 때문에 아니라 확장하는 유형 인 경우에만 상속을 사용하십시오 . 상속은 서브 클래스가 데이터뿐만 아니라 메소드도 얻는다는 것을 기억하십시오.

파생 클래스가 수퍼 클래스의 모든 메서드를 갖는 것이 합리적입니까? 아니면 파생 클래스에서 해당 메소드를 무시해야한다고 조용히 약속합니까? 아니면 수퍼 클래스에서 메서드를 재정 의하여 자신을 실수로 만들지 않으므로 아무도 실수로 호출하지 않습니까? 또는 API 문서 생성 도구에 힌트를 제공하여 문서에서 메소드를 생략 하시겠습니까?

이러한 경우 집계가 더 나은 선택이라는 강력한 단서입니다.


6

이 질문과 관련 질문에 대해 "is-a vs. has-a; 개념적으로 다르다"는 답변이 많이 있습니다.

내 경험에서 찾은 한 가지는 관계가 "is-a"인지 "has-a"인지 확인하려고하면 실패한다는 것입니다. 지금 객체를 올바르게 결정할 수 있다고해도 요구 사항을 변경하면 나중에 언젠가 잘못되었을 수 있습니다.

내가 찾은 또 다른 것은 상속 계층 구조 주위에 많은 코드가 작성되면 상속에서 집계로 변환 하기가 매우 어렵다는 것입니다. 수퍼 클래스에서 인터페이스로 전환하면 시스템의 거의 모든 서브 클래스가 변경됩니다.

그리고이 포스트의 다른 곳에서 언급했듯이 집계는 상속보다 유연성이 떨어지는 경향이 있습니다.

따라서 하나 또는 다른 것을 선택해야 할 때마다 상속에 대한 완벽한 논쟁이 일어납니다.

  1. 당신의 선택은 어느 시점에서 잘못된 것일 수 있습니다
  2. 일단 선택을 변경하면 어렵습니다.
  3. 상속은 더 제한적이기 때문에 더 나쁜 선택 인 경향이 있습니다.

따라서 강력한 is-a 관계가있는 경우에도 집계를 선택하는 경향이 있습니다.


변화하는 요구 사항은 OO 디자인이 필연적으로 잘못 될 것임을 의미합니다. 상속과 집계는 그 빙산의 일각 일뿐입니다. 가능한 모든 미래를 설계 할 수는 없으므로 XP 아이디어를 활용하십시오. 오늘날의 요구 사항을 해결하고 리팩토링해야 할 수도 있습니다.
Bill Karwin

나는이 질문과 질문을 좋아한다. blurrung is-a, has-a 및 uses-a에 대해 걱정합니다. 인터페이스로 시작합니다 (구현하려는 추상화를 식별하는 것과 함께). 추가적인 간접적 인 수준은 매우 중요합니다. 집계 결론을 따르지 마십시오.
orcmid

그것은 내 요점과 거의 같습니다. 모두 상속 및 집계는 방법이 문제를 해결하기 위해. 이러한 방법 중 하나는 내일 문제를 해결해야 할 때 모든 종류의 처벌을받습니다.
Craig Walker

내 생각에 집계 + 인터페이스는 상속 / 서브 클래스 링의 대안입니다. 집계만으로는 다형성을 수행 할 수 없습니다.
Craig Walker

3

이 질문은 일반적으로 Composition vs. Inheritance 로 표시되며 여기에서 이전에 요청되었습니다.


그렇습니다. SO가 관련 질문 중 하나로서 그것을주지 않았다는 것이 재미 있습니다.
Craig Walker

2
SO 검색 여전히 큰 시간을 빨아
Vinko Vrsalovic

동의했다. 그래서 나는 이것을 닫지 않았습니다. 그리고 많은 사람들이 이미 여기에 대답했습니다.
Bill the Lizard

1
또한 2 개의 질문 (및 답변)을 1로 롤링하는 기능이 필요합니다.
Craig Walker

3

나는 이것을 원래 질문에 대한 의견으로 만들고 싶었지만 300 문자는 [; <)에 물렸다.

조심해야한다고 생각합니다. 첫째, 질문에서 만들어진 두 가지 구체적인 예보다 더 많은 풍미가 있습니다.

또한, 목표를 기기와 혼동하지 않는 것이 좋습니다. 선택한 기술이나 방법론이 주요 목표의 달성을 지원하는지 확인하고 싶지만 어떤 기술이 가장 적합한 지에 대한 내용이 문맥 상 유용한 것은 아닙니다. 서로 다른 접근법의 함정과 명확한 스위트 스팟을 아는 데 도움이됩니다.

예를 들어, 무엇을 성취하고, 무엇을 시작할 수 있으며, 제약은 무엇입니까?

컴포넌트 프레임 워크, 심지어 특수 목적 프레임 워크를 작성하고 있습니까? 프로그래밍 시스템에서 구현과 인터페이스를 분리 할 수 ​​있습니까 아니면 다른 종류의 기술을 사용하여 실습을 통해 달성 할 수 있습니까? 인터페이스의 상속 구조 (있는 경우)를 구현하는 클래스의 상속 구조와 분리 할 수 ​​있습니까? 구현이 제공하는 인터페이스에 의존하는 코드에서 구현의 클래스 구조를 숨기는 것이 중요합니까? 동시에 여러 가지 구현을 사용할 수 있습니까? 아니면 유지 관리 및 향상으로 인해 시간이 지남에 따라 더 많은 변형이 있습니까? 도구 나 방법론을 고치기 전에이 이상을 고려해야합니다.

마지막으로, 추상화에서 구별을 잠그는 것이 중요하며 OO 기술의 다른 기능에 대해 (is-a 대 has-a에서와 같이) 어떻게 생각 하는가? 아마도 그것이 당신과 다른 사람들에게 개념적 구조를 일관되고 관리 가능하게 유지한다면. 그러나 그것에 의해 노예화되는 것은 현명하지 않으며, 결국 당신이 할 수있는 왜곡이 있습니다. 어쩌면 레벨을 세우고 너무 단단하지 않은 것이 가장 좋습니다 (그러나 다른 사람들이 무슨 일인지 알 수 있도록 좋은 나레이션을 남겨 두십시오). [프로그램의 특정 부분을 설명 할 수있는 것이 무엇인지 찾아 보지만, 때로는 더 큰 승리가있을 때 우아함을 찾는 경우가 있습니다. 항상 최고의 아이디어는 아닙니다.]

저는 인터페이스 순수 주의자이며 Java 프레임 워크를 구축하든 COM 구현을 구성하든 인터페이스 순수주의가 적합한 종류의 문제와 접근 방식에 이끌 렸습니다. 맹세하더라도 모든 것에 적합하지는 않습니다. (인터페이스 순수주의에 반대되는 심각한 반례를 제시하는 것으로 보이는 두 개의 프로젝트가 있으므로 어떻게 대처할 수 있는지 보는 것이 흥미로울 것입니다.)


2

적용 할 수있는 부분을 다룰 것입니다. 다음은 게임 시나리오에서 두 가지 예입니다. 다른 유형의 군인이있는 게임이 있다고 가정 해보십시오. 각 병사는 배낭을 가지고있어 다른 물건을 담을 수 있습니다.

상속? 해양, 녹색 베레모 및 저격수가 있습니다. 이들은 군인의 유형입니다. 그래서 파생 클래스로 Marine, Green Beret & Sniper가있는 기본 클래스 Soldier가 있습니다.

여기에 집계? 배낭에는 수류탄, 총 (다른 유형), 나이프, 메디 킷 등이 포함될 수 있습니다. 군인은 특정 시점에 이들 중 하나를 장착 할 수 있으며 공격 할 때 갑옷 역할을하는 방탄 조끼를 가질 수도 있습니다. 부상이 특정 비율로 감소합니다. 군인 클래스에는 방탄 조끼 클래스와 배낭 아이템이 포함되어 있습니다.


2

나는 그것이 논쟁이 아니라고 생각합니다. 그것은 단지 :

  1. is-a (상속) 관계는 has-a (구성) 관계보다 덜 자주 발생합니다.
  2. 상속은 사용하기에 적절할 때조차도 제대로 이해하기가 어렵 기 때문에 캡슐화를 깨고 구현을 노출 시켜서 밀접한 결합을 장려 할 수 있기 때문에 상당한주의를 기울여야합니다.

둘 다 자리가 있지만 상속은 더 위험합니다.

물론 Shape 'having-a'Point 클래스와 Square 클래스를 갖는 것은 의미가 없습니다. 여기에 상속이 예정되어 있습니다.

사람들은 확장 가능한 것을 디자인하려고 할 때 상속을 먼저 ​​생각하는 경향이 있습니다.


1

호의는 두 후보가 모두 자격을 갖추면 발생합니다. A와 B는 옵션이며 A를 선호합니다. 그 이유는 컴포지션이 일반화보다 더 많은 확장 성 / 유연성 가능성을 제공하기 때문입니다. 이 확장 / 유연성은 대부분 런타임 / 동적 유연성을 나타냅니다.

이점은 즉시 보이지 않습니다. 이점을 보려면 다음 예기치 않은 변경 요청을 기다려야합니다. 따라서 대부분의 경우 구성을 포용 한 사람들과 비교할 때 일반화에 충실한 사람들은 실패합니다 (나중에 언급 한 명백한 경우 제외). 따라서 규칙. 학습 관점에서 의존성 주입을 성공적으로 구현할 수 있다면 어느 것을 선호하고 언제 사용해야하는지 알아야합니다. 이 규칙은 의사 결정에도 도움이됩니다. 확실하지 않은 경우 컴포지션을 선택하십시오.

요약 : 구성 : 더 작은 것을 더 큰 것으로 연결하면 커플 링이 줄어들고 더 큰 객체는 더 작은 객체를 다시 호출합니다. 생성 : API 관점에서 메소드를 대체 할 수 있도록 정의하는 것은 메소드를 호출 할 수 있도록 정의하는 것보다 더 강력한 의무입니다. (일반화에서이기는 경우가 거의 없음). 컴포지션을 사용하면 큰 클래스 대신 인터페이스에서 상속을 사용한다는 것을 잊지 마십시오.


0

두 가지 방법 모두 다른 문제를 해결하는 데 사용됩니다. 한 클래스에서 상속 할 때 항상 둘 이상의 클래스를 집계 할 필요는 없습니다.

클래스가 봉인되었거나 가상이 아닌 멤버가 있기 때문에 단일 클래스를 집계 해야하는 경우가 있습니다. 따라서 상속해야하지만 프록시하는 클래스가 아닌 한 프록시 계층을 작성해야합니다. 구독 할 수있는 인터페이스가있어 상당히 잘 작동 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.