Composition over inheritance

From Wikipedia, the free encyclopedia
This diagram shows how the fly and sound behaviour of an animal can be designed in a flexible way by using the composition over inheritance design principle.[1]

Composition over inheritance (or Composite Reuse Principle) in object-oriented programming is a technique by which classes may achieve polymorphic behavior and code reuse by containing other classes that implement the desired functionality instead of through inheritance.[2]

Basics

An implementation of composition over inheritance typically begins with the creation of various interfaces representing the behaviors that the system must exhibit. The use of interfaces allows this technique to support the polymorphic behavior that is so valuable in object-oriented programming. Classes implementing the identified interfaces are built and added to business-domain classes as needed. Thus, system behaviors are realized without inheritance. In fact, business-domain classes may all be base classes without any inheritance at all. Alternative implementation of system behaviors are accomplished by providing another class that implements the desired behavior interface. Any business-domain class that contains a reference to the interface can easily support any implementation of that interface and the choice can even be delayed until run time.

Example

Inheritance

An example in C++ follows:

class Object
{
  public:
     virtual void update() {};
     virtual void draw() {};
     virtual void collide(Object objects[]) {};
};
 
class Visible : public Object
{
  public:
     virtual void draw() { /* draw model at position of this object */ };
  private:
     Model* model;
};
 
class Solid : public Object
{
    public:
        virtual void collide(Object objects[]) { /* check and react to collisions with objects */ };
};
 
class Movable : public Object
{
    public:
        virtual void update() { /* update position */ };
};

Then, we have concrete classes:

  • class Player - which is Solid, Movable and Visible
  • class Cloud - which is Movable and Visible, but not Solid
  • class Building - which is Solid and Visible, but not Movable
  • class Trap - which is Solid and neither Visible nor Movable

Using inheritance we either have to do multiple inheritance, which leads to the diamond problem, or make classes like VisibleAndSolid, VisibleAndMovable, VisibleAndSolidAndMovable, etc. for every needed combination, which leads to a large amount of repetitive code.

Composition and Interfaces

The following C++ and C# examples demonstrate the principle of using composition and interfaces to achieve code reuse and polymorphism. Due to the C++ language not having a dedicated keyword to declare interfaces, the C++ example uses "inheritance from a pure abstract base class". For most purposes, this is functionally equivalent to the interfaces provided in other languages, such as Java and C#.

class Object
{
    public:
        Object(VisibilityDelegate *v, UpdateDelegate *u, CollisionDelegate *c) : _v(v), _u(u), _c(c) {};
 
        void update() { _u->update(); };
        void draw()   { _v->draw(); };
        void collide(Object objects[]) { _c->collide(objects); };
    private:
        VisibilityDelegate *_v;
        UpdateDelegate *_u;
        CollisionDelegate *_c;
};
 
class VisibilityDelegate
{
    public:
        virtual void draw() = 0;
};
 
class Invisible : public VisibilityDelegate
{
    public:
        virtual void draw() {};
};
 
class Visible: public VisibilityDelegate
{
    public:
        virtual void draw() { /* draw model */ };
};
 
class CollisionDelegate
{
    public:
        virtual void collide(Object objects[]) = 0;
};
 
class Solid : public CollisionDelegate
{
    public:
        virtual void collide(Object objects[]) { /* check collisions with object and react */ };
};
 
class NotSolid : public CollisionDelegate
{
    public:
        virtual void collide(Object objects[]) {};
};
 
class UpdateDelegate
{
    public:
        virtual void update() = 0;
};
 
class Movable : public UpdateDelegate
{
    public:
        virtual void update() { /* move object */ };
};
 
class NotMovable : public UpdateDelegate
{
    public:
        virtual void update() { };
};

Then, concrete classes would look like:

class Player : public Object
{
    public:
        Player():Object(new Visible(), new Movable(), new Solid()) {};
        []
};
 
class Smoke : public Object
{
    public:
        Smoke():Object(new Visible(), new Movable(), new NotSolid()) {};
        []
};

In C#:

class Program
{
    static void Main()
    {
        var player = new Player();
        player.Update();
        player.Collide();
        player.Draw();
    }
}
 
interface IVisible
{
    void Draw();
}
 
class Invisible : IVisible
{
    public void Draw()
    {
        Console.Write( "I won't appear." );
    }
}
 
class Visible : IVisible
{
    public void Draw()
    {
        Console.Write( "I'm showing myself." );
    }
}
 
interface ICollidable
{
    void Collide();
}
 
class Solid : ICollidable
{
    public void Collide()
    {
        Console.Write( "Bang!" );
    }
}
 
class NotSolid : ICollidable
{
    public void Collide()
    {
        Console.Write( "Splash!" );
    }
}
 
interface IUpdatable
{
    void Update();
}
 
class Movable : IUpdatable
{
    public void Update()
    {
        Console.Write( "Moving forward." );
    }
}
 
class NotMovable : IUpdatable
{
    public void Update()
    {
        Console.Write( "I'm staying put." );
    }
}
 
class GameObject
{
    public GameObject( IVisible v , IUpdatable u , ICollidable c )
    {
        _v = v;
        _u = u;
        _c = c;
    }
 
    public void Update()
    {
        _u.Update();
    }
 
    public void Draw()
    {
        _v.Draw();
    }
 
    public void Collide()
    {
        _c.Collide();
    }
 
    readonly IVisible _v;
    readonly IUpdatable _u;
    readonly ICollidable _c;
}
 
class Player : GameObject
{
    public Player() : base( new Visible() , new Movable() , new Solid() ) { }
}
 
class Cloud : GameObject
{
    public Cloud() : base( new Visible() , new Movable() , new NotSolid() ) { }
}
 
class Building : GameObject
{
    public Building() : base( new Visible() , new NotMovable() , new Solid() ) { }
}
 
class Trap : GameObject
{
    public Trap() : base( new Invisible() , new NotMovable() , new Solid() ) { }
}

Benefits

To favor composition over inheritance is a design principle that gives the design higher flexibility, giving business-domain classes and more stable business domain in the long term. In other words, HAS-A can be better than an IS-A relationship.[1]

Initial design is simplified by identifying system object behaviors in separate interfaces instead of creating a hierarchical relationship to distribute behaviors among business-domain classes via inheritance. This approach more easily accommodates future requirements changes that would otherwise require a complete restructuring of business-domain classes in the inheritance model. Additionally, it avoids problems[3] often associated with relatively minor changes to an inheritance-based model that includes several generations of classes.

Drawbacks

One drawback to using composition in place of inheritance is that all of the methods being provided by the composed classes must be implemented in the derived class, even if they are only forwarding methods. In contrast, inheritance does not require all of a base class's methods to be re-implemented within the derived class. Rather, the derived class need only implement (override) the methods having different behavior than the base class methods. This can require significantly less programming effort if the base class contains many methods providing default behavior and only a few of them need to be overridden within the derived class. This drawback can be avoided by using traits.

References

  1. 1.0 1.1 Freeman, Eric; Freeman, Elisabeth; Kathy, Sierra; Bert, Bates (2004). Hendrickson, Mike; Loukides, Mike, eds. Head First Design Patterns (paperback) 1. O'REILLY. p. 23. ISBN 978-0-596-00712-6. 
  2. Kirk Knoernschild (2002). Java Design - Objects, UML, and Process: 1.1.5 Composite Reuse Principle (CRP). Addison-Wesley Inc. Retrieved 2012-05-29. 
  3. http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance

See also

This article is issued from Wikipedia. The text is available under the Creative Commons Attribution/Share Alike; additional terms may apply for the media files.