There was a book that talks about have a PhoneNumber class, and then we would define an Address class that inherits from PhoneNumber, and I said at one time, that we can't do that, because an address is not a phone number, and to inherit, it must be a "is a" relationship. Such as: a dog is an animal, and we we can make Dog inherit from Animal.
But since we have to follow LSP -- Liskov Substitution Principle, then the "is a" rule actually is not the determining factor here, because a square "is a" rectange (with width == height), but LSP says we can't define a Square class and inherit from the Rectangle class. The simple explanation in English, I think, is the object aRect can respond to the message setWidthAndHeight(w, h), but aSquare can't respond to it correctly and allow the whole program to run correctly.
So surprisingly, the Address class inheriting the PhoneNumber class violates the "is a" relationship, but it doesn't violate LSP. Then formally, what OOP principle(s) does it violate?
First, that really does violate LSP.
Generally, you would not expect to be able to substitute Adresses for PhoneNumbers. That's the 'common sense' bit everyone is talking about in the comments.
The point of the OOP theory is that following the formal rules will make your class robust to things like change and strange use cases. Even if the book example doesn't actually break under an LSP violation, I imagine it would break very quickly if you tried to expand that class architecture. So, to avoid potential errors in the future of a class, you should follow LSP - even when choosing not to do so doesn't immediately break anything.
Second, it violates ISP (the Interface Segregation Principle).
This states that the set of methods available in a class should be the minimum required for objects of the class to function.
If the Address class inherits (or might reasonably inherit) a bunch of PhoneNumber methods that are never used when dealing with the actual street address (e.g. a getAreaCode() method, which would be undefined for house addresses), then its interface isn't minimal.
The gist of this is that OOP principles are really just guidelines. It's definitely possible to make up weird example code that violates a SOLID principle yet doesn't actually introduce bugs. That doesn't mean that you're getting around the rule; it just means you'll end up with many more bugs as soon as you try to expand the class.
There are mainly two reasons to use inherits or extends.
reuse implementation (for DRY)
subtyping (LSP related)
I don't know all the principles but for your case it violates is common sense as #mpm commented.
Because even if your code satisfies all principles, it still can be inappropriate. In other words principles can not cover everything.
Related
This is one of the past year's exams that have no answers:
[1]: https://i.stack.imgur.com/RDzz0.jpg
It shows a diagram with two classes, Customer and Supplier, inheriting both from a class Partner. Another class Customer_Supplier inherits from both, Customer and Supplier.
The question asks what SOLID principle this design would violate. Despite attentive verification, I could not find any, and would really like to know.
The following diagram is the famous diamond of death, a very delicate problem when working with multiple inheritance:
The academic answer
The academic answer to your question is that this design violates the Single Responsibility Principle. The reasoning is the following:
A class should have a single responsibility
Supplier and Customer both have a single responsibility
Customer_Supplier inherits of at least two responsibilities
In reality, the SRP is about reason to change. But the general tendency is to apply the same reasoning: Customer_Supplier might have to change because of changes in Supplier or changes in Customer.
Elimination of the other candidates
It is in principle compliant with the Open/Close Principle by design. Each class could be extended, and unless the contrary is proven, there is no need to modify them.
A class diagram is rarely sufficient to confirm or deny compliance with the Liskov Substitution Principle, since this principle is about the contracts/promises of the classes and their subclasses. LSP requires that an object of the subclass can be used whenever an object of the superclass is expected. At first sight, a Customer_Supplier could be used instead of a Supplier, as well as instead of a Customer. Of course, one could easily imagine a class that breaks this. But this is equally true with any of the simplest inheritance. The fact is that nothing in the diagram lets us assume the opposite.
The Interface Segregation Principle is not violated either. On contrary, if a client does not need the Customer_Supplier interface, you could use one of the parent classes. If you use the Customer_Supplier in a client, it's probably because you need the full interface.
Finally, the Dependency Inversion Principle is not relevant here. Nothing indicates that one class is more concrete or more abstract than the other. In fact, these could even all be abstract classes. So there is no reason to think that this design does not comply with "Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions".
Is the academic answer is flawed?
The diamond of death is an extreme case that is often given as example to explain that multiple inheritance would be bad. And multiple inheritance can easily be misused. But so can be single inheritance and any other programming construct.
Let's bring in some more objectivity:
Classes should have a single responsibility. If a class inherits from another it may then have two responsibilities: its own and the responsibility of the superclass. On the other side, nothing tells us that these responsibilities are independent: one could be a sub-responsibility of the other.
Consequently, if Supplier has a sub-responsibility of BusinessPartner, and Customer has a sub-responsibility of BusinessPartner, they both have the sub-responsibilities of the same larger responsibility, especially considering the Open/Closed principle. This means that Customer_Supplier could in the end still just be a sub-responsibility of this single large responsibility. So SRP could perfectly be respected.
This is not advanced computer science, but basic set theory. You can use the same reasoning for the reason to change.
Another reasoning can be used for reason to change: if the subclass uses only the public interface of the superclass (which is a robust practice in view of LSP's history constraint) the subclass would not be impacted by changes to the superclass more than by any other change of a class that its dependent uppon. So the single reason to change could still hold.
For all these reasons, I'd reformulate the academic answer as follows: if this design would violate a SOLID principle, it could be only the the SRP. Nevertheless it does not necessarily violate it.
And to hammer the nail, I'll conclude with a quote from R.C. Martin who invented the SRP concept:
And this gets to the crux of the Single Responsibility Principle. This principle is about people.
And this design does not say anything about people.
And to finish with a philosophical question: is multiple inheritance really needed?
Recently I was talking to a very experienced programmer (8+ years of experience) and he told me that "combining data with functions that work with them in a capsula" is a wrong term for encapsulation. He told me that that was what encapsulation allowed me to do, but not what encapsulation itself was. He told me that as soon as inheritance is not possible without encapsulation, encapsulation must be just a capsula creation (class or anything like that). But today I got interviewed by a less experienced programmer and he was so sure all those classic definitions on wikipedia were right he told me not to even think of passing the interview. So I tried to google all that stuff about encapsulation, and about inheritence not being possible without encapsulation, but didn't find anything. But I can't believe that experienced programmer was wrong, he convienced not only me, but other experienced programmers too. Maybe that correct definition is just something that is lost in the chunks of useless and unimportant info?
So please, give me answers on these two questions:
1) can inheritence be possible without encapsulation? (A class's Inheritence from a class)
2) If not, then can we consider declaring a class encapsulation? Because only after declaring a class we can inherit.
Well, I'm Sorry, but I fail to see the connection between encapsulation and inheritance.
Encapsulation is hiding your internal implementation behind a publicly visible API.
Basically, it's a separation between a type's actual implementation and what it exposes.
In a broad sense, one can look at even the human body and see encapsulation:
For example: You are breathing air in and out, that's your public API, but the internals of what your body is doing with this air are hidden away inside your respiratory system - your lunges passes oxygen to your blood and collects from it carbon dioxide in return - thus changing the mixture ratio of the gasses in the air you breath, but none of this is visible to the outside world.
Inheritance, in the OOP world, is the ability to take a specific object, and derive an even more specific object from it, while adding capabilities (and sometimes mutating existing capabilities via overriding).
For example: A Dog is a kind of Mammal which is a kind of Animal.
An Animal might contain methods such as Eat() and properties such as Weight and Age.
A Mammal might override the Eat() method to implement suckling (from it's mother's breast) as an infant, but depending on it's age eating solid foods.
A Dog might introduce another capability such as Bark.
All of this have nothing to do with encapsulation as desribed in the previous paragraph.
Inheritance is tightly related to another core principle of object oriented programming called Polymorphism - basically, the ability to reference a derived class using it's base class type - perhaps you (or the interviewer) are confusing the two?
However, today is the first time I've seen another definition of encapsulation (and I've been working with oop languages for about two decades now):
A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.
Under that
definition, encapsulation is the process of creating capsules - stand-alone code snippets that holds data and ways to interact with it - a.k.a types, classes, etc', and is somewhat related to inheritance - in order to inherit a type, that type first needs to be defined.
However, the way I see it, this definition is not enough to define encapsulation. It can be a part of the definition, but not a stand-alone definition of encapsulation.
I want to create a class with two methods, and no other purpose than so I can create two subclasses which inherit the methods. This class cannot function on its own. Is this a bad programming design or habit?
There are even classes that don't do anything, other than letting other classes derive from it. It doesn't matter whether the superclass can have useful instances themselves. Classes that only exist for other classes to derive from are often called abstract classes; some languages such as C++ also have syntax features to allow the compiler to give errors when you try to create an object from an abstract class. So it can't be THAT bad to have classes like this.
Beyond that, what's "bad practice"? If the setup makes the code easier to understand then it can't be bad.
Of course, if the two classes you intend to derive really don't have anything in common and those two methods are merely "hey, I noticed that 10 lines of code in that class are the same as these 10 lines in the other class", then making this into a common superclass may confuse more than help. Classes should still have some form of relationship. If it's just about sharing some code that randomly happens to show up here and there, standalone functions might be a better choice.
Basically, look at the names of the classes. If your new superclass is named something along the lines of "Some very generic name 'cause I have no idea what it is", then it's probably not "good design". If, on the other hand, you have a proper name for the superclass, and the names of the derived classes are still something that has a "kind of" relationship to the superclass, then it's probably not a bad thing.
Another strong hint for something being "good" is when you start using pointers to the superclass because you don't care whether you're dealing with one or the other subclass.
Its a good habit, it leads to better organization of functions. Another reason is you can just look at the inheritance tree and know that it is related to the two function class. There isn't much harm in it.
There is no inherent good or bad, in general. It depends a lot on the specifics of situation. But, in general, you should always try to follow the principles of object-orientation. For example, whenever you are creating a class, whether abstract or concrete, the class should have both data and behaviour. This rule is very important, it goes all the way to the very foundation of object-orientation. A class without data, is just a bunch of methods (this is procedural programming, not OO). A class without behavior, is a bunch of variables (again procedural, not OO). So, having both data and behavior together is important. But, they should have logical relation to each other, not randomly put together. For example, a method should access data in some way.
Of course, there are deviations from this rule. For example, you may have just a bunch of methods in a static class (like Math class in Java), or just a bunch of constanst in a Interface. But, they are exceptions not rule. They are there, out of necessity, for convenience. They are not true classes in the strict object-oriented sense.
So, always aim toward the right principles, and deviate only when there is no other way to accomplish it, and only as an exception, not as a rule.
The previous point was refering to how to structure a class. For designing relationship among classes, again, the logical path should be followed. Think through each concept that you are dealing with and see if each one makes sense as a class, and then see what is the relationship among these classes. If it looks the you have three concepts that can be organized in inheritance - two classes deriving from a parent, the so be it. If the parent class has two methods, its ok. Even if it has one method, it is still OK. As long as it represents a coherent logical unit.
My understanding of the Liskov substitution principle is that some property of the base class that is true or some implemented behaviour of the base class, should be true for the derived class as well.
I guess this would mean when a method is defined in a base class, it should never be overrided in the derived class - since then substituting the base class instead of the derived class would give different results. I guess this would also mean, having (non-pure) virtual methods is a bad thing?
I think I might have a wrong understanding of the principle. If I don't, I do not understand why is this principle good practice. Can someone explain this to me? Thanks
Subclasses overriding methods in the base class are totally allowed by the Liskov Substituion Principle.
This might be simplifying it too much, but I remember it as "a subclass should require nothing more and promise nothing less"
If a client is using a superclass ABC with a method something(int i), then the client should be able to substitute any subclass of ABC without problems. Instead of thinking about this in terms of variable types, perhaps think about it in terms of preconditions and postconditions.
If our something() method in the ABC base class above has a relaxed precondition that permits any integer, then all subclasses of ABC must also permit any integer. A subclass GreenABC is not allowed to add an additional precondition to the something() method that requires the parameter to be a positive integer. This would violate the Liskov Substitution Principle (i.e., requiring more). Thus if a client is using subclass BlueABC and passing negative integers to something() the client won't break if we need to switch to GreenABC.
In reverse, if the base ABC class something() method has a postcondition - such as guaranteeing it will never return a value of zero - then all subclasses must also obey that same postcondition or they violate the Liskov Substitution Principle (i.e., promising less).
I hope this helps.
There is one popular example which says if it swims like a duck, quack likes a duck but requires batteries, then it breaks Liskov Substitution Principle.
Put it simply, you have a base Duck class which is being used by someone. Then you add hierarchy by introduction PlasticDuck with same overridden behaviors (like swimming, quacking etc.) as of a Duck but requires batteries to simulate those behaviors. This essentially means that you are introducing an extra pre-condition to the behavior of Sub Class to require batteries to do the same behavior that was earlier done by the Base Duck class without batteries. This might catch the consumer of your Duck class by surprise and might break the functionality built around the expected behavior of Base Duck class.
Here is a good link - http://lassala.net/2010/11/04/a-good-example-of-liskov-substitution-principle/
No, it tells that you should be able to use derived class in the same way as its base. There're many ways you can override a method without breaking this. A simple example, GetHashCode() in C# is in base for ALL classes, and still ALL of them can be used as "object" to calculate the hash code. A classic example of breaking the rule, as far as I remember, is derivin Square from Rectangle, since Square can't have both Width and Height - because setting one would change another and thus it's no more conforms to Rectangle rules. You can, however, still have base Shape with .GetSize() since ALL shapes can do this - and thus any derived shape can be substituted and used as Shape.
Overriding breaks Liskov Substitution Principle if you change any behavior defined by a base method. Which means that:
The weakest precondition for a
child method should be not stronger
than for the base method.
A postcondition for the child method
implies a postcondition for the
parent method. Where a postcondition
is formed by: a) all side
effects caused by a method execution and b)
type and value of a returned expression.
From these two requirements you can imply that any new functionality in a child method that does not affect what is expected from a super method does not violate the principle. These conditions allow you to use a subclass instance where a superclass instance is required.
If these rules are not obeyed a class violates LSP. A classical example is the following hierarchy: class Point(x,y), class ColoredPoint(x,y,color) that extends Point(x,y) and overridden method equals(obj) in ColoredPoint that reflects equality by color. Now if one have an instance of Set<Point> he can assume that two points with the same coordinates are equal in this set. Which is not the case with the overridden method equals and, in general, there is just no way to extend an instantiable class and add an aspect used in equals method without breaking LSP.
Thus every time you break this principle you implicitly introduce a potential bug that reveals when invariant for a parent class that is expected by the code is not satisfied. However, in real world often there is no obvious design solution that does not violate LSP, so one can use, for example, #ViolatesLSP class annotation to warn a client that it is not safe to use class instances in a polymorphic set or in any other kind of cases that rely on the Liskov substitution principle.
I think that you're literally correct in the way you describe the principle and only overriding pure virtual, or abstract methods will ensure that you don't violate it.
However, if you look at the principle from a client's point of view, that is, a method that takes a reference to the base class. If this method cannot tell (and certainly does not attempt to and does not need to find out) the class of any instance that is passed in, then you are also not violating the principle. So it may not matter that you override a base class method (some sorts of decorators might do this, calling the base class method in the process).
If a client seems to need to find out the class of an instance passed in, then you're in for a maintenance nightmare, as you should really just be adding new classes as part of your maintenance effort, not modifying an existing routine. (see also OCP)
The original principle:
"What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.".
Barbara Liskov, 1987
The word is behavior. The "preconditions and postconditions" understanding is useful for a good design but is not related to LSP.
Let's check this summary of "preconditions and postconditions" theory:
Don’t implement any stricter validation rules on input parameters than implemented by the parent class.
Apply at the least the same rules to all output parameters as applied by the parent class.
An indication that it has nothing to do with LSP is: what about VOID methods? VOID does not have OUTPUT parameters. How could this rule be applied to VOID methods? How, according to this rule, could we guarantee to be complying with LSP in VOID methods?
LSP refers to Behavior. When a subclass inherits from a superclass and you have to use some trick to make this work, and the result change the behavior of the program you are breaking LSP.
LSP is about behaviour and the clasic example of Square x Rectangle help us to understand. In fact is the example used by Uncle Bob.
The you inherit Square from Rectangle and overrides SetHeight and SetWidth to force Square act as a Square even if it's a rectangle (by inheritance).
When the user calls SetHeight do not expect Width change.... but will change and this change the expected behavior and break LSP.
This is the problem with Virtuals x LSP
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
There are two schools of thought on how to best extend, enhance, and reuse code in an object-oriented system:
Inheritance: extend the functionality of a class by creating a subclass. Override superclass members in the subclasses to provide new functionality. Make methods abstract/virtual to force subclasses to "fill-in-the-blanks" when the superclass wants a particular interface but is agnostic about its implementation.
Aggregation: create new functionality by taking other classes and combining them into a new class. Attach an common interface to this new class for interoperability with other code.
What are the benefits, costs, and consequences of each? Are there other alternatives?
I see this debate come up on a regular basis, but I don't think it's been asked on
Stack Overflow yet (though there is some related discussion). There's also a surprising lack of good Google results for it.
It's not a matter of which is the best, but of when to use what.
In the 'normal' cases a simple question is enough to find out if we need inheritance or aggregation.
If The new class is more or less as the original class. Use inheritance. The new class is now a subclass of the original class.
If the new class must have the original class. Use aggregation. The new class has now the original class as a member.
However, there is a big gray area. So we need several other tricks.
If we have used inheritance (or we plan to use it) but we only use part of the interface, or we are forced to override a lot of functionality to keep the correlation logical. Then we have a big nasty smell that indicates that we had to use aggregation.
If we have used aggregation (or we plan to use it) but we find out we need to copy almost all of the functionality. Then we have a smell that points in the direction of inheritance.
To cut it short. We should use aggregation if part of the interface is not used or has to be changed to avoid an illogical situation. We only need to use inheritance, if we need almost all of the functionality without major changes. And when in doubt, use Aggregation.
An other possibility for, the case that we have an class that needs part of the functionality of the original class, is to split the original class in a root class and a sub class. And let the new class inherit from the root class. But you should take care with this, not to create an illogical separation.
Lets add an example. We have a class 'Dog' with methods: 'Eat', 'Walk', 'Bark', 'Play'.
class Dog
Eat;
Walk;
Bark;
Play;
end;
We now need a class 'Cat', that needs 'Eat', 'Walk', 'Purr', and 'Play'. So first try to extend it from a Dog.
class Cat is Dog
Purr;
end;
Looks, alright, but wait. This cat can Bark (Cat lovers will kill me for that). And a barking cat violates the principles of the universe. So we need to override the Bark method so that it does nothing.
class Cat is Dog
Purr;
Bark = null;
end;
Ok, this works, but it smells bad. So lets try an aggregation:
class Cat
has Dog;
Eat = Dog.Eat;
Walk = Dog.Walk;
Play = Dog.Play;
Purr;
end;
Ok, this is nice. This cat does not bark anymore, not even silent. But still it has an internal dog that wants out. So lets try solution number three:
class Pet
Eat;
Walk;
Play;
end;
class Dog is Pet
Bark;
end;
class Cat is Pet
Purr;
end;
This is much cleaner. No internal dogs. And cats and dogs are at the same level. We can even introduce other pets to extend the model. Unless it is a fish, or something that does not walk. In that case we again need to refactor. But that is something for an other time.
At the beginning of GOF they state
Favor object composition over class inheritance.
This is further discussed here
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:
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.
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:
Your choice will likely be the wrong one at some point
Changing that choice is difficult once you've made it.
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'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.
I think it's not an either/or debate. It's just that:
is-a (inheritance) relationships occur less often than has-a (composition) relationships.
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.
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.