在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 ++比较陌生,所以这可能只是我的问题。
答案 0 :(得分:3)
如果以实际方式实例化RealPerson
,则必须在编译时知道您需要RealPerson
。工厂方法返回shared_ptr<Person>
,因此代码可以在运行时决定要创建哪种Person
;根据具体情况,该方法可能会决定为您提供FictionalPerson
,PlatosIdealPerson
,LongDeadPerson
或其他任何内容。
派生类方法是虚拟的,因为您可能希望从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)
您的代码可能是发布Person
接口的库的客户端,甚至不知道是否存在RealPerson
类。或者,对于名称以“John”开头的人,库可能具有优化的实现,并且可能会根据名称返回JohnPerson
而不是RealPerson
。作为客户,您不需要知道,也不应该关心。
请注意,这不一定是严格的库客户端场景,它也可以是一个应用程序的不同模块/部分。尽管如此,“编码到接口而不是实现”是一种很好的做法,因为它强制执行封装并促进单元测试。
“一旦虚拟,永远是虚拟的。”当函数在基类中声明为virtual
时,无论是否按照它们放置关键字,它的覆盖都是自动虚拟的。然而,将它作为提醒放在那里是一种很好的做法。在C ++ 11中,我们也强烈建议你也提供override
说明符,以便编译器检查你是否真的在你想要的时候覆盖。
答案 5 :(得分:1)
您可以在wikipedia或许多有关设计模式的书籍中找到有关工厂功能的信息。简而言之,它是一个具有不同行为的构造函数,它将根据参数(RealPerson或FootballPlayer或从Person派生的任何其他类)返回不同的对象。
不需要派生类中的virtual关键字。提醒一下,该方法是虚拟的。