【问题标题】:How do I create an automatic key value factory pattern in C++?如何在 C++ 中创建自动键值工厂模式?
【发布时间】:2018-01-02 14:04:51
【问题描述】:

我的确切问题有点难以解释,所以这里有一个简化版本: 我有各种类型的动物(模块)和一个庇护所(模块管理器)。这些包括狗,猫和鸟。所有者(父母)能够调用庇护所的功能,其中包括他想要采用的动物类型的字符串和通用项目列表(参考),以使动物的生活更轻松。物品清单是可变的,但必须包括所有且仅包括特定动物所需的所有物品,这些物品因动物而异(狗需要项圈和皮带,猫需要猫树和垃圾箱,鸟需要笼子等等..)。此列表还包括对所有者的引用,因此动物知道它属于谁。

基本上它应该是这样的:

Animal * getAnimal(String name, void * Items, int size_items)
//From Customer Class
Parrot * lester=dynamic_cast<Parrot*>(getAnimal("parrot", [this, new Cage()]))

现在我需要的是一种自动化工厂模式,它可以以某种方式将类存储在键值数组中,并能够使用它们的构造函数创建它们的实例,如下所示:

Parrot(void * items)

以后程序员应该可以在运行时通过调用将新类型的动物添加到庇护所

addAnimal(String Key, Class type)

我希望这能准确地解释我的问题。基本上我需要一种方法来创建父类的标准化子类,用户可以在其中添加更多子类而无需回到经典

if x=="Parrot" then return new Parrot(a)

【问题讨论】:

  • 不使用boost的更详细的解释:blog.fourthwoods.com/2011/06/04/factory-design-pattern-in-c
  • 感谢接缝有用
  • 我不明白你希望用户在运行时创建新的动物,例如 dog(),而 dog 是一个继承自动物但你没有实现的新类?
  • 我总是对常见的 OOP 示例(例如动物)感到困惑。制作父类动物和子类(例如狗或鹦鹉)对我来说意义不大。它们之间没有足够的共性,而且闻起来像 Java 'Object'。 C++ 不是这样的。

标签: c++ inheritance module


【解决方案1】:

如果它们都是从基类派生的,您可以使用工厂模式,例如来自 Modern C++ Design's factory (注​​意,这本书是 C++11 之前的,但这个答案会针对更现代的编译器进行更新) .

下面创建了一些具体的动物并将它们放在一个向量中,然后对它们中的每一个调用eat() 方法:

#include <memory>
#include <iostream>
#include "factory.h"

struct Animal
{
    virtual ~Animal() = default;
    virtual void eat() = 0;
};

struct Cat : Animal
{
    static constexpr auto ID = 1;
    explicit Cat( const void* ) { }
    void eat() override { std::cout << "Mice\n"; }
};

struct Parrot : Animal
{
    static constexpr auto ID = 2;
    explicit Parrot( const void* ) { }
    void eat() override { std::cout << "Crackers\n"; }

    struct Data
    {
        double age;
        std::string name;
    };
};

// Create the factory object
auto g_factory = MyUtil::Factory<std::unique_ptr<Animal>, int, std::function<std::unique_ptr<Animal>(const void*)>>{};

void RegisterTypesWithFactory()
{
    // Map an ID to a creator function in the factory
    g_factory.Register( Cat::ID,    []( const void* data ) { return std::make_unique<Cat>( data );    } );
    g_factory.Register( Parrot::ID, []( const void* data ) { return std::make_unique<Parrot>( data ); } );
}

int main()
{
    // Configure the factory
    // Note: Registration can be done any time, e.g., later based on input 
    // from a file. I do them all at once here for convenience of illustration.
    RegisterTypesWithFactory();

    // Arbitrary initialization data of differing types 
    // (to be passed as opaque data to ctors with void* or std::any)
    const auto catData  = { 1, 2, 3 };
    const auto birdData = Parrot::Data{ 5.6, "polly" };

    // Create some objects with the factory
    auto       animals  = std::vector<std::unique_ptr<Animal>>{};
    animals.emplace_back( g_factory.Create( Cat::ID,    &catData  ) );
    animals.emplace_back( g_factory.Create( Parrot::ID, &birdData ) );

    // Do something with the objects
    for( const auto& a : animals )
    {
        a->eat();
    }
}

哪个打印:

Mice
Crackers

查看它在 Wandbox 上的实时运行。

顺便说一句,在 C++ 中,void* 不受欢迎。一个合适的替代品可能是std::any(如果您缺少 C++17,则可能是 boost::any)。

【讨论】:

  • 接缝有点复杂,但是在说车辆的地方,不应该说动物吗?我不明白这行代码的作用: g_factory.Register( Cat::ID, &std::make_unique ); g_factory.Register( Parrot::ID, &std::make_unique );
  • @user1510024 对不起,我根据我之前写的答案改编了这个。我现在已经更新了它并链接到它在 Wandbox 上运行。对Register() 的调用会动态设置工厂(即在运行时),这意味着您只能根据用户输入或文件提供的庇护所可用的内容来填充工厂。
  • 所以您基本上将函数保存在一个键值对中,该键值对依次获取 Animalitems 然后返回 Animal 实例?
  • @user1510024 是的。 Wandbox 链接有 factory.h 的源代码,它是从 Loki 的 C++03 版本更新的。
  • @user1510024 您也可以半自动化注册过程。有关这样做的更多信息,请参阅this Q&A。我会在这里做,但我看到你已经选择了一个答案,所以你已经削弱了我的动力。 ;-)
【解决方案2】:

正如 cmets 中所讨论的,您似乎正在寻找一个框架来将派生类型注册到工厂调度中。这实际上是一种非常流行的模式,在堆栈溢出中存在相当多的类似问题(和答案)(例如this one)。

this blog 中解释了从头开始的更一步一步的实现,这反过来又受到堆栈溢出时another question 的启发。

当然,使用 boost 的好处是很多东西已经实现,因此隐藏在你的主代码之外。 那么缺点是首先你需要依赖boost,其次,没有经验的程序员可能会觉得它很混乱。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-06-21
    • 1970-01-01
    • 2019-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多