Summary: We apply the Abstract Factory Design Pattern to abstract the manufacturing of the list structure and hide its implementation. Such abstract construction together with the abstract specification of the intrinisc structural behavior of the list and the abstract specification of extrinsic operations on the list constitute a minimal and complete abstract specification of what is called a list software component. It provides a framework for writing an open ended number of algorithms on list that are independent from any particular list implementation.
Information hiding is a tried-and-true design principle that advocates hiding all implementation details of software components from the user in order to facilitate code maintenance. It was first formulated by David L. Parnas (in 1971-1972) as follows.
IList is implemented in order to use it. The user should only program to the abstract specification.IList will be used. The implementor should write the implementation code based solely on the abstract specification.By adhering to the above, code written by both users and implementors will have a high degree of flexibility, extensibility, interoperability and interchangeability.
The list framework that we have developed so far has failed to hide MTList and NEList, which are concrete implementations of IList, the abstract specification of the list structure. In many of the list algorithms that we have developed so far, we need to call on MTList.Singleton or the constructor of NEList to instantiate concrete IList objects. The following is another such examples.
InsertInOrder.java
|
|
The above algorithm to insert in order an integer into an ordered list of integers can only be used for a very specific implementation of IList, namely the one that has MTList and NEList as concrete subclasses. How can we write list algorithms that can be used for ANY implementation of the abstract specification of the list structure represented by the abstract class IList?
We can achieve our goal by
MTList and NEList into interfaces with pure abstract structural behaviors. The concrete empty list and non-empty list implemented as MTList and NEList are now expressed as interfaces as follow.
|
|
|
|
Before we describe in general what the Abstract Factory Pattern is, let's examine what we have to do in the case of IList.
IListFactory, to manufacture empty and non-empty IList objects. Put IList, IMTList, INEList, IListVistor, and IListFactory in the same package. IListFactory is specified as followed.
IListFactory.java
|
|
IList, IListAlgo, and IListFactory prescribe a minimal and complete abstract specification of what we call a list software component. We claim without proof that we can do everything we ever want to do with the list structure using this specification.
IList objects will need to have an abstract factory as a parameter and use it to manufacture IList objects. We usually pass the factory as an argument to the constructor of the visitor. The visitor is thus not a singleton.
InsertInOrderWithFactory.java
|
|
The above algorithm only "talks" to the list structure it operates on at the highest level of abstraction specified by IList and IListFactory. It does know and does not care how IList and IListFactory are implemented. Yet it can be proved to be correct. This algorithm can be plugged into any system that subscribes to the abstract specification prescribed by IList, IListAlgo, and IListFactory.
IList as private static classes and thus hide them from all external code.
CompositeListFactory.java
|
|
IList instances. Below is an example of a unit test for the InsertInOrderWithFactory algorithm.
Test_InsertInOrderWithFactory.java
|
|
The above design process is an example of what is called the Abstract Factory Design Pattern. The intent of this pattern is to provide an abstract specification for manufacturing a family of related objects (for examples, the empty and non-empty IList) without specifying their actual concrete classes thus hiding all details of implementation from the user.
Our example of the list structure framework successfully delineates specification from implementation and faithfully adheres to the principle of information hiding.
IList, IMTList, INEList, IListAlgo, and IListFactory provide a minimal and complete abstract specification. InsertInOrderWithFactory is a concrete implementation of IListAlgo that performs a concrete operation on the host list. Yet this algorithm need only communicate with the list structure and the list factory via their public interface. It will work with any implementation of IList and IListFactory. CompositeListFactory is a concrete implementation of IListFactory. It uses the composite pattern and the visitor pattern to implement IList. It only communicates with IListAlgo at and knows nothing about any of its concrete implementation. The private static attributes provide the proper mechanism to hide all implementation details from all code external to the class.
Click here to access the complete javadoc documentation and UML class diagram of the list component described in the above.
Click here to download the complete source code and documentation of the list component described in the above.
The following is a direct quote from the Design Patterns book by Gamma, Helm, Johnson, and Vlissides (the Gang of Four - GoF).
"Frameworks thus emphasizes design reuse over code resuse...Reuse on this level leads to an inversion of control between the application and the software on which it's based. When you use a toolkit (or a conventional subroutine library software for that matter), you write the main body of the application and call the code you want to reuse. When you use a framework, you reuse the main body and write the code it calls...."
The linear recursive structure (IList) coupled with the visitors as shown in the above is one of the simplest, non-trivial, and practical examples of frameworks. It has the characteristic of "inversion of control" described in the quote. It illustrates the so-called Hollywood Programming Principle: Don't call me, I will call you. Imagine the IList union sitting in a library.
The above list framework dictates the design of all algorithms operating on the list structure:
IListAlgo. IListFactory. When we write an algorithm on an IList in conformance with its visitor interface, we are writing code for the IList to call and not the other way around. By adhering to the IList framework's protocol, all algorithms on the IList can be developed much more quickly. And because they all have similar structures, they are much easier to "maintain". The IList framework puts polymorphism to use in a very effective (and elegant!) way to reduce flow control and code complexity.
We do not know anything about how the above list framework is implemented, yet we have been able to write quite a few algorithms and test them for correctness. In order to obtain concrete lists and test an algorithm, we call on a concrete IListFactory, called CompositeListFactory, to manufacture empty and non-empty lists. We do not know how this factory creates those list objects, but we trust that it does the right thing and produces the appropriate list objects for us to use. And so far, it seems like it's doing its job, giving us all the lists we need.
Let's take a look back at what we've done with a list so far:
Is there something systematic going on here?
Notice that at every stage in our development of our current list framework, we have applied the same abstraction principles to the then current system to advance it to the next stage. Specifically, we have identified and separated the variant and invariant pieces of the system and defined abstract representations whenever needed.
This really tells us about some general characteristics of software development:
Are we done with our list refinements? Will we ever be "done"? What do the above characteristics say about the way we should approach software development?
Also, now that we have managed to abstract structure, behavior and construction, is there anything left to abstract? Or is there one more piece to our abstraction puzzle (at least)?