假设我有以下界面:
struct Person {
std::string name;
unsigned short age;
};
class ContainerInterface {
public:
virtual ~ContainerInterface () = default;
virtual void addPerson (Person const&) = 0;
virtual std::optional<Person> getPerson (std::string const& name) const = 0;
virtual bool hasPerson (std::string const& name) const = 0;
};
该接口可以通过以下方式实现:
class Container: public ContainerInterface {
public:
virtual void addPerson (Person const& person) override {
_people.push_back(person);
}
virtual std::optional<Person> getPerson (std::string const& name) const override {
for (const auto& person: _people) {
if (person.name == name) return person;
}
return std::nullopt;
}
virtual bool hasPerson (std::string const& name) const override {
return getPerson(name).has_value();
}
private:
std::vector<Person> _people;
};
虽然这看起来很简单,但有一个问题:像
hasPerson
这样的方法实际上只是 getPerson(name).has_value()
的别名。 ContainerInterface
的所有实现都应该共享这一点,不应该由实现本身来强制执行。事实上,没有什么可以阻止实现执行这样的操作:
class BrokenContainer: public Container {
public:
virtual bool hasPerson (std::string const& name) const override {
return false;
}
};
当然,我可以通过将
hasPerson
作为 ContainerInterface
的一部分来解决这个问题:
class ContainerInterface {
public:
virtual ~ContainerInterface () = default;
virtual void addPerson (Person const&) = 0;
virtual std::optional<Person> getPerson (std::string const& name) const = 0;
virtual bool hasPerson (std::string const& name) const final;
};
// Should always do this, regardless of implementation
bool ContainerInterface::hasPerson (std::string const& name) const {
return getPerson(name).has_value();
}
但是我的
ContainerInterface
不再是一个纯粹的界面了。在某些生产设置中,我可以将一些宏粘贴到我的接口上以将其标记为纯接口,并检查它是否未实现任何方法。如果我使用这种方法,我将无法将我的类标记为纯接口。
另一种解决方法是将
hasPerson
实现为免费函数:
bool hasPerson (ContainterInterface const& container, std::string const& name) {
return container.getPerson(name).has_value();
}
但这感觉不太令人满意,因为
hasPerson
听起来应该是 ContainerInterface
的一种方法。
有没有更优雅的方法来保持
ContainerInterface
是一个纯接口,同时强制所有实现与 hasPerson
具有相同的含义?
解决方法是不要让其他类直接继承
ContainerInterface
。
class ContainerInterface { // pure
public:
virtual ~ContainerInterface() = default;
virtual void addPerson (Person const&) = 0;
virtual std::optional<Person> getPerson (std::string const& name) const = 0;
virtual bool hasPerson (std::string const& name) const = 0;
private:
ContainerInterface() = default; // private
friend class ContainerBase; // except for ContainerBase
};
class ContainerBase : public ContainerInterface {
public:
bool hasPerson (std::string const& name) const override final {
// ^^^^^
return getPerson(name).has_value();
}
};
class Container : public ContainerBase {
// ^^^^^^^^^^^^^
//...
};
这本质上与为接口提供一个不能被覆盖的默认实现相同:
class ContainerInterface {
public:
virtual ~ContainerInterface() = default;
virtual void addPerson (Person const&) = 0;
virtual std::optional<Person> getPerson (std::string const& name) const = 0;
virtual bool hasPerson (std::string const& name) const final {
return getPerson(name).has_value();
}
};