C ++中的接口类和工厂函数

时间:2013-06-17 18:28:33

标签: c++

在Meyers Effective C ++第31项(我的第3版中的第147页)中,他谈到了接口类。他展示了一个使用纯虚方法和派生类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类时,为什么我要使用这个'create'函数?

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

修改

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

6 个答案:

答案 0 :(得分:3)

如果以实际方式实例化RealPerson,则必须在编译时知道您需要RealPerson。工厂方法返回shared_ptr<Person>,因此代码可以在运行时决定要创建哪种Person;根据具体情况,该方法可能会决定为您提供FictionalPersonPlatosIdealPersonLongDeadPerson或其他任何内容。

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

答案 1 :(得分:3)

当你有多个时,选择正确的子类。

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

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

你不能创造'新人' - 它是虚拟的。

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

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

答案 2 :(得分:2)

  

当我能以正常方式实例化RealPerson类时,为什么我要使用这个'create'函数?

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

  

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

因为它们在基类中声明为virtual。是否在派生类中声明它们virtual是可选的;无论你是否愿意,他们都是虚拟的。

答案 3 :(得分:2)

一个简单的原因是你将create function扩展为这样的东西:

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类。

答案 4 :(得分:1)

  1. 您的代码可能是发布Person接口的库的客户端,甚至不知道是否存在RealPerson类。或者,对于名称以“John”开头的人,库可能具有优化的实现,并且可能会根据名称返回JohnPerson而不是RealPerson。作为客户,您不需要知道,也不应该关心。

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

  2. “一旦虚拟,永远是虚拟的。”当函数在基类中声明为virtual时,无论是否按照它们放置关键字,它的覆盖都是自动虚拟的。然而,将它作为提醒放在那里是一种很好的做法。在C ++ 11中,我们也强烈建议你也提供override说明符,以便编译器检查你是否真的在你想要的时候覆盖。

答案 5 :(得分:1)

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

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