Search code examples
.netinheritancecompositionsolid-principles

Using inheritance for encapsulating composition


Is this good/acceptable practice to derive from object only to hide all logic related to creation of that object.

For example (although not perfect example) I have a Scene class. I want to initialize it with some entities, some systems, set background color and so on. I could create Factory for that but instead I choose to do all that in constructor of derived class.

Instead of this:

var scene = new Scene();
scene.Systems (new ColisionSystem);
scene.Systems (new MovementSystem);
scene.SceneGraph = new MyCustomSceneGraph();

I do this:

class MyScene : Scene
{
    void MyScene(SceneGraph sceneGraph)
    {
        this.Systems (new ColisionSystem);
        this.Systems (new MovementSystem);

        this.SceneGraph = sceneGraph;
    }
}

var scene = new MyScene(new MyCustomSceneGraph());

So I am hiding all that creation stuff in constructor and as a bonus I got separate file for every scene I will create.

On the other way. If I ever create a scene editor this can be a problem. Because in that case client of the scene object should be responsible for preparing Scene, and not the scene itself.

So is this acceptable practice or does is violate SRP?


Solution

  • You have identified the exact problem with the subclass-per-composition approach: it is bound to compile time, making run-time composition a lot harder.

    In addition, hard-coding composition logic into subclass constructor requires you to recompile every time that you need to change the logic, even though the only thing that gets changed is the run-time instance graph inside your system.

    You can address the requirement of composing your objects at run-time by adding a class that describes connections without creating them, and then adding a constructor that takes the descriptions, and processes them into a complete Scene object.

    To make this less abstract, think of a configuration file in your favorite human-readable format, e.g.

    {"Systems":["ColisionSystem", "MovementSystem"], "SceneGraph":"MyCustomSceneGraph"}
    

    and an interface that lets you access this data:

    interface SceneConfiguration {
        IList<String> Systems {get;}
        String SceneGraph {get;}
        ... // More configuration items
    }
    

    Now consider a parser that produces an instance of SceneConfiguration from this representation, and a constructor of Scene that takes SceneConfiguration as its argument:

    public Scene(SceneConfiguration config)
    

    This arrangement lets you "soft-code" the composition through a configuration file. The approach works great with scene editors, too: your scene editor creates instances of SceneConfiguration, while Scene remains in control of creating itself.