Design For Reuse

From eqqon

Jump to: navigation, search

Contents

The Value of Single-Purpose-Classes

Most of the classes that get written are used once and never again. The same special problem the class is intended to solve will never appear again. The effort of creating and testing them is practically wasted because it's value is gained only once for a specific part of a software. Often, similar problems arise but adopting the existing classes is too expensive so we need to write new classes again from scratch. If we were able to write classes that are highly reusable we had the opportunity to build a huge resource of diversely applicable modules which could be reused over and over again without any overhead. The commercial potential of a highly reusable class is enormous for a software company.

A Design Guide to Highly Reusable Classes

To rip off these benefits it is essential to understand what makes a class highly reusable and what design guidelines to follow. Here they are:

Single Purpose Classes

The most important thing is to keep a class small and simple. The rule of thumb here is to analytically dissect the functionality and divide everything into different classes what can be divided. A functionality immediately looses its reuse potential if it is mixed with other functionalities in a hard coded way. A class which is made only for a single purpose and holds only a single simple responsibility is what you should always be going for. Such a class is easily reused because its implementation is slim and thus can be easily understood. The documentation of the class is small because only one simple functionality has to be described. The simple class can be combined with other classes dynamically to solve complicated problems.

A badly factored class having multiple responsibilities, when reused in a different context, always bears the risk of unforeseeable bugs resulting from a functional part of the class that does not fit in the new context. When attempting to reuse a multi-purpose-class it might be necessary to work around some aspects which are not needed in the reuse context effectively reducing the value of reusing it. The risk that the reusing engineer is misunderstanding how the multiple functionalities work together in a fat class is high. The result are bugs which are hard to spot which cut the value gained from reusing, or even worse, force you to write it all new.

Side-Effect Free Methods

The methods of a class must not have any side-effects. A side-effect is something that might not be expected by the caller of the method. Side-effects inherently poison any attempts to reuse. Side-effect eliminating workarounds nullify any value gained from reusing. The resulting solution is prone to errors and hard to understand.

If there are any mandatory side-effects which cannot be avoided at all convert them into regular expected functionality by asserting on pre- and post-conditions that enforce the side-effect behavior.

Naming

Names are not just hollow words. It is extremely important to name classes and methods (and all other identifiers) such that the name describes the purpose of the class/method as exactly as possible. Put some effort in finding a good name and the users of your class (who might be you yourself after some time) will find it tremendously easier to understand and reuse it.

Badly chosen names lead to bugs or even prevent possible reuse which is, from our point of view, the worst thing that could ever happen.

Decoupling through Events

Putting different functionality in different classes is often not enough to provide a reusable solution. If those classes were to notify each other by maintaining hard coded references to each other they would be too highly coupled to be reused singularly. Events resolve such reuse-stopping dependencies and render related classes replaceable by any other class. The interchangeability of a piece of functionality is most essential for achieving reusable code.

Use Interfaces to Decouple Unrelated Functionalities

Classes which implement services often need to call methods on their clients or even store them in a data structure. Using events for decoupling sometimes is not possible or efficient enough. The service class could enforce all their clients to inherit from an abstract client class or something like that but these unnecessary restrictions would decrease the usability and prevent reuse. The solution is to declare a client interface which is required for the client classes to request the services of the class in question.

Use the Strategy Pattern to Abstract from Specific Needs

Quite often when analytically dissecting the functionality of a design I come to a point where there might be multiple ways of doing something. Those who reuse your class will surely need something different than you need right now. This is when you should think about applying the Strategy Pattern [Gamma et al 95]. The functionality or algorithm that might be replaced by a similar but different one needs to be factored out and placed in its own class. The user may supply its own algorithm and replace yours.

Use Composition in Favor of Dubious Inheritance

Quite often the same problem where one could apply Strategy is solved by using inheritance. The badly factored class where the strategy pattern should have been applied is sub-classed and the specific parts are overridden. Despite the fact, that it is a commonly accepted way of solving the reuse problem, I think most of the time it were better to use Strategy instead of dubious inheritance. Inheritance is most useful if you are able to make use of polymorphism and late binding. If inheritance is used just to reuse some functionality in another unrelated class and nothing else then it surely is better to use composition instead. With composition, the other class that has a functionality your class needs is just used as is. It is owned or referenced by the user class instead of inherited. This leaves your user class free to inherit from a class that makes more sense to inherit from and brings you the freedom of replacing the used functionality without changing class hierarchies.

Kill any Application-Specific Code

Last but not least, it must be said, that application specific parts in a class are a great show stopper for reuse. Encapsulate any application specific things into application classes. To make sure, that it really works move your reusable class in a library project that is not part of the application. This will bring up any hidden dependencies which have to be eliminated.

A Practical Example

Once you get used to it, it is not very hard to follow the guide. Of course, this approach has a little "thinking and refactoring" overhead over a naive approach but in the end you quickly get a large set of small grained highly reusable classes which can be combined to flexible solutions of complicated problems with little effort.

I will try to explain the design guidelines on a small, easy to understand example where I started with a single not very reusable class and after some refactoring ended up with a quite flexible result consisting of six small highly reusable classes.

Selection of Objects in a GUI

Back then, in my inexperienced days, I was writing a graphics engine. It had a class named Canvas which could draw graphic objects and supported all sorts of user interaction like picking, moving, scrolling ... etc. Canvas also maintained a list of currently selected objects. As you can see, in my naive first try, I mixed a lot of functionalities in the Canvas class which was a huge monolithic unmaintainable chaos-class.

These days I had to extend an existing engine (Piccolo.NET) with object-selection interaction and, of course, I did not repeat this mistake. I created a class called SelectionController which should maintain the selection and implement the select/deselect behavior on top of the events OnClick ... etc.

The intended purpose of SelectionController was
Maintain a list of selected objects (for efficiency it holds them as keys in a Hashtable) and implement exclusive selection behavior. (Exclusive selection behavior means an object is deselected automatically if another object is selected, except if the CTRL key is pressed)

Soon I hit some problems. Piccolo's Click event was also fired if the up-location was far away from the down-location. In other words, dragging also fired a click event. I decided that the SelectionController should maintain a last_down_location of the mouse too. Wait a minute, I would need that in other locations of my program too. The class ClickController was born. SelectionController could be notified by the ClickController about click events and had one less unnecessary responsibility. The ClickControllers functionality could be easily cut-and-pasted into a different class without any problems. I was going with the design rule Single-Purpose-Classes.

Doing this I also found some other functionality that was unrelated to the original purpose of SelectionController. I broke it out into the class KeyboardController having the responsibility to maintain keyboard states and deliver them via a convenient interface (i.e IsCtrlPressed). Some days later I earned the fruits of doing so, because I found out, that I needed multiple SelectionControllers which could now share the same KeyboardController effectively not wasting memory and processor time by maintaining their own keyboard states each.

The next clue illustrates the Strategy Pattern-design guide. Of course It were possible that different selection mechanisms are needed. I immediately refactored SelectionController and changed so that it used a class named SelectionStrategy.Exclusive which implemented the behaviour described above. Later alternative behaviors like SelectionStrategy.Simple and SelectionStrategy.Radio followed because they were needed.

Now we already have six very simple classes that were initially mixed up in a single class. The reuse potential of SelectionController is much better now, because one has fine grained control over it's behavior and it is amazingly small and simple.

One last step had to be done yet. Realizing that SelectionController was not application specific I wanted to place it into a library. The problem was that the selection strategies needed to call two methods Select() and Unselect() on the graphic objects stored in the selection hashtable which are used to advise them to change their representation accordingly. To be highly reusable SelectionController and its strategies should work with all kinds of objects. These objects should not need inherit from a specific base class just to be clients of SelectionController. So I created the interface ISelectable which exposes the Select() and Unselect() methods. The incorporation of this interface eventually removed the last bit of application specific code and made SelectionController ubiquitously applicable and flexible.

--Henon 16:04, 9 December 2007 (CET)