4、单独的继承类
写StringSpreadsheetCell与DoubleSpreadsheetCell类只是实现定义在父类中的功能的实现的总是。因为想让客户能够实例化并且在string单元格与double单元格上工作,单元格不能是抽象的--它们必须实现继承自父类的所有干净的virtual成员函数。如果继承类不实现所有的来自基类的干净的virtual成员函数,那么继承类也是抽象的,客户也不能够实例化继承类的对象。
4.1、StringSpreadsheetCell类定义
StringSpreadsheetCell类定义在了叫做string_spreadsheet_cell的模块中。写StringSpreadsheetCell类定义的第一步是继承SpreadsheetCell。为了做到这一点,spreadsheet_cell模块应该被import进来。
下一步,继承的干净的virtual成员函数被重载,这一次没有被设置为0.
最后,string单元格添加 一个私有的数据成员,m_value,它保存了真实的单元格数据。该数据成员为std::optional,区分单元格的值从来没有被设置过还是被设置为空的字符串是可能的。
cpp
export module string_spreadsheet_cell;
export import spreadsheet_cell;
import std;
export class StringSpreadsheetCell : public SpreadsheetCell
{
public:
void set(std::string_view value) override;
std::string getString() const override;
private:
std::optional<std::string> m_value;
};
4.2、StringSpreadsheetCell实现
set()成员函数是直接的,因为内部表示已经是string。getString()成员函数必须要保持住m_value是optional类型,可能没有值。当m_value没值的时候,getString()应该返回一个缺省的string,在本例中是空的string。用optional的value_or()成员函数是很容易的。通过使用m_value.value_or(""),如果m_value包含一个真实值,真实值就会被返回;否则的话,就会返回空的string。
cpp
void set(std::string_view value) override { m_value = value; }
std::string getString() const override { return m_value.value_or(""); }
4.3、DoubleSpreadsheetCell类定义与实现
double遵从同样的模式,但是用了不同的逻辑。除了给来自基类的接受string_view的set()成员函数,也提供了新的set()成员函数,允许客户用double参数设置值。还有,它提供了一个新的getValue()成员函数作为double来访问它的值。两个新的私有的静态的成员函数用于相互转换string与doulbe。与在StringSpreadsheetCell一样,它有一个叫做m_value的数据成员,这次是optional<double>类型。
cpp
export module double_spreadsheet_cell;
export import spreadsheet_cell;
import std;
export class DoubleSpreadsheetCell : public SpreadsheetCell
{
public:
virtual void set(double value);
virtual double getValue() const;
void set(std::string_view value) override;
std::string getString() const override;
private:
static std::string doubleToString(double value);
static double stringToDouble(std::string_view value);
std::optional<double> m_value;
};
set()成员函数使用double是显而易见的,与在getValue()中的实现一样。string_view重载使用了私有的静态的成员函数stringToDouble()。getString()成员函数返回了保存的double值作为string,或者返回一个空的string,如果没有值被保存的话。它使用std::optional的has_value()的成员函数来查询optional是否有值。如果它有值,value()成员函数用于查询。
cpp
virtual void set(double value) { m_value = value; }
virtual double getValue() const { return m_value.value_or(0); }
void set(std::string_view value) override { m_value = stringToDouble(value); }
std::string getString() const override
{
return (m_value.has_value() ? doubleToString(m_value.value()) : "");
}
你可能已经注意到了用层次结构实现spreadsheet单元格的一个巨大的好处是--代码简化了很多。每个类可以是自我为中心的,只需要处理自己的功能。
记住doubleToString()与strinToDouble()的实现省略了,因为与以前是一样的,就不浪费大家的精力了。
5、利用多态
现在SpreadsheetCell层次结构是多态的了,客户代码可以利用多态提供的这么多好处。下面的测试程序探索了许多特性。
为了展示多态,测试程序声明了三个SpreadsheetCell指针的一个vector。记住因为SpreadsheetCell是一个抽象类,不能生成该类型的对象。然而,仍然可以有SpreadsheetCell的指针或引用,因为它实际上指向继承类中的一个。该vector,因为它是父类SpreadsheetCell的vector,允许保存两个继承类的各种各样的混合。这意味着vector的元素可以是StringSpreadsheetCell或DoubleSpreadsheetCell。
cpp
vector<unique_ptr<SpreadsheetCell>> cellArray;
vector最先的两个元素设置为指向新的StringSpreadsheetCell,而第三个是一个新的DoubleSpreadsheetCell。
cpp
cellArray.push_back(make_unique<StringSpreadsheetCell>());
cellArray.push_back(make_unique<StringSpreadsheetCell>());
cellArray.push_back(make_unique<DoubleSpreadsheetCell>());
现在vector包含多种类型的数据,在基类中声明的任何成员函数都可以应用于vector中的对象。代码只是使用了SpreadsheetCell指针--编译器在编译时不知道对象实际上是什么类型的。然而,因为对象继承自SpreadsheetCell,它们必须支持SpreadsheetCell的成员函数。
cpp
cellArray[0]->set("hello");
cellArray[1]->set("10");
cellArray[2]->set("18");
当调用getString()成员函数时,每个对象恰当地返回一个代表其值的string。重要的,有点儿令人惊奇的事情是要意识到不同的对象行为方式不同。StringSpreadsheetCell返回保存的值,或者空的string。DoubleSpreadsheetCell如果有值的话首先执行转换;否则的话,它返回一个空的string。作为程序员,不必知道对象做了什么--只需要知道由于对象是一个SpreadsheetCell,它可以执行这种行为。
cpp
println("Vector: [{},{},{}]", cellArray[0]->getString(),
cellArray[1]->getString(),
cellArray[2]->getString());