Talk:Composition over inheritance
This article is rated C-class on Wikipedia's content assessment scale. It is of interest to the following WikiProjects: | ||||||||||||||||||
|
Misleading
editThe first paragraph lets the reader believe that developers should use composition and not inheritance, 1) that this is often stated and 2) and makes a reference to the Design Patterns book.
Firstly, the authors of Design Patterns didn't write that. The message is that inheritance can easily be abused and that composition should be favoured if possible, but that in practice composition is not always (rarely) possible because the set of components is not rich enough. I quote, "Inheritance and object composition thus work together" (p. 20).
Secondly, this is as often stated as the opposite because this is a controversial subject.
So I suggest to adjust the first paragraph to make it more objective and to avoid distorting the only given reference (or to remove it). — Preceding unsigned comment added by 2A02:A03F:C0B9:7C00:8446:E99A:2992:1613 (talk) 18:35, 14 February 2023 (UTC)
Missing drawback -- memory footprint
editA drawback of composition is that each instance must have a reference to the constituent parts. In the example given, on a 64-bit system, that's 192 bytes additional per instance for the composition version of the example. In the inheritance version there's no per-instance overhead. Kasajian (talk) 03:43, 15 August 2022 (UTC)
Bad?
edit"Using inheritance we either have to do multiple inheritance, which is bad,[vague]"
Why is it bad? Use multiple inheritance for the example given (using mix-in classes) seems like a much more conceptually cleaner way to do it. Is this referring to some performance/gotcha specific to C++.
This article doesn't outline a clear argument against multiple inheritance (specifically mix-ins), and reeks of opinion.
Also it seems to be very biassed to statically typed languages (C++).
For example in dynamic language (such as Python, Perl, etc) duck typing would be a much more appropriate solution to the example given.
It's not even clear in the C++ example why supplying delegate classes is any better than just defining the methods for each of the sub-interfaces (Visibility, Update, Collision) and using single inheritance. — unsigned comment by 121.45.113.228 on 2011-03-12
yes, bad
edit[the article] C++ and C# code snippets, even if of some use to those familiar with the languages, don't help others to understand this concept at all better. — Preceding unsigned comment added by 68.183.23.147 (talk) 22:27, 16 January 2013 (UTC)
I agree, there is waaaay too much code here. It should have a succinct, simple example using pseudocode, if anything. 213.216.142.12 (talk) 13:52, 4 October 2013 (UTC)
Confusion
editI started off writing that just the diagram was confused but have come to the conclusion that whole article is.
Is the article trying to explain that 'HAS-A can be better than IS-A' or is it trying to explain that 'many narrow interfaces can be better than a single broad interface'?
From the title, it should surely be about the former, but it certainly seems to blur into the latter in places (without identifying when it is crossing the line...).
As for the diagram... it shows (by inheritance arrows) that a Duck IS-A Quackable and (transitively) IS-A Flyable, whereas the point of p.22..23 of Head First Design Patterns on which this diagram is based is that 'HAS-A can be better than IS-A'.
Conversely the diagram does not actually depict the HAS-A relaionship in a clear exemplary way at all.
What the diagram's author has done is make Duck inherit from (implement the interfaces of) Flyable and Quackable (whereas the Head First Design Patterns book does not do so). And by doing this the diagram confuses rather than clarifies the distinction betwen IS-A and HAS-A - because the diagram actually depicts that a Duck both IS-A and HAS-A Flyable and Quackable behaviour.
This is likely to confuse rather than aid understanding, I think.
Note that the code examples do not do the same thing - the composite does not inherit the interfaces of it's components. However the fact that it ends up with a series of single line 'forwarding' calls might make the reader wonder whether it might not be better if it did...
Suggested resolution: identify/segregate and reference the 'Interface segregation principle' wherever the article is in fact talking about it.
I agree. My understanding of an interface is that it is a specification that if complied with allows different things to interact effectively. For example if a plug and socket conform to a defined specification the plug will fit into the socket, in software it may be defining the order of arguments being passed from one thing to another. But 'interface' seems to mean something else here. If that is the case, we need an explanation of what this 'interface' is. FreeFlow99 (talk) 12:49, 7 March 2023 (UTC)
Boiler plate in the C++ example
editThe C++ example contains a lot of boiler plate code
(which is probably essentially the same for every "interface"
that is created). A lot of it can be removed by allowing
the constructor of Object to be called with null pointers instead
of new NotMovable()
, new NotSolid()
etc. For example, Object::update()
would check
the pointer _u
and only if _u
is non-null it would call _u->update()
.
Then only the interfaces that actually do something
(Movable
, Solid
etc.) have to be defined.
For readibility and type safety,
Movable * const NotMovable=0;
etc. should probably be defined at namespace scope
anyway. With this definition, the constructor of the Smoke
class would be
Smoke():Object(new Visible(), new Movable(), NotSolid) {};
for example.
This solution has less lines of code, but is no less readable, and causes less namespace polution (no Delegates), so I wonder: Is there a reason why it is not advocated in the "Composition over inheritance" approach in C++? — Preceding unsigned comment added by Matthieumarechal (talk • contribs) 16:11, 19 February 2014 (UTC)
The C# example demonstrates code reuse and polymorphism ?
editHow can i more easily reuse the classes Player, Cloud, Building and Trap in the C#/composition example than the same classes in the C++/inheritance example?
As for polymorphism, shouldn't the class GameObject explicitly declare implementation of the interfaces IVisible, ICollidable, IUpdatable? - that is:
class GameObject : IVisible, IUpdatable, ICollidable { ... }
.
If not,
then i can define and run through a collection of GameObject subtype instances - say:
GameObject[] t_go = { new Building(), new Trap(), new Player(), new Player() }; foreach ( GameObject obj in t_go ) obj.Draw();
but i cannot define a collection of any "interface instances" - at least, not with instances of Player / Cloud / Building / Trap - like:
IVisible[] = { new Building(), new Trap(), new Player(), new VisibleThingButNotGameObject() }; /* won't compile */
Business domain
editWhy is there talk of the business domain? Is there anything peculiar to business domains and composition?
Why are there examples in different languages? As if inheritance is for C++ programs and composition for C# programs.
It would also be better if the diagram at the top was for the code below. 87.102.44.18 (talk) 16:21, 7 November 2015 (UTC)
Drawback example
editThe drawback example seems bogus to me, but maybe I'm misunderstanding. The drawback is described but the description doesn't make sense to me and example that follows doesn't demonstrate the drawback. Wouldn't the composition-based solution be to give Employee an object that implements an IPayable interface and which only has a pay() method?
Drawbacks?
editI don't understand the example under "Drawbacks". Couldn't we just use the same method as in the previous example to only duplicate the Pay method?
- This whole example is too convoluted, especially when it's only intended to show one minor drawback.
- Also, the entire section has no references.
- Also, it overlooks the main drawback to composition, which is performance. There are two performance drawbacks to composition:
- Composition often happens at run-time, as in the example in this article (above). Every time a cloud is allocated, three additional objects must be allocated, and allocation is generally considered very expensive.
- Composition requires heavy run-time conditional checks: if this object has a "movable" component, move it; if this object has a "visible" component, draw it; etc... With inheritance, a non-movable object can never call a "move()" method because it wouldn't exist in that class or its parents. This would be a compile-time error and there is no run-time overhead involved.
// The interface calculating pay
interface IPayable
{
// Get pay for the current pay period
// Since we don't have access to m_payRate and m_hoursWorked,
// we need to include them as method parameters.
decimal Pay(decimal m_payRate, int m_hoursWorked);
}
// Interface implementation
class HourlyPay: IPayable
{
// Get pay for the current pay period
public decimal Pay(decimal m_payRate, int m_hoursWorked)
{
// Time worked is in hours
return m_hoursWorked * m_payRate;
}
}
// Interface implementation
class SalariedPay: IPayable
{
// Get pay for the current pay period
public decimal Pay(decimal m_payRate, int m_hoursWorked)
{
// Pay rate is annual salary instead of hourly rate
return m_hoursWorked * m_payRate/2087;
}
}
// Base class
public abstract class Employee
{
// Member variables
protected string m_name;
protected int m_id;
protected decimal m_payRate;
protected int m_hoursWorked;
private readonly IPayable _p;
// Get/set the employee's name
public string Name
{
get { return m_name; }
set { m_name = value; }
}
// Get/set the employee's ID
public int ID
{
get { return m_id; }
set { m_id = value; }
}
// Get/set the employee's pay rate
public decimal PayRate
{
get { return m_payRate; }
set { m_payRate = value; }
}
// Get hours worked in the current pay period
public int HoursWorked()
{
return m_hoursWorked;
}
// Get pay for the current pay period
public decimal Pay()
{
return _p.Pay(m_hoursWorked, m_payRate);
}
public Employee(IPayable payable) {
_p = payable;
}
}
// Derived subclass
public HourlyEmployee: Employee
{
public HourlyEmployee() : base(new HourlyPay()) { }
}
// Derived subclass
class SalariedEmployee: Employee
{
public SalariedEmployee() : base(new SalariedPay()) { }
}
Sure, we had to change the method signature a bit for the interface, but it's still cleaner than the original? (Since the subclasses doesn't touch any of the internals of the superclass) Anka.213 (talk) —Preceding undated comment added 00:35, 29 August 2016 (UTC)
Languages
editWouldn't it make sense to use the same language for examples? Presumably, people looking up "Composition over inheritance" aren't going to be intimately familiar both with how inheritance works in C++ and how interfaces do in C#. The examples assume that the reader knows what base() does in C#, and how it's different from typical C++ approaches, and thus do nothing to illustrate actual differences between the two approaches. It's the kind of example that only makes sense once you already understand what is being illustrated.
It's a little like saying that the difference between 'malloc' in C and 'new' in C++ is that one works like this:
malloc
and one works like this:
new
Solid examples, but the reader has learnt nothing.
Examples
editIn the examples, it would be much clearer if two implementations, one using composition, and one using inheritance, were provided to compare and contrast.
In addition, the class diagram shows many instances of inheritance since it uses mostly UML 'generalize' relationships between 'interfaces' and their concrete implementations, rather than 'realization' relationships. Jgmccabe (talk) 00:31, 29 August 2019 (UTC)
- I came here to say this. Agreed. I am a designer though and am not confident in my ability to make an alternative image for this idea. 216.81.81.84 (talk) 20:01, 10 July 2024 (UTC)
Drawback mitigation in python
editTake a look at this edit. I think the removal is a bit harsh, you can certainly use __getattr__
to avoid the boilerplate drawback mentioned. It can also be adapted to multiple contained objects. Stackoverflow probably isn't a great source, can we add this again if we find a better one? Pink pipes (talk) 17:01, 15 May 2023 (UTC)