4

在 Meyers Effective C++ item 31(我的第 3 版中的 p147)中,他谈到了接口类。他展示了一个带有纯虚方法和派生类 RealPerson 的 Person 类的示例。它看起来像这样,尽管我已经对其进行了简化并添加了一些简单的实现。

#include <string>

class Person {
public:
    virtual ~Person() {};
    virtual std::string name() const = 0;    
};

class RealPerson : public Person {
public:
    RealPerson(const std::string& name) : theName(name) {}
    virtual ~RealPerson() {};
    virtual std::string name() const { return theName; }

private:
    std::string theName;
};

然后他接着说,我们可以轻松地创建一个“工厂功能”来创建真实的人:

std::shared_ptr<Person>Person::create(const std::string& name) // EDIT - removed tr1::
{
    return std::shared_ptr<Person>(new RealPerson(name));
}

当我可以以正常方式实例化 RealPerson 类时,为什么还要使用这个“创建”函数?

另外,为什么派生类方法是“虚拟的”。

编辑

感谢您的评论。我理解目的(现在),但我看不出它与任何层次结构中的任何基类有什么不同——这种工厂函数在所有基类中都是典型的吗?它也有一种杂乱无章的感觉,而不是语言的一部分。但我对 C++ 比较陌生,所以这可能只是我的问题。

4

6 回答 6

4

当您有多个子类时,它用于选择正确的子类。

并将该逻辑保留在一个地方以使其可维护(OO+程序老兄!!)

如果您有一个可以存储或允许输入多个人的数据库或数据输入屏幕,当您想编写一个函数来读取屏幕以创建新的人员对象,或者从数据库加载记录时,第一行不能是 'new RealPerson(name);'。数据输入屏幕或数据库记录可能正在谈论“ImaginaryFriend(name)”。在最简单的情况下,这将通过屏幕上的下拉菜单或数据库列中的代码/字符串给出。

而且你不能创建“新人”——它是虚拟的。

因此,在您开始将字段或数据库列加载到新对象之前,您可以通过调用工厂并将其传递一个“代码”(或其他与对象一致的“赠品”)来创建新对象,其中,最基本的情况,将由工厂检查(比如通过简单的 switch 语句)以确定调用哪个“新”。这样一来,您的程序就不会乱用 switch 语句来制作正确的具体对象。

当然,它可以变得比这更复杂。这只是一种情况,如果您确实使用继承,在构建正确的子类时必须处理。

于 2013-06-17T19:00:57.007 回答
3

如果你RealPerson以真实的方式实例化 a ,你必须在编译时知道你想要一个RealPerson. 工厂方法返回一个shared_ptr<Person>,所以代码可以在运行时决定Person创建什么样的;该方法可能会根据情况决定给您一个FictionalPerson,PlatosIdealPersonLongDeadPerson其他任何内容。

派生类方法是虚拟的,因为您可能希望从 派生其他类,YoungRealPerson例如RealPerson.

于 2013-06-17T18:34:51.403 回答
2

一个简单的原因是您将 create 函数扩展为如下所示:

std::tr1::shared_ptr<Person>Person::create(const std::string & obj,const std::string& name)
{

    if(obj=="RealPerson")
        return std::tr1::shared_ptr<Person>(new RealPerson(name));
    else if ( obj == "ImaginaryPerson")
        return std::tr1::shared_ptr<Person>(new ImaginaryPerson(name)); 
      // Lets assume there exists some other class ImaginaryPerson: public Person .. 
    return std::tr1::shared_ptr<Person>();
}

通过使用不同的 obj 调用 create 函数,您可以创建不同类型的 Person 对象。

基类被标记为虚拟,因为有人可能希望将 RealPerson 类扩展为具有不同name()实现的 RealPersonWithHair 类。

于 2013-06-17T18:33:54.343 回答
2

当我可以以正常方式实例化 RealPerson 类时,为什么还要使用这个“创建”函数?

这样用户就不需要知道实现Person接口的具体类型;他们只需要了解创建他们想要的那种人的工厂功能。

另外,为什么派生类方法virtual

因为它们是virtual在基类中声明的。是否virtual在派生类中声明它们是可选的;无论您是否这样做,它们都是虚拟的。

于 2013-06-17T18:32:06.010 回答
1
  1. 您的代码可能是发布Person接口的库的客户端,甚至不知道RealPerson类的存在。或者,该库可能对名字以“John”开头的人有一个优化的实现,并且可能会根据名字返回 aJohnPerson而不是 a 。RealPerson作为客户,您不需要知道也不应该关心。

    请注意,这不一定是严格的库客户端场景,它也可以是一个应用程序的不同模块/部分。尽管如此,“编码到接口,而不是实现”是一种很好的做法,因为它强制封装并促进单元测试。

  2. “一次虚拟,永远虚拟。” 当一个函数virtual在基类中声明时,它的覆盖器自动是虚拟的,无论你是否将关键字放在它们旁边。但是,最好将其放在那里作为提醒。在 C++11 中,还强烈建议您也提供说明override符,以便编译器在您打算时检查您是否实际覆盖。

于 2013-06-17T18:36:50.423 回答
1

您可以在wikipedia或大量有关设计模式的书籍中找到有关工厂函数的信息。简而言之,它是一个具有多种行为的构造函数,它将根据参数(RealPerson 或 FootballPlayer 或派生自 Person 的任何其他类)返回不同的对象。

派生类中的 virtual 关键字不是必需的。这是一个提醒,该方法是虚拟的。

于 2013-06-17T18:38:58.767 回答