2

I've been reading this Wikipedia article Composition over inheritance. It gives a code example of inheritance first, and then a code example of composition. In case of inheritance there are data that are present in form of the model field. When any of the methods draw, collide or update need to do their function, they have access to to the model field to draw, collide or update it respectively.

In the composition example, this field is prominently absent. The three methods do not have any parameters, and their respective classes have no fields. Where the data that they draw, update or collide come from in case of the composition?

Update

It was pointed out in comments that model field belongs to the Visible class. I assumed above that model comes with the "base" class in inheritance terms. Still, conceptually, ditching inheritance means that we are no longer have shared data. I'm trying to understand what people do to exchange these data, since it's reasonable to suggest that Player, Cloud, Building and Trap have some common data members required by all those methods, e.g. size, coordinates, etc.

7
  • "In the composition example, this field is prominently absent. " - Most probably the field is absent for the sake of simplicity, because it does not matter much for what the articles wants to explain - just assume Visible has the same model field in both cases. IMHO the Wikipedia example is contrived, hence don't overthink this.
    – Doc Brown
    Commented Aug 24, 2023 at 4:11
  • @DocBrown If Visible has the same model in the composition case, how do the methods get access to it? Do we have to pass it on each call? This is a very practical problem - all the methods need access to data, and with composition the data does not seem to be easily available. E.g. if you try to use composition the first time after extensively using inheritance, you would have the same question I would imagine ;) If you have a non-contrived example, it would be most welcome in an answer! Commented Aug 24, 2023 at 4:30
  • As far as I can see, there is only one method in the examples which requires access to the private model field, the draw method of the class Visible. So where is the problem? Note even in the "inheritance" example, model cannot be accessed from Player.
    – Doc Brown
    Commented Aug 24, 2023 at 4:36
  • @DocBrown yep, thanks and apologies, I missed that. I assumed that model comes with the "base" class in inheritance terms. Still, conceptually, ditching inheritance means that we are no longer have shared data. I'm trying to understand what people to do to exchange this data, since it's reasonable to suggest that Player, Cloud. Building and Trap have some common data members required by all those methods, e.g geometry. Commented Aug 24, 2023 at 4:42
  • This is a reasonable first question to have, but it places the focus on the wrong thing. For both composition and, to a slightly lesser extent, inheritance, its preferable to write code that's not accessing private data at all (not in all cases, but in many cases). Avoid or limit asking other objects (or parent objects) for data that you'll then manipulate outside the source object; instead, tell the other object to do something for you. That's the essence of OOP. That said, some objects can act as simple data structures with no behavior. Commented Aug 24, 2023 at 8:06

2 Answers 2

4

After your update it became clear that @jimmykurian's answer (which was unfortunately deleted instead curated) was actually on the right track: what you had in mind is a situation along the lines of

class Object
{
protected:
    Model *model;

public:
    virtual void update() {
        // no-op
    }

    virtual void draw() {
        // no-op
    }

    virtual void collide(Object objects[]) {
        // no-op
    }
};

where in the inherited classes Visible.draw, Solid.collide and Moveable.update could access model directly.

Without inheritance, one has to make access to the model field explicit. Let's take the composite version of Visibility: either the draw method requires an additional Model * parameter, so Object.draw will look like this:

 void draw() {
    _v->draw(model);
 }

or the Visibility class needs to have a model field for itself, where the constructor will take pointer or reference to the model field of the Object. The Player constructor will have to be implemented like this:

   Player()
      : Object(new Visible(model), /*...*/)
    {}

This requires a little bit more boilerplate code, but it makes the dependencies explicit.

For example, assume Object has a few other protected members. Now it is clear Visible will never access those other members, only the model member. Or, you can make Visible consume just a const Model * instead of a Model * in case it does not change anything in the model. This will make it easier to reason about the code in case you have to change or extend the Visible class at a later point in time.

2

Still, conceptually, ditching inheritance means that we are no longer have shared data.

Perhaps I am reading into this sentence too much but I think this is the crux of your confusion. In a 'traditional' OO language, when an object A is composed of other objects B and C, the references (or pointers) to B and C are the data of class A. A will either delegate to those objects or use their data through those references. Saying they don't share data is a little confused. In an inheritance relationship, the if A extends B, then, conceptually, the definition of B becomes part of A. When we create an instance of A, there's no 'sharing' because there's only one object. Another reason that saying there is no 'sharing' is misleading is that two A objects could be composed using a single B object. I think it's pretty awkward to describe that relationship without using the word 'share'.

Perhaps you are thinking of 'sharing' the way we talk about genetic inheritance. When we say two people 'share' DNA, we don't mean that they literally have a shared pool of DNA that they both use. We mean to that they both have the same DNA or at least some part of it is the same. While that aligns with the concept of inheritance in OO, I would recommend using the more literal meaning of 'share' as in "we share a home" or "we shared a cookie". That is, data sharing means having access to the same data.

As to how the composed class A makes use of the data in B and C, the traditional OO approach typically requires that to be done explicitly in code as Doc Brown explains. There are ways to make it more implicit, though, especially in dynamic languages. I'm not aware of any 'composition-oriented' languages and I'm a little surprised that implicit object composition hasn't become a common feature of contemporary languages.

Not the answer you're looking for? Browse other questions tagged or ask your own question.