Object-oriented – Parallel hierarchies – partly same, partly different

design-patternsinheritancemixinsmultiple-inheritanceobject-oriented

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:

3 parallel class hierarchies, the centre column shows the common parts, the left column is the canvas hierarchy and the right column shows the SVG hierarchy

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 of Composite, but such is not the case with the canvas hierarchy.
  • 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:

The three columns again, the left and right columns are parallel hierarchies, where each class also inherent from a common class. The common classes are not part of an hierarchy

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:

An image showing a hierarchy of objects like layer, rect, scroller, etc.

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:

// common

public interface IComposite {
    public void addChild(Composite e);
}

public interface IViewee extends IComposite{
    public void validate();
    public List<IBound> getAbsoluteBouns();
}

public interface IVisual {
    public List<IBound> getBounds();
}

public interface IRec {
}

public interface IPaintable {
    public void paint();
}

// canvas

public interface ICanvasViewee extends IViewee, IPaintable {
}

public interface ICanvasVisual extends IViewee, IVisual {
}

public interface ICanvasRect extends ICanvasVisual, IRec {
}


// SVG

public interface ISVGViewee extends IViewee {
    public void element();
}

public interface ISVGVisual extends IVisual, ISVGViewee {
}

public interface ISVGRect extends ISVGVisual, IRect {
}
Related Topic