1

I am dealing with a drawing app that allow the user to draw a set of Components (which are like the graph vertices) and they can be wired (like the graph edges) on each other to simulate how their connection propagate and affect each component based on their input, like a function on each node that takes all inputs, apply its function and set its outputs. Then, it propagates the value to other components connected to the outputs.

The struggle lies on the fact that my Component is also a Draw to be painted on a canvas. Usually problems like this are solved using the self drawing solution like the following example:

abstract class Component {
    // component stuff
    inputs: [] = [];
    outputs: [] = [];

    abstract function(): void;

    // drawing stuff
    x: number = 0;
    y: number = 0;

    draw(drawer: Drawer){
       drawer.drawCircle(this.x, this.y, 20);
    }
}

But it seems that mixing the draw of the with the meaning of that Vertice is bad design and really messy when trying to understand the code.

I have seen people suggesting creating different classes for Draw and Component and the draw holds the reference of the real component. It seems reasonable but I do not know if this is the more practical solution because it looks like you have to duplicate the whole structure in the form of graph just to separate the draw from it.

I would like to know if there is a better approach for this sort of problem.

  • well there is the strategy design pattern. all you drawing would be done by strategy objects, that your component executes. – esoterik Jun 21 '18 at 20:27
  • I would split up aspects related to simulation from those related to drawing and UI. – whatsisname Jun 21 '18 at 20:34
2

You could very well let your Component and your Wire both be a Draw. This works very well as long as there is only a single canvas to draw. It could even work with several views and canvas, as long as there is a simple transformation formula between them (e.g. scaling).

The main inconvenience of such a design is however that the components and wires classes might need to change either because of the business logic or because of the drawing logic: two reasons for change seem not totally according to the SRP principle of SOLID.

The alternate approach is to use Component and Wire as business objects only, without immediate graphical representation. You'd then use DrawableComponent and DrawableWire as Draw acting as graphical proxy for the business objects.

It's a little bit more complex (i.e. more parts), without being complicated (i.e. it's easy to understand the roles of the different objects). But there are many advantages:

  • You may easily use several very different graphical views (e.g. diagrams that optimise layout depending on the visible objects, which means that relative coordinates may not be the same between views)
  • You may easily manage several different graphical representations for the components, if they are used in different contexts (e.g. you can very well represent the same UML message (business object) in several UML interaction diagrams (graphical representations of that object).
  • Thanks to the SRP, you could have one team in charge of the business objects and another team in charge of the drawings: the whole system will be more maintenable and manageable.

If you'd be in charge of a geographic information system that represents thousands of different kind of components (gas pipes and equipement, electrical pipes and equipment, telecom fiber, ...) on maps (with flat 2D horizontal graphs, 2D vertical under-ground graphs, 3D graphs, etc... ), and your code base would be of several millions of SLOC, you'd particularly appreciate this last benefit ;-)

1

Where to place the drawing logic depends on how flexible/extensible the system should be. But separating the objects/components from their visualization is in general a good idea.

One way of implementing this is to make the objects provide all the data and (meta) information which is needed to draw them, but not the actual drawing logic. For example, a Component object already provides x and y, it may also provide something like a shape type (circle, square, whatever you like), maybe a drawing size. Then have a separate DrawAll class which interprets the meta information of all objects to draw them. That approach is fine for a small, fixed number of shape types with not too complex drawing logic.

Another approach is to let the draw method where it is now in your example, but make Drawer an abstract interface to something which can draw "somewhere". That will also decouple the "objects" layer from the "drawing" layer. It has the advantage of making it easy to add new objects with completely unforeseen drawing logic to your system, but the disadvantage of having at least some drawing code in each Component class.

And finally, yes it is possible to have one drawing class per object type, and yes, this has the disadvantage of duplicating the class structure to some degree. But it might be beneficial if you expect the drawing logic to be so complex that is justifies a class of its own for each specific drawing algorithm.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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