Framework-Based Software Development in C++
Gregory Rogers
Introduction
Framework-based software development requires a different approach to
solving problems. It's not really top-down or bottom-up, it's sort of
inside-out. You start by studying your current problem. Then you
compare it to other problems and determine those aspects of your problem
that are unique. Those aspects that are not unique are built into
frameworks. The frameworks are then tied together and extended for a
specific problem.
The methodology is very dependent on a set of emerging standards that, when
used together, comprise what is described as an
object infrastructure. These standards are:
- STL
- CORBA
- ODMG-93 (an industry standard API to object databases)
It is a bit complicated to combine these standards presently because the
products that implement them have evolved from different lineages.
A catalog of design patterns is presented. The catalog is fairly small and
its patterns are geared toward framework development using C++, CORBA, and
ODMG-93. The applicability and depth of discussion of each does not
compare with the Gang-of-Four catalog.
- Blackboard
- CORBA-friendly class
- Detail filtering
- Generic algorithm
- Interpreter
- Name-value pairs
- Observer
- Persistent data manager
- Persistent singleton
- Protocol
- Specification engine
- Thresholding
Fourteen design rules are also included. Many originated in various
textbooks and articles but now exist as popular styles and idioms.
-
Design base classes to be "inheritance friendly" by making data members
and functions protected instead of private,
unless you have a good reason to do otherwise.
-
Never return error conditions from function calls. Use exceptions for
all error reporting.
-
Name each persistent object, allowing anyone to get a reference to that
object by name.
-
Make arguments to functions string or d_String
instead of const char* to avoid core dumps due to
dereferencing NULL.
-
Use const char* instead of char* whenever possible.
-
Use the following contract between caller and callee: if non-const
pointer is a return value, the caller is responsible for deleting the
space pointed to when finished using the return value.
-
To return a single small value, return it by value as the return value
of the function. To return a single large value, fill in a non-const
reference to an object passed as an argument by the caller.
-
For transitive operators, always return *this.
-
If a function argument is not of a built-in type, it should be passed
by reference.
-
Never add a member function whose purpose can be satisfied with a
combination of other member functions and trivial logic.
-
Assign a namespace to every framework.
-
Don't build frameworks that have any dependency on a specific GUI API.
-
Don't build a framework that relies on nonstandard components or tools,
unless you have the source code for it.
-
Use the ORB sparingly, preferably for control information being passed
between coarse-grained objects or passing small amounts of simple data.
Domain analysis
- Categorize the problem.
- Research similar problems.
- Prepare a domain description.
- Create list of characteristics describing problem domain.
- Reword characteristics to be more like requirements.
- Separate domain-specific characteristics from generic ones.
- Graphically partition domain-specific characteristics from generic ones.
- Create new generic characteristics.
- Identify facets by graphically clustering the generic characteristics.
- Create separate lists of characteristics for each cluster and show
existing assets from the repository.
- Analyze generic facets of the domain.
- Repeatedly review and refine the analysis.
Designing a framework
-
Identify applicable design patterns.
-
Map framework classes to roles. Decide on the names of classes in the
framework that will play the roles in the patterns. Also, name data members
and member functions that play roles in patterns.
-
Introduce new classes. Round out the remaining design with entities in the
domain that have not been accounted for in the roles of the selected patterns.
-
Decide how the framework will be extended. Consider the various techniques
that can be used to implement abstract classes:
- pure virtual member functions
- delegation
- protected virtual interface
- "object splitting" (instead of making an abstract class extendable via
inheritance, architect an object that associates the framework part with
the more application specific part by using a key attribute)
-
Design class interfaces. Map out division of labor across all the
classes. Define relationships. Incrementally specify each class's public
and protected sections.
-
Design primary usage scenarios. Document the timing-oriented behavior with
Object Interaction Diagrams.
-
Rework class declarations. Tune the data members and member functions in
response to the insight gained from the usage scenarios.
-
Apply the design rules.
-
Work through an example usage. Go back to the original problem that inspired
the domain analysis and framework and determine how you would extend the
framework to solve that problem.
-
Document the design. While the discovery of the design may emerge from
stream of consciousness and exploration activities, the presentation of the
design must take a logical, top-down approach.
-
Apply and document design metrics.
-
Hold a design review.
-
Incorporate comments from the review.
Implementing a framework
-
Document source code packaging strategy (e.g. one header file and one source
file per class).
-
Set up a source code control environment.
-
Pseudocode member function implementations.
-
Set up a build environment.
-
Code and compile the member function bodies.
-
Prepare manual pages.