There are quite a few similar questions out there 1, 2, 3, 4, but non seems exactly the case in this question, nor do the solutions seem optimal.
This is a general OOP question, assuming polymorphism, generics, and mixins are available. The actual language to be used is OOP Javascript (Typescript), but it's the same problem in Java or C++.
I have parallel class hierarchies, that sometimes share the same behaviour (interface and implementation), but sometimes each has its own 'protected' behaviour. Illustrated like so:
This is for illustration purposes only; it isn't the actual class diagram. To read it:
- Anything in the common hierarchy (centre) is shared between both the Canvas (left) and SVG (right) hierarchies. By share I mean both interface and implementation.
- Anything only on the left or right columns means a behaviour (methods and members) specific to that hierarchy. For example:
- Both the left and right hierarchies use exactly the same validation mechanisms, shown as a single method (
Viewee.validate()
) on the common hierarchy. - Only the canvas hierarchy has a method
paint()
. This method calls the paint method on all children. - The SVG hierarchy needs to override the
addChild()
method ofComposite
, but such is not the case with the canvas hierarchy.
- Both the left and right hierarchies use exactly the same validation mechanisms, shown as a single method (
- The constructs from the two side hierarchies cannot be mixed. A factory ensures that.
Solution I – Tease Apart Inheritance
Fowler's Tease Apart Inheritance doesn't seem to do the job here, because there is some discrepancy between the two parallels.
Solution II – Mixins
This is the only one I can currently think of. The two hierarchies are develop separately, but at each level the classes mixin the common class, which are not part of a class hierarchy. Omitting the structural
fork, will look like this:
Note that each column will be in its own namespace, so class names won't conflict.
The question
Can anyone see faults with this approach? Can anyone think of a better solution?
Addendum
Here is some sample code how this is to be used. The namespace svg
may be replaced with canvas
:
var iView = document.getElementById( 'view' ),
iKandinsky = new svg.Kandinsky(),
iEpigone = new svg.Epigone(),
iTonyBlair = new svg.TonyBlair( iView, iKandinsky ),
iLayer = new svg.Layer(),
iZoomer = new svg.Zoomer(),
iFace = new svg.Rectangle( new Rect( 20, 20, 100, 60) ),
iEyeL = new svg.Rectangle( new Rect( 20, 20, 20, 20) ),
iEyeR = new svg.Rectangle( new Rect( 60, 20, 20, 20) );
iKandinsky.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iEpigone.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iFace.addChildren( iEyeL, iEyeR );
iZoomer.setZoom( new Point( 2, 2 ) );
iZoomer.addChild( iFace );
iLayer.addChild( iZoomer );
iTonyBlair.setContent( iLayer );
Essentially, in run-time clients compose instances hierarchy of Viewee subclasses; like so:
Say all these viewees are from the canvas hierarchy, they are rendered by traversing the hierarchy can calling paint()
on each viewee. If they are from the svg hierarchy, viewees know how to add themselves to the DOM, but there isn't paint()
traversal.
Best Answer
The second approach segregates interfaces better, following the interface segregation principle.
However, I would add a Paintable interface.
I would also change some names. No need to create confusion: