Question

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.

Était-ce utile?

La 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.

Autres conseils

Firstly I believe you are modelling it incorrectly, an ellipse does not represent a State but a State is represented by an ellipse. And as you very well put it, a State is not an ellipse. So you should have composition and I would choose the State object to have the Shape that represents it.

abstract class State
{
    private Shape shape;
}

To have the shape being selectable and still keep it abstracted away from the State then there should be an interface for selectable shapes.

interface SelectableShape
{
    void setSelected();
    void setUnselected();
}

In addition to the above, if you want to have for example the rectangle to connect to another rectangle through a transition, then you need to declare extra interfaces for example ConnectableShape and ShapeConnection.

interface ConnectableShape extends 
{
}

interface ShapeConnection
{
    void connect(ConnectableShape sh1, ConnectableShape sh2);
}

Also it is the states' responsibility to know their transitions (or the transitions' responsibility to know the states they connect) the thing that must be abstracted away with the Shape, ConnectableShape, ShapeConnection is the presentation of it all. Thus, it is not bad design to do something like the following.

abstract class State
{
    private ConnectableShape shape;
    public ConnectableShape getShape() {
        return shape;
    }
}

abstract class Transition
{
    private ShapeConnection conn;
    public void connect(State s1, State s2) {
        conn.connect(s1.getShape(),s2.getShape());
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top