【问题标题】:Is it possible to make a factory in C++ that complies with the open/closed principle?是否可以在 C++ 中创建一个符合开放/封闭原则的工厂?
【发布时间】:2009-05-12 22:07:49
【问题描述】:

在我正在使用 C++ 处理的项目中,我需要为通过网络传入的消息创建对象。我目前正在使用factory method pattern 来隐藏对象的创建:

// very psuedo-codey
Message* MessageFactory::CreateMessage(InputStream& stream)
{
    char header = stream.ReadByte();

    switch (header) {
    case MessageOne::Header:
        return new MessageOne(stream);
    case MessageTwo::Header:
        return new MessageTwo(stream);
    // etc.
    }
}

我的问题是我很懒,不喜欢在两个地方写类名!

在 C# 中,我会在第一次使用工厂时进行一些反思(额外的问题:这是可以使用反射的,对吗?)但由于 C++ 缺乏反射,所以这不在讨论范围内。我考虑过使用某种注册表,以便消息在启动时向工厂注册,但这受到non-deterministic (or at least implementation-specific) static initialization order problem 的阻碍。

那么问题是,在尊重开闭原则的情况下,是否可以在 C++ 中实现这种类型的工厂,以及如何实现?

编辑:显然我想多了。我打算将这个问题作为“你将如何在 C++ 中做到这一点”,因为在其他语言中使用反射真的很容易。

【问题讨论】:

  • 使用 python 也很容易,因为您可以读取字符串(如消息标签/id)并在文件中查找类名,然后实例化您可以从中实例化对象的类。不过,我认为在 C++ 中没有一种“简单”的方法。
  • 我不明白为什么静态初始化顺序会阻止注册表。首先,它不必(并且可能不应该)是静态的,其次,即使是,初始化顺序只有在从其他静态访问时才会成为问题。如果是这样,那么您遇到的问题(过度使用全局变量)比这要严重得多。
  • 我的意思是,如果不使用单例或依赖静态初始化顺序,您将无法执行类似于 epatel 建议的操作。但现在这一切都没有实际意义,因为它过度设计了。

标签: c++


【解决方案1】:

我认为开放/封闭方法和 DRY 是很好的原则。但它们并不神圣。目标应该是使代码可靠且可维护。如果您必须执行不自然的行为以遵守 O/C 或 DRY,那么您可能只是让您的代码不必要地变得更加复杂而没有任何物质利益。

这里是几年前的something I wrote,我是如何做出这些判断的。

【讨论】:

  • 好文章。感谢您的实际检查。
【解决方案2】:

您不需要让您的代码同时遵循所有可能的原则。目标应该是尽可能多地坚持这些范式,而不是更多。不要过度设计你的解决方案——否则你很可能会得到意大利面条式的代码。

【讨论】:

    【解决方案3】:

    我已经回答了关于 C++ 工厂的另一个 SO 问题。如果对灵活工厂感兴趣,请参阅there。我尝试从 ET++ 中描述一种使用宏的旧方法,这对我来说非常有用。

    该方法基于宏并且易于扩展。

    ET++ 是一个将旧 MacApp 移植到 C++ 和 X11 的项目。在此过程中,Eric Gamma 等人开始考虑设计模式

    【讨论】:

    • +1 我喜欢它,但我已经标记了 foredecker 的回答,因为这整个问题都是多余的。
    【解决方案4】:

    您可以将创建消息的类(MessageOne、MessageTwo ...)转换为消息工厂,并在初始化时将它们注册到顶级 MessageFactory。

    消息工厂可以保存 MessageX::Header 的映射 -> MessageXFactory 类型的映射实例。

    在 CreateMessage 中,您会根据消息头找到 MessageXFactory 的实例,检索对 MessageXFactory 的引用,然后调用它的方法来返回实际 MessageX 的实例。

    有了新消息,您不再需要修改“开关”,只需将新 MessageXFactory 的实例添加到 TopMessageFactory。

    示例:

    #include <iostream>
    #include <map>
    #include <string>
    
    using namespace std;
    
    struct Message
    {
        static const int id = 99;
        virtual ~Message() {}
        virtual int msgId() { return id; }
    };
    
    struct NullMessage : public Message
    {
        static const int id = 0;
        virtual int msgId() { return id; }
    };
    
    struct MessageOne : public Message
    {
        static const int id = 1;
        virtual int msgId() { return id; }
    };
    
    struct MessageTwo : public Message
    {
        static const int id = 2;
        virtual int msgId() { return id; }
    };
    
    struct MessageThree : public Message
    {
        static const int id = 3;
        virtual int msgId() { return id; }
    };
    
    struct IMessageFactory
    {
        virtual ~IMessageFactory() {}
        virtual Message * createMessage() = 0;
    };
    
    struct MessageOneFactory : public IMessageFactory
    {
        MessageOne * createMessage()
        {
            return new MessageOne();
        }
    };
    
    struct MessageTwoFactory : public IMessageFactory
    {
        MessageTwo * createMessage()
        {
            return new MessageTwo();
        }
    };
    
    struct TopMessageFactory
    {
        Message * createMessage(const string& data)
        {
            map<string, IMessageFactory*>::iterator it = msgFactories.find(data);
            if (it == msgFactories.end()) return new NullMessage();
    
            return (*it).second->createMessage();
        }
    
        bool registerFactory(const string& msgId, IMessageFactory * factory)
        {
            if (!factory) return false;
            msgFactories[msgId] = factory;
            return true;
        }
    
        map<string, IMessageFactory*> msgFactories;
    };
    
    int main()
    {
        TopMessageFactory factory;
        MessageOneFactory * mof = new MessageOneFactory();
        MessageTwoFactory * mtf = new MessageTwoFactory();
    
        factory.registerFactory("one", mof);
        factory.registerFactory("two", mtf);
    
        Message * msg = factory.createMessage("two");
        cout << msg->msgId() << endl;
    
        msg = factory.createMessage("one");
        cout << msg->msgId() << endl;
    }
    

    【讨论】:

    • 但这只是用需要注册每种消息类型的映射来替换打开消息类型......仍然违反了打开/关闭原则。
    • @Neil:不,它不违反打开/关闭。因为您可以通过加载新的 DLL(共享库)来添加更多类型。在加载 DLL 时,它会向工厂映射注册消息构造函数。因此,您只需将 DLL 拖放到应用程序目录中即可动态扩展应用程序。
    • @Martin:答案从未提及 DLL,即使是更新后的形式。 @stefan:复制现在刚刚移到 factory.registerFactory 行。
    • @Neil:是的,没错。我会说它反映了开放/封闭原则的思想。我不是 dll 专家,但我想你会在目录中查找特定的文件名(例如 MessageX.dll 或 libMessageX ),加载与描述匹配的所有库,实例化它们并在工厂注册.
    • @Neil:它不需要是 DLL。原则是你不需要改变工厂方法(即关闭改变)。但是可以简单地通过将新版本注册到工厂映射中来动态添加新实现(即开放扩展[或任何术语]):-)
    【解决方案5】:

    首先,您的系统没有那么开放,因为您打开了一个 8 位字符,所以您的消息类型计数不会超过 256 ;-)

    开个玩笑,在这种情况下,我会使用一个小的模板工厂类(如果您将 char 消息类型放在非类模板 arg 中,或者仅使用该 char 作为状态,则为无状态),它接受您的流&和在其 T 模板 arg 上执行 new,传递 stream& 并返回它。您需要一个小的注册器类来声明为具有全局范围的静态,并使用管理器注册具体的 T 实例化工厂(通过抽象基类指针)(我们有一个通用的,它采用“工厂域”键) .在您的情况下,我不会使用地图,而是直接使用 256“插槽”数组来放入 factory_base*。

    您拥有工厂框架,它既简单又可重用。 --DD

    【讨论】:

      猜你喜欢
      • 2018-06-11
      • 1970-01-01
      • 2011-01-23
      • 1970-01-01
      • 1970-01-01
      • 2021-12-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多