Software Reuse - the REBOOT consortium
Even-Andre Karlsson et al

REBOOT (REuse Based on Object-Oriented Techniques) was a four year, 10 company (from 6 European nations) consortium commissioned to synthesize a holistic approach to software reuse. The major elements of their results include: organization, methodology, training, and tools. In one portion of the book, 58 "design for reuse" guidelines are presented under the headings: general guidelines, OO guidelines, and reuse-specific guidelines. This list is a refactoring and summary of that material.


Use inheritance to express specialization on types.
Using inheritance structures is an important reuse-promoting technique that supports extension and refinement. Inheritance structures identified in early phases are likely to be usable across an entire problem domain, contrary to solution-specific structures that are likely to be identified in later stages.
Factor common responsibilities as high as possible up the inheritance hierarchy.
If a set of classes all support a common responsibility, they should inherit that responsibility from a common superclass. Create a common superclass if it does not already exist and if a good abstraction can be found.
If a generalization of several concrete classes can be identified, create a superclass to encapsulate the generalization.
This is a "bottom-up" guideline for use during the construction of classes. Generalization and abstraction is preferably performed in earlier development phases when the structure of each component is being architected. By capturing commonalities in higher abstractions such as superclasses, subclasses can focus on differences. The reuser can than find the most suitable abstraction from which to inherit. If commonalities are not captured in higher abstractions, the reuser is forced to make large modifications and/or extensive redefinitions.
Subclasses should be specializations.
The standard use of inheritance is to implement specialization relationships, not to achieve code sharing. In order to not confuse the maintainer or the reuser, other uses of inheritance should be avoided.
Create as many abstract classes as possible.
Defining as many abstract classes as possible means you have factored out as much common behavior as you could possibly foresee. Look for responsibilities, behaviors, and attributes that are duplicated, or could be common, in existing or future subclasses. Look for further useful abstractions. If you don't have, or cannot foresee, at least two subclasses of a candidate abstract class, reject it.
Use abstract classes as far down inheritance hierarchies as possible.
Only the leaves of a class hierarchy should be concrete. It is often easier to inherit from an abstract class that from a concrete class. An abstract class enforces a standard protocol and prepares for polymorphism. A concrete class must provide a definition for its data representation, and some subclasses are likely to need different representations. Since an abstract class does not have to provide a data representation, future subclasses can use any representation without fear of conflicting with the one they inherited. Also, in a concrete superclass it is easy to make over-restrictive assumptions about specializations.
Inhibit classes intended to be abstract from being instantiated.
If the virtual member functions of a class that should not be instantiated are made pure virtual, then several purposes are served. It forces a reuser to subclass the abstract base class before reusing it. It compels the reuser to study the protocol and semantics of the base class abstraction. And it specifies interface only and defers all implementation details, thus eliminating the problems that have caused such bitter controversy about the use of multiple inheritance.
Class hierarchies should be fairly deep and narrow.
Experience shows that class hierarchies are often too shallow and too wide. If there are more than ten subclasses directly below one superclass, it is recommended to look for a new abstraction level. The disadvantage of having too deep a hierarchy is the difficulty of comprehending its behavior, since the methods are spread in the hierarchy. When making a class hierarchy, the focus must be no modelling the real world and mapping the class hierarchy to normal abstractions.
Use multiple inheritance rarely.
Multiple inheritance is not common in the real world and it is important to focus on modelling the real world. It makes little sense to imply that an object is an instance of more than one thing at the same time. Multiple inheritance can also introduce ambiguity and increase complexity - both of which can cause difficulties when reusing or maintaining a component. If multiple inheritance is necessary, at least try to avoid ambiguous inheritance.
Do not use inheritance with cancellation (private inheritance).
Private inheritance in C++ is not an OO concept and it is not used in any OO design methodology. It may confuse the reuser, since it is difficult to examine what is inherited and therefore what is reused.


All methods that might be overridden, should be declared as virtual.
Defining methods as virtual tells a reuser which methods are intended to be overridden. If a non-virtual method is "redefined", the reuser must be more careful, since he will be reusing the class differently from how the developer of the component intended.
Always make destructors virtual in the base class.
This will ensure correct calling of destructors in an inheritance hierarchy. If a base class pointer is used to point to a subclass instance, and the base class destructor is virtual; then when the instance is deleted, the destructor of the actual subclass is called first, and every destructor all the way up the hierarchy is called. If the base class destructor is not virtual, only the destructor for the actual class of the pointer is called. If the base classes of a reusable hierarchy have their destructors declared virtual, then reusers will not be "surprised" by the behavior of the derived classes they write.


Distribute system intelligence evenly.
A very "intelligent" object knows and manages a lot of information, can do a lot of things, and affect many other objects. Such a class or subsystem will be hard to subclass or adapt. Distributing the intelligence among a variety of objects allows each object to know about relatively fewer things. Such objects will be easier to modify. The only advantage of the centralized approach is that it may be easier to see and understand the control and information flow.
Minimize the number of collaborations a class has with other classes or subsystems.
Classes should collaborate with as few other classes and subsystems as possible. Fewer collaborations means that the class is less likely to be affected by changes to other parts of the system. One way to accomplish this is to centralize the communication flowing into a subsystem. You can create a new class or subsystem to be the principal communications intermediary, or you can use an existing one for the role.
Factor implementation differences into "hasa" abstractions.
Not all problems are solved by extending the inheritance hierarchy. Some implementation differences are better treated by introducing a new "contained" abstraction. Too deep and large an inheritance hierarchy can be hard to understand. A more flat, and configurable, set of "hasa" relationships can be easier to comprehend and adapt than one large "isa" hierarchy.
Specify attributes as private.
This hides data representation and keeps the interface stable even if the implementation is changed. The reuser should not have to worry about the component's implementation. The interface for the reuser consists of the public and protected methods. The developer has the responsibility of providing a suitable interface for the reuser. By using this interface, the component is reused correctly, and misuse is prevented.
Use protected member functions instead of protected data members.
A subclass of a reusable component should not couple itself, or depend upon, the implementation choices of its base classes. It should use their public and protected interfaces just like outside users of the base classes use their public interfaces. If the component being reused is exchanged for a new version with a different data representation, the interface for the reuser need not change. This makes exchanging an old version for a new one easy.
Avoid using "friend".
The "friend" concept violates encapsulation and makes reuse more difficult. It is better to make one or more member functions friends than to make an entire class a friend.
Hide the copy constructor and assignment operator when these functions do not make sense.
This will inhibit misuse by preventing the reuser from performing inappropriate operations with the component.


Keep classes small - avoid large total protocols.
The total protocol for a class consists of all methods defined in the class and all its superclasses that are not redefined or overloaded. It is hard to reuse large classes (25 or more methods). It is easier to understand and modify or subclass a few smaller classes from different inheritance hierarchies instead. A large class may be a candidate for several smaller and less complex class hierarchies instead.
Keep classes small - avoid introducing too many methods in a single class.
It is harder to reuse a large class than a small inheritance hierarchy where common behavior is extracted to appropriate superclasses. When a large class is refactored into a small hierarchy, the reuser can choose the most suitable layer of abstraction to reuse.
Keep methods small.
It is easier to reuse by subclassing if the superclasses have small methods, since the behavior can be changed by overriding a few small methods instead of a few large ones. The smaller methods are easier to understand and modify. It is also easier to identify common behavior when the methods are small. This common behavior may be migrated to an abstract superclass which can then probably be reused as is. Every method with more than twenty lines of code should be inspected for potential modification.
One task - one method.
Each method should perform only one task to keep it simple. For example, decompose a method called putAndPrint() into two methods - put() and print(). The protocol of the class will be easier to understand and thus to reuse.
Keep the number of method arguments small.
Methods with more than five arguments are hard to read and therefore hard to reuse. It is less likely that standard protocols can be found when the number of arguments is large. Every method with more than three arguments (except constructors) should be inspected for potential modification.


Make subsystems of strongly coupled classes that implement one unit of functionality.
This is a good way of managing the design at a higher level of abstraction. Classes in each subsystem are likely to be affected by the same changes in requirements, and are suitable for reuse as a unit. Even if the precise changes cannot be predicted, it is still worth the effort to predict which objects or classes will be affected by the same changes. Such objects or classes should be grouped together to minimize the effect of each change.
Introduce subsystems late in the architectural design phase.
While a quick functional decomposition into subsystems allows early division of work among development teams, it has several drawbacks for reuse. If the introduction of subsystems is postponed, the maturing class and object designs allow coupling issues to be more realistically evaluated, and a better division into loosely-coupled, highly-cohesive subsystems to be realized.


Eliminate "switch" statements on object types.
Use polymorphism instead. Explicitly checking the type of objects results in maintainability burdens when new object types are created - because a new test condition must be added to every switch statement in the application. By using polymorphism instead, the reuse component will be more adaptable.
Avoid "casting down" the inheritance hierarchy.
Explicitly casting from a base class to a derived class creates unnatural dependencies within a class hierarchy. Explicit knowledge of, and dependence on, object types requires extensive administration and inhibits reuse. If a component uses downcasting, then the reuser must expand its type administration if new subclasses are defined.
Declare member functions and parameters as "const" when possible.
Liberal use of "const" clearly documents developer intentions and allows the compiler to enforce those intentions. Misunderstanding and misuse on the part of the reuser are significantly curtailed.
Avoid implicit and explicit "inline" in header files.
Any "inline" implementations in a header file violate encapsulation and compromise information hiding. The reuser should not be tempted to "peek beneath the covers" of the class's implementation. Use explicit "inline" in the class's source file instead.
Implement implicitly-generated class methods.
To avoid unexpected behavior, prohibit the compiler from implementing implicitly-generated methods (constructor, destructor, copy constructor, and assignment operator) by implementing them yourself. This is especially important for classes with dynamically allocated memory and "uses-a" relations with other classes. The explicit definitions show the reuser how to use the class and how to define these methods if the classes in the component are inherited. Misuse will be prevented.