Professional C__ - Marc Gregoire [119]
SpreadsheetCell* cellArray[3];
The 0th element of the array is set to point to a new StringSpreadsheetCell; the first is also set to a new StringSpreadsheetCell, and the second is a new DoubleSpreadsheetCell.
cellArray[0] = new StringSpreadsheetCell();
cellArray[1] = new StringSpreadsheetCell();
cellArray[2] = new DoubleSpreadsheetCell();
Now that the array contains multityped data, any of the methods declared by the base class can be applied to the objects in the array. The code just uses SpreadsheetCell pointers — the compiler has no idea at compile time what types the objects actually are. However, because they are subclasses of SpreadsheetCell, they must support the methods of SpreadsheetCell.
cellArray[0]->set("hello");
cellArray[1]->set("10");
cellArray[2]->set("18");
When the getString() method is called, each object properly returns a string representation of their value. The important, and somewhat amazing, thing to realize is that the different objects do this in different ways. A StringSpreadsheetCell will return its stored value. A DoubleSpreadsheetCell will first perform a conversion. As the programmer, you don’t need to know how the object does it — you just need to know that because the object is a SpreadsheetCell, it can perform this behavior.
cout << "Array values are [" << cellArray[0]->getString() << "," <<
cellArray[1]->getString() << "," <<
cellArray[2]->getString() << "]" <<
endl;
return 0;
}
Future Considerations
The new implementation of the SpreadsheetCell hierarchy is certainly an improvement from an object-oriented design point of view. Yet, it would probably not suffice as an actual class hierarchy for a real-world spreadsheet program for several reasons.
First, despite the improved design, one feature of the original is still missing: the ability to convert from one cell type to another. By dividing them into two classes, the cell objects become more loosely integrated. To provide the ability to convert from a DoubleSpreadsheetCell to a StringSpreadsheetCell, you could add a typed constructor. It would have a similar appearance to a copy constructor but instead of a reference to an object of the same class, it would take a reference to an object of a sibling class.
class StringSpreadsheetCell : public SpreadsheetCell
{
public:
StringSpreadsheetCell();
StringSpreadsheetCell(const DoubleSpreadsheetCell& inDoubleCell);
// Omitted for brevity
};
Code snippet from PolymorphicSpreadsheet\StringSpreadsheetCell.h
With a typed constructor, you can easily create a StringSpreadsheetCell given a DoubleSpreadsheetCell. Don’t confuse this with casting, however. Casting from one sibling to another will not work, unless you overload the cast operator as described in Chapter 18.
You can always cast up the hierarchy, and you can sometimes cast down the hierarchy, but you can never cast across the hierarchy unless you have changed the behavior of the cast operator.
The question of how to implement overloaded operators for cells is an interesting one, and there are several possible solutions. One approach is to implement a version of each operator for every combination of cells. With only two subclasses, this is manageable. There would be an operator+ function to add two double cells, to add two string cells, and to add a double cell to a string cell. Another approach is to decide on a common representation. The preceding implementation already standardizes on a string as a common representation of sorts. A single operator+ function could cover all the cases by taking advantage of this common representation. One possible implementation, which assumes that the result of adding two cells is always a string cell, is as follows:
const StringSpreadsheetCell operator+(const StringSpreadsheetCell& lhs,
const StringSpreadsheetCell& rhs)
{
StringSpreadsheetCell newCell;
newCell.set(lhs.getString() + rhs.getString());
return newCell;
}
Code snippet from PolymorphicSpreadsheet\SpreadsheetTest.cpp