Programing

상속 vs. 집계

lottogame 2020. 6. 20. 10:45
반응형

상속 vs. 집계


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

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

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

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

이 토론이 정기적으로 이루어 지지만 스택 오버플로에 대해서는 아직 묻지 않았다고 생각합니다 (일부 관련된 토론이 있음). Google 검색 결과가 훌륭하지 않다는 것도 놀라운 사실입니다.


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

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

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

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

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

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

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

예제를 추가하자. '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;

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


의 시작 부분에서 GOF 그들은 상태

클래스 상속보다 객체 구성을 선호합니다.

이것은 여기서 더 논의 됩니다


The difference is typically expressed as the difference between "is a" and "has a". Inheritance, the "is a" relationship, is summed up nicely in the Liskov Substitution Principle. Aggregation, the "has a" relationship, is just that - it shows that the aggregating object has one of the aggregated objects.

Further distinctions exist as well - private inheritance in C++ indicates a "is implemented in terms of" relationship, which can also be modeled by the aggregation of (non-exposed) member objects as well.


Here's my most common argument:

In any object-oriented system, there are two parts to any class:

  1. Its interface: the "public face" of the object. This is the set of capabilities it announces to the rest of the world. In a lot of languages, the set is well defined into a "class". Usually these are the method signatures of the object, though it varies a bit by language.

  2. Its implementation: the "behind the scenes" work that the object does to satisfy its interface and provide functionality. This is typically the code and member data of the object.

One of the fundamental principles of OOP is that the implementation is encapsulated (ie:hidden) within the class; the only thing that outsiders should see is the interface.

When a subclass inherits from a subclass, it typically inherits both the implementation and the interface. This, in turn, means that you're forced to accept both as constraints on your class.

With aggregation, you get to choose either implementation or interface, or both -- but you're not forced into either. The functionality of an object is left up to the object itself. It can defer to other objects as it likes, but it's ultimately responsible for itself. In my experience, this leads to a more flexible system: one that's easier to modify.

So, whenever I'm developing object-oriented software, I almost always prefer aggregation over inheritance.


I gave an answer to "Is a" vs "Has a" : which one is better?.

Basically I agree with other folks: use inheritance only if your derived class truly is the type you're extending, not merely because it contains the same data. Remember that inheritance means the subclass gains the methods as well as the data.

Does it make sense for your derived class to have all the methods of the superclass? Or do you just quietly promise yourself that those methods should be ignored in the derived class? Or do you find yourself overriding methods from the superclass, making them no-ops so no one calls them inadvertently? Or giving hints to your API doc generation tool to omit the method from the doc?

Those are strong clues that aggregation is the better choice in that case.


I see a lot of "is-a vs. has-a; they're conceptually different" responses on this and the related questions.

The one thing I've found in my experience is that trying to determine whether a relationship is "is-a" or "has-a" is bound to fail. Even if you can correctly make that determination for the objects now, changing requirements mean that you'll probably be wrong at some point in the future.

Another thing I've found is that it's very hard to convert from inheritance to aggregation once there's a lot of code written around an inheritance hierarchy. Just switching from a superclass to an interface means changing nearly every subclass in the system.

And, as I mentioned elsewhere in this post, aggregation tends to be less flexible than inheritance.

So, you have a perfect storm of arguments against inheritance whenever you have to choose one or the other:

  1. Your choice will likely be the wrong one at some point
  2. Changing that choice is difficult once you've made it.
  3. Inheritance tends to be a worse choice as it's more constraining.

Thus, I tend to choose aggregation -- even when there appears to be a strong is-a relationship.


The question is normally phrased as Composition vs. Inheritance, and it has been asked here before.


I wanted to make this a comment on the original question, but 300 characters bites [;<).

I think we need to be careful. First, there are more flavors than the two rather specific examples made in the question.

Also, I suggest that it is valuable not to confuse the objective with the instrument. One wants to make sure that the chosen technique or methodology supports achievement of the primary objective, but I don't thing out-of-context which-technique-is-best discussion is very useful. It does help to know the pitfalls of the different approaches along with their clear sweet spots.

For example, what are you out to accomplish, what do you have available to start with, and what are the constraints?

Are you creating a component framework, even a special purpose one? Are interfaces separable from implementations in the programming system or is it accomplished by a practice using a different sort of technology? Can you separate the inheritance structure of interfaces (if any) from the inheritance structure of classes that implement them? Is it important to hide the class structure of an implementation from the code that relies on the interfaces the implementation delivers? Are there multiple implementations to be usable at the same time or is the variation more over-time as a consequence of maintenance and enhancememt? This and more needs to be considered before you fixate on a tool or a methodology.

Finally, is it that important to lock distinctions in the abstraction and how you think of it (as in is-a versus has-a) to different features of the OO technology? Perhaps so, if it keeps the conceptual structure consistent and manageable for you and others. But it is wise not to be enslaved by that and the contortions you might end up making. Maybe it is best to stand back a level and not be so rigid (but leave good narration so others can tell what's up). [I look for what makes a particular portion of a program explainable, but some times I go for elegance when there is a bigger win. Not always the best idea.]

I'm an interface purist, and I am drawn to the kinds of problems and approaches where interface purism is appropriate, whether building a Java framework or organizing some COM implementations. That doesn't make it appropriate for everything, not even close to everything, even though I swear by it. (I have a couple of projects that appear to provide serious counter-examples against interface purism, so it will be interesting to see how I manage to cope.)


I think it's not an either/or debate. It's just that:

  1. is-a (inheritance) relationships occur less often than has-a (composition) relationships.
  2. Inheritance is harder to get right, even when it's appropriate to use it, so due diligence has to be taken because it can break encapsulation, encourage tight coupling by exposing implementation and so forth.

Both have their place, but inheritance is riskier.

Although of course it wouldn't make sense to have a class Shape 'having-a' Point and a Square classes. Here inheritance is due.

People tend to think about inheritance first when trying to design something extensible, that is what's wrong.


I'll cover the where-these-might-apply part. Here's an example of both, in a game scenario. Suppose, there's a game which has different types of soldiers. Each soldier can have a knapsack which can hold different things.

Inheritance here? There's a marine, green beret & a sniper. These are types of soldiers. So, there's a base class Soldier with Marine, Green Beret & Sniper as derived classes

Aggregation here? The knapsack can contain grenades, guns (different types), knife, medikit, etc. A soldier can be equipped with any of these at any given point in time, plus he can also have a bulletproof vest which acts as armor when attacked and his injury decreases to a certain percentage. The soldier class contains an object of bulletproof vest class and the knapsack class which contains references to these items.


Favour happens when both candidate qualifies. A and B are options and you favour A. The reason is that composition offers more extension/flexiblity possiblities than generalization. This extension/flexiblity refers mostly to runtime/dynamic flexibility.

The benefit is not immediately visible. To see the benefit you need to wait for the next unexpected change request. So in most cases those sticked to generlalization fails when compared to those who embraced composition(except one obvious case mentioned later). Hence the rule. From a learning point of view if you can implement a dependency injection successfully then you should know which one to favour and when. The rule helps you in making a decision as well; if you are not sure then select composition.

Summary: Composition :The coupling is reduced by just having some smaller things you plug into something bigger, and the bigger object just calls the smaller object back. Generlization: From an API point of view defining that a method can be overridden is a stronger commitment than defining that a method can be called. (very few occassions when Generalization wins). And never forget that with composition you are using inheritance too, from a interface instead of a big class


Both approaches are used to solve different problems. You don't always need to aggregate over two or more classes when inheriting from one class.

Sometimes you do have to aggregate a single class because that class is sealed or has otherwise non-virtual members you need to intercept so you create a proxy layer that obviously isn't valid in terms of inheritance but so long as the class you are proxying has an interface you can subscribe to this can work out fairly well.

참고URL : https://stackoverflow.com/questions/269496/inheritance-vs-aggregation

반응형