- Objects: the Traditional View and the New View
- Encapsulation: the Traditional View and the New View
- Find What Is Varying and Encapsulate It
- Commonality/Variability and Abstract Classes
- Summary
Find What Is Varying and Encapsulate It
Using inheritance this way in design patterns
In Design Patterns: Elements of Reusable Object-Oriented Software, the Gang of Four suggests the following:
Or, as I like to rephrase it, "Find what varies and encapsulate it."
These statements seem odd if you only think about encapsulation as data-hiding. They are much more sensible when you think of encapsulation as hiding classes using abstract classes. Using composition of a reference to an abstract class hides the variations.
In effect, many design patterns use encapsulation to create layers between objectsenabling the designer to change things on different sides of the layers without adversely affecting the other side. This promotes loose-coupling between the sides.
This way of thinking is very important in the Bridge pattern, which will be discussed in Chapter 9, "The Bridge Pattern." However, before proceeding, I want to show a bias in design that many developers have.
Containing variation in data versus containing variation in behavior
Suppose I am working on a project that models different characteristics of animals. My requirements are the following:
Each type of animal can have a different number of legs.
Animal objects must be able to remember and retrieve this information.
Each type of animal can have a different type of movement.
Animal objects must be able to return how long it will take to move from one place to another given a specified type of terrain.
A typical approach of handling the variation in the number of legs would be to have a data member containing this value and having methods to set and get it. However, one typically takes a different approach to handling variation in behavior.
Suppose there are two different methods for moving: walking and flying. These requirements need two different pieces of code: one to handle walking and one to handle flying; a simple variable won't work. Given that I have two different methods, I seem to be faced with a choice of approach:
Having a data member that tells me what type of movement my object has.
Having two different types of Animals (both derived from the base Animal class)one for walking and one for flying.
Unfortunately, both of these approaches have problems:
Tight couplingThe first approach (using a flag with presumably a switch based on it) may lead to tight coupling if the flag starts implying other differences. In any event, the code will likely be rather messy.
Too many detailsThe second approach requires that I also manage the subtype of Animal. And I cannot handle Animals that can both walk and fly.
Handling variation in behavior with objects
A third possibility exists: have the Animal class contain an object that has the appropriate movement behavior. I show this in Figure 8-2.
Figure 8-2 Animal containing AnimalMovement object.
Overkill?
This may seem like overkill at first. However, it's nothing more than an Animal containing an object that contains the movement behavior of the Animal. This is very analogous to having a member containing the number of legsin which case an intrinsic type object is containing the number of legs. I suspect these appear more different in concept than they really are, because Figures 8-2 and 8-3 appear to be different.
Figure 8-3 Showing containment as a member.
Comparing the two
Many developers tend to think that one object containing another object is inherently different from an object having a mere data member. But data members that appear not to be objects (integers and doubles, for example) really are. In object-oriented programming, everything is an object, even these intrinsic data types, whose behavior is arithmetic.
Using objects to contain variation in attributes and using objects to contain variation in behavior are very similar; this can be most easily shown through an example. Let's say I am writing a point-of-sale system. In this system, there is a sales receipt. On this sales receipt there is a total. I could start out by making this total be a type double. However, if I am dealing with an international application, I quickly realize I need to handle monetary conversions, and so forth. I might therefore make a Money class that contains an amount and a currency. Total can now be of type Money.
Using the Money class this way appears to be using an object just to contain more data. However, when I need to convert Money from one currency to the next, it is the Money object itself that should do the conversion, because objects should be responsible for themselves. At first it may appear that this conversion can be done by simply having another data member that specifies what the conversion factor is.
However, it may be more complicated than this. For example, perhaps I need to be able to convert currency based on past dates. In that case, if I add behaviors to the Money or Currency classes I am essentially adding different behaviors to the SalesReceipt as well, based upon which Money objects (and therefore which Currency objects) it contains.
I will demonstrate this strategy of using contained objects to perform required behavior in the next few design patterns.