Search code examples
javasubclassing

Implementing subclasses with sparse common functionality


I am building a system with a large number of classes all of which share common functionality but also have smaller amounts of functionality shared by overlapping subsets of classes. What are the best way/s of implementing this?

As an example I will take SVG (I am implementing a subset for chemistry), although I have examples in other areas such as chemistry. This, and related problems, have clear, immutable, design specifications in XML Schemas.

There is a base class SVGElement which has much common functionality. All other classes (SVGLine, SVGCircle...) are directly derived from this. They then have other functionality defined in the spec (through XMLattributes) such as:

 - x
 - width
 - fill
 - opacity

and many more. Note that the design is given - we cannot recreate new elements or attributes. For example SVG has both circle and ellipse (the classic problem) and one uses r(adius), the other width and height. Where subclasses share common functionality they can use common code.

There seem to be the following solutions:

  • implement each function in each subclass. (Error-prone and tedious)
  • implement all functions in superclass (SVGElement) and make derived methods no-ops. (Bloated and unreadable and frustrating for developers)
  • create interfaces (hasWidth) and create a delegate subclass for each (WidthImplementor). Each class then has a list of Interfaces (maybe up to 20 for each SVG attribute) and there maybe 100-200 such implementors.
  • autogenerate code from the schema. I have tried this and it works clunkily but I haven't got a pretty implementation. It is also not easily maintainable (Note most XML-to-Java systems are not appropriate for building library systems so I have to do much of this myself)

and maybe others.

I'm aware that there are other SO questions on how to design the abstraction but here that is largely fixed. I'd like a general solution, not just an SVG one (which is used as an understandable example). For example Chemical Markup Language (CML) also has 100-200 subclasses and 100-200 attributes, spread in a sparse matrix.


Solution

  • You might want to consider implementing the 'common' functionality as 'Decorators'. You can then apply these decorators at runtime, as you parse your SVG elements.

    abstract class SVGElement {
    
        public void draw (void) {
            return;
        }
    
        public boolean hasWidth (void) {
            return false;
        }
    
        public boolean hasHeight (void) {
            return false;
        }
    
        // ..
    }
    
    // An abstract decorator
    abstract class SVGElementDecorator extends SVGElement {
    
        protected SVGElement decoratedElement;
    
        public SVGElementDecorator (SVGElement decoratedElement) {
            this.decoratedElement = decoratedElement;
        }
    
        public void draw (void) {
            decoratedElement.draw ();
        }
    }
    
    // Decorator to add width functionality
    class WidthDecorator extends SVGElementDecorator {
    
        private double width;
    
        public WidthAndHeightDecorator (SVGElement decoratedElement) {
            super(decoratedElement);
        }
    
        // override base class implementation
        public boolean hasWidth (void) {
            return true;
        }
    
        // implement a new method
        public double getWidth (void) {
            return width;
        }
    
        public void draw (void) {
    
            // do special stuff for width
            // ..
    
            // then draw the rest
            decoratedElement.draw ();
        }
    }
    

    Then, when you see an ellipse:

    SVGElement ellipse = new SVGElement();
    ellipse = new WidthDecorator (ellipse);
    ellipse = new HeightDecorator (ellipse);