Search code examples
javaoopjava-canvas

Inheritance vs Composition for canvas shapes model?


For the following model, would you prefer inheritance or composition:

  • I want to draw objects on a canvas that each represent a data object
  • think of it like a state machine diagram: Ellipses represent States, Lines represent connections/transitions between them. The object representation itself will never change, ie a State will always be shown by a ellipse. But the way an ellipse is drawn should differ, eg for selection it should have a different color, while dragging it should maybe have an alpha channel, etc.

From the design point of view, an ellipse is not a state, and a line is not a transition. Anyhow it would be suitable to combine both objects for being able to collect them in a List<Shape> and execute shape.draw() on every object.

Now 2 design models are possible, whereas I would consider 2 classes to be always the same:

interface Shape {
    void draw();
}
abstract class Figure implements Shape {
    //basic vars like start and end coordinates
    int x0, y0, x1, y1;
}

Inheritance:

abstract class State extends Figure {
    String name;
}
class Rectangle extends State {
    @Override void draw();
}

class Line extends Figure;
class Transition extends Line

Although from design point of view a rectangle is not a State, and a state is not a figure, regarding the drawing context this might be viable. Because I could inherit most of the stuff needed for handling the shapes, drawing etc.

Or Composition:

abstract class State {
    String name;
}

class Rectangle extends Figure {
    private State state;
    @Override void draw();
}
class Line extends Figure {
    private Transition transition;
    @Override void draw();
}

So Rectangle + Line would be wrappers for my objects.

Should a Rectangle and Line rather extends State and Transition, or contain it?

Or, maybe there is also a 3rd design option that I did not see. Looking forward to your ideas.


Solution

  • So, here's my ideas, but with most design question, there's rarely ever one "correct" answer.

    Like you said, a State is not a Rectangle, and a Transition is not a Line. We can draw a Line and/or a Rectangle, and there may be some advantage to treating them similarly. So I can translate those statements into a simple design:

    public interface Drawable 
    {
      public void draw();
      public Position getPos();
    }
    
    public class Rectangle implements Drawable ...
    public class Line implements Drawable ...
    

    Now, State's and Transition's can be represented by these Drawables. Have you ever heard of Single Responsibility Principle? It's basically exactly what it sounds like, an Object should be responsible for doing one "thing". Rectangles and Lines know how to draw themselves. States and Transitions presumably have some other job to do in your system.

    public interface Figure 
    {
       public Drawable getDrawable();
    }
    
    public class State implements Figure
    {
       private Rectangle rect;
    
       public Drawable getDrawable() { return rect; }
    
       //... State's real "work" below
    }
    
    public class Transition implements Figure
    {
       private Line line;
       // you get the idea
    }
    

    On a small/simple system, the advantages of SRP may be lost, but the idea is that we're separating rendering from the other system logic. The more we separate functionality, the less brittle the system will be when the time for changes come.