Professional C__ - Marc Gregoire [116]
Designing the Polymorphic Spreadsheet Cell
The SpreadsheetCell class is screaming out for a hierarchical makeover. A reasonable approach would be to narrow the scope of the SpreadsheetCell to cover only strings, perhaps renaming it StringSpreadsheetCell in the process. To handle doubles, a second class, DoubleSpreadsheetCell, would inherit from the StringSpreadsheetCell and provide functionality specific to its own format. Figure 8-5 illustrates such a design. This approach models inheritance for reuse since the DoubleSpreadsheetCell would only be subclassing StringSpreadsheetCell to make use of some of its built-in functionality.
FIGURE 8-5
If you were to implement the design shown in Figure 8-5, you might discover that the subclass would override most, if not all, of the functionality of the base class. Since doubles are treated differently from strings in almost all cases, the relationship may not be quite as it was originally understood. Yet, there clearly is a relationship between a cell containing strings and a cell containing doubles. Rather than using the model in Figure 8-5, which implies that somehow a DoubleSpreadsheetCell “is-a” StringSpreadsheetCell, a better design would make these classes peers with a common parent, SpreadsheetCell. Such a design is shown in Figure 8-6.
FIGURE 8-6
The design in Figure 8-6 shows a polymorphic approach to the SpreadsheetCell hierarchy. Since DoubleSpreadsheetCell and StringSpreadsheetCell both inherit from a common parent, SpreadsheetCell, they are interchangeable in the view of other code. In practical terms, that means:
Both subclasses support the same interface (set of methods) defined by the base class.
Code that makes use of SpreadsheetCell objects can call any method in the interface without even knowing whether the cell is a DoubleSpreadsheetCell or a StringSpreadsheetCell.
Through the magic of virtual methods, the appropriate instance of every method in the interface will be called depending on the class of the object.
Other data structures, such as the Spreadsheet class described in Chapter 7, can contain a collection of multityped cells by referring to the parent type.
The Spreadsheet Cell Base Class
Since all spreadsheet cells are subclasses of the SpreadsheetCell base class, it is probably a good idea to write that class first. When designing a base class, you need to consider how the subclasses relate to each other. From this information, you can derive the commonality that will go inside the parent class. For example, string cells and double cells are similar in that they both contain a single piece of data. Since the data is coming from the user and will be displayed back to the user, the value is set as a string and retrieved as a string. These behaviors are the shared functionality that will make up the base class.
A First Attempt
The SpreadsheetCell base class is responsible for defining the behaviors that all SpreadsheetCell subclasses will support. In our example, all cells need to be able to set their value as a string. All cells also need to be able to return their current value as a string. The base class definition declares these methods, but note that it has no data members. The reason will be explained afterwards.
class SpreadsheetCell
{
public:
SpreadsheetCell();
virtual ~SpreadsheetCell();
virtual void set(const std::string& inString);
virtual std::string getString() const;
};
When you start writing the .cpp file for this class, you very quickly run into a problem. Since the base class of spreadsheet cell contains neither a double nor a string, how can you implement it? More generally, how do you write a parent class that declares the behaviors that are supported by subclasses without actually defining the implementation of those behaviors?
One possible approach is to implement “do nothing” functionality for those behaviors. For example, calling the set() method on the SpreadsheetCell base class will have no effect because the base