Fork me on GitHub

Design Principles Applied in VGLViewer

An index of design goodness in VGLViewer

Here's a few design principles I've applied in the design of VGLViewer. I'm not going to point out basic principles like "One Responsibility Rule", because I think that following such principles is a bit obvious! Incidentally, Martin Fowler points out that some of the principles I've followed are not mandatory to follow.

The Open Closed Principle (OCP)

"A module of software should be open to extension while being closed to change."

Low-level packages in VGLViewer leave it to their higher-level users to implement their application-specific implementation(s) of some key abstract class(es). For example, package vglDocument leaves it to the user (in this case package vglViewer) to implement VGLElementVisitor, AbstractVGLFontMetrics and AbstractVGLFontFactory. We can extend vglViewer to print a VGLDocument by implementing a new kind of VGLElementVisitor, or switch from using AWT to using Swing, without needing to change package vglDocument in any way, by implementing new types of AbstractVGLFontFactory and AbstractVGLFontMetrics.


The Dependency Inversion Principle (DIP)

"Packages that implement high-level policy should not depend upon packages that implement low-level policy."

A high-level policy is one which is less application-dependent than a low-level policy. It is more abstract than the low-level policy. This is analogous to the concept of high-level and low-level programming languages.

An example of where DIP has been implemented in VGLViewer is in package boundaryMap. This package implements a high level policy with its general KD-Tree algorithm. By offering the abstract BoundaryMapElement for users to implement, it is not concerned with the low-level policy of how BoundaryMap is implements by elements in package vglDocument, such as how they split themselves about a boundary edge when they don't fit neatly in a half-space.


The Interface Segregation Principle (ISP)

"Clients should not be forced to depend upon interfaces that they do not use."

A good example of where this principle is followed is package vglRenderer. VGLDocumentRenderer could have implemented VGLElementVisitor and visited all its VGLDocuments elements itself. However, this would have exposed all the VGLElementVisitor methods in the VGLDocumentRenderer API, which would not be of any use to the user. Therefore, VGLDocument has a private VGLElementVisitor implementation which it applies to its VGLDocument.


The Acyclic Dependencies Principle (ADP)

"Dependencies between packages should be a Directed Acyclic Graph."

Among the packages in VGLViewer, there are no cyclic dependencies. In this case, the dependencies go across clearly-defined layer (API) boundaries. This allows
  • re-use of lower-level packages, such as sdaz or boundaryMap, for other things besides rendering VGL,
  • you to clearly see what the responsibilities of each package are, and
  • easier documentation, where I can require you to read documentation for each package in a particular order and follow references to package documents which you have read earlier, not some time in the future.

ADP was enabled in this application through the use of the Adapter design pattern in package vglViewer. For example, vglViewer was able to glue a SDAZDriver to a DocViewPanel by adapting the DocViewPanel to the SDAZSubject interface. Packages like sdaz do not need to know anything about who was using it at higher levels; as mentioned under The Open-Closed Principle, they require instead that high level packages to supply their application-specific implementation(s) of some key abstract class(es).


The Stable Dependencies Principle (SDP)

"Dependencies between packages in a design should be in the direction of the stability of the packages, that is, a package should only depend upon packages that are more stable than it is. Stability of a package is defined as the likelihood that its API will be changed, either because it is hard to do so, or because change is not likely to be required."



An example of where SDP is applied is package boundaryMap. Because it follows the Open-Closed Principle by leaving open a way to extend it without needing modifications (via new implementations of the abstract BoundaryMapElement), it is not likely to need any changes to its API. Other higher-level packages, such as vglViewer, can therefore safely depend upon it in the knowledge that its API is not going to change. Another reason why package boundaryMap is not likely to change is because it does not depend upon any other package.

The packages in VGLViewer form a non-cyclic tree. The stability of a package is proportional to the number of other packages that depend upon it and is inversely proportional to the number of other packages it depends upon. As we move up the acyclic dependency tree of packages in VGLViewer, the packages have more dependencies upon lower packages and less dependencies upon them by higher packages. Their stability decreases accordingly. This particular aspect is sometimes called the Stability Principle.


The Stable Abstractions Principle (SAP)

"Packages that are maximally stable should be maximally abstract. Instable packages should be concrete. The abstraction of a package should be in proportion to it's stability."

In VGLViewer, the lower level packages in the dependency tree are maximally abstract; they require users to supply their application-dependent implementation of certain abstract classes. The lower level packages are also maximally stable since they have less dependencies than higher-level packages (if any). A top-level package such as vglViewer is the least stable, and accordingly has no abstract classes in it.


Avoiding Inheritance for Implementation

"Do not subclass just to benefit from inheriting some of the implementation of the superclass."

This is my personal favourite, since I really like composition, especially since design patterns and heuristics show how to compose properly. The only places I have used inheritance for implementation are
  • vglViewer, where ViewPanel extends java.awt.Panel, to contain a DocViewPanel and a few buttons, and
  • docViewPanel, where DoubleBufferedPanel extends java.awt.Panel and is extended in turn by DocViewPanel.

I was happy doing inheritance in these cases because it is the convention when working with java.awt.Panels. Also, DoubleBufferedPanel has a paintBuffer Template Method which must be implemented by DocViewPanel.

As an exercise, and in order to demonstrate composition as an alternative to inheritance, I made VGLElement in package vglDocument an interface rather than the usual abstract class that this sort of element tends to be. To do this, I took all the stuff which would have been implemented in VGLElement and split it out into a VGLProperties, to be associated with its concrete implementations.


Liskov Substitution Principle

"If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behaviour of P is unchanged when o1 is substituted for o2 then S is a subtype of T."

An example of an application of LSP is in package vglViewer. DocViewMouseListener implements the State design pattern, where it maintains a reference to either a ListeningForMarqueeMouse or a ListeningForSDAZMouse at any given instant, according to it's current state. The reference is of type MouseHandler. Since both these classes implement MouseHandler (with no difference in contract discernable to DocViewListener), DocViewMouseListener can dispatch events it gets to the reference without having to know which of the two concrete classes the reference points to.


Separation of Concerns

"Minimize the overlap of functionality between different parts of a system, so that the parts are as distinct as possible. This allows one to approach problems independently."

This is demonstrated by the model-view seperation in the VGLViewer architecture. Package vglDocument implements the document model, while package vglRenderer implements a means of rendering it. A benefit of this is that we could could change the way the document model is rendered by implementing a new renderer package, without having to change the way vglDocument is implemented.

Here's a better example. I originally implemented the VGLDocument to load itself from an XML source using the Interpretor pattern, where the VGLDocuments constructor was given a pre-constructed DOM tree to initialise itself from. The VGLDocument constructor would iterate through second-level DOM nodes; for each recognised node, it would instantiate a corresponding VGLElement implementation and pass the subtree rooted by that node to its constructor, for it to initialise itself from. This was not a seperation of concerns since it tied the document model to a particular XML syntax.

I separated the concerns of storing the document model from how it was loaded by implementing VGLDocumentBuilder, which is driven by VGLDocumentLoader. The document model is now not concerned with how it is loaded.