在模型-视图-控制器中镜像C++模型类层次结构

bq9c1y66  于 2023-02-26  发布在  其他
关注(0)|答案(1)|浏览(121)

我有一个系统,它用C++中复杂的类层次结构来描述许多具有复杂数据和行为的设备。系统的这一部分对这些设备的屏幕表示一无所知,部分原因是解耦,部分原因是许多不同屏幕表示的可能性。
这里MVC Design Pattern/Model-View-Controller用来连接视图和模型,但是作为高级模式,它没有提到在特定语言中镜像模型的类层次结构。
现实需要为每个视图创建一个类的镜像层次结构。不仅如此,我还应该为每个视图创建一个工厂,它将为Model对象创建相应的View类对象。这是大量肮脏和相似的代码。每次有人在Model类层次结构中添加一个新类时(新设备),他必须记得更新工厂,因为知道类SpecficDeviceModel和SpecficDevice2DView之间关系的唯一人是开发者,对于编译器,它们是独立的类,当然可以。
有些人要求提供这种"肮脏和类似代码"的例子,所以下面是工厂代码的总体思路:

NodeView* ViewNodesFactory(NodeModel* nodeModel) {
    std::string class_name = typeid(*nodeModel).name();
    class_name = class_name.substr(sizeof("class ") - 1);

    if (class_name == "DeviceType1") { return new DeviceType1View; } 
      else if (class_name == "DeviceType2") { return new DeviceType2View; } 
      else if (class_name == "DeviceType3") { return new DeviceType3View; }
  
    assert(0); // Type is not supported
    return new DeviceDefaultView;
}

此代码的主要问题:
1.容易在类名中引入印刷错误。
1.每次将类添加到模型中时,都应该更新工厂。
1.类抽取的名称是"hacky"和容易出错的,因为如果有人将类封装在命名空间中,名称将改变并且代码将中断,因此必须使其更加复杂;这里我只展示了一个概念。
1.有了几十个类,这将是经典的"意大利面代码"。
所以这就是我的问题。随着C++最近的20/23创新,有没有什么方法可以简化这个任务?有没有一种方法可以用许多视图类来表示许多模型类,而不需要大量的"连接"代码?

    • 保留**:

当然,我可以写一个复杂的宏,将用于代替声明的意见,将履行工厂数据库。
当然,我可以根据我的命名约定编写python脚本来更新Factory。
但对我来说,这些都是复杂的"hacky"变通方法。在C中有什么直接的方法来解决这个问题吗?据我所知,我们在C中仍然没有这样做的反射。

hec6srdp

hec6srdp1#

一个相当简单的改进是在模型的基类NodeModel中有一个虚拟工厂方法create_view,然后工厂方法可以委托给被覆盖的方法,因此,当你引入一对新的具体模型/视图时,你永远不必接触工厂方法。
下面是一个粗略的例子:

#include <typeindex> 
#include <unordered_map>
#include <functional>
#include <memory>
#include <iostream>

// base class for your views
struct NodeView
{
    // just for demonstration purposes:
    virtual void say_hello() const = 0;
};

// abstract base class for your models
struct NodeModel
{
    virtual std::shared_ptr<NodeView> create_view() const = 0;
};

// the factory function delegates to NodeModel::create_view
std::shared_ptr<NodeView> ViewNodesFactory(NodeModel const& nodeModel) {
    return nodeModel.create_view();
}

// A concrete model/view implementation follows. 

struct ConcreteView : public NodeView
{
    void say_hello() const override 
    {
        std::cout << "Hello from ConcreteView\n";
    }
};

struct ConcreteModel : public NodeModel
{
    std::shared_ptr<NodeView> create_view() const override
    {
        return std::make_shared<ConcreteView>();
    }
};

int main()
{
  
    ConcreteModel x;

    auto view = ViewNodesFactory(x);
    view->say_hello();
}

https://godbolt.org/z/oK77fMzhf
缺点是,模型不是独立于视图的。如果你想让模型完全不受视图的影响,并且保持对同一个模型使用不同视图的可能性,你必须能够在运行时注册要创建的视图的类型。为了能够做到这一点,你可以存储一个unordered_map,它具有std::type_index作为键和(类型擦除的)工厂方法作为值。
下面是一个例子,其中的工厂是一个单例函子。

#include <typeindex> 
#include <unordered_map>
#include <functional>
#include <memory>
#include <iostream>

// base class for your views
struct NodeView
{
    // just for demonstration purposes:
    virtual void say_hello() const = 0;
};

// base class for your models (must be polymorphic for typeid!)
struct NodeModel
{
    virtual ~NodeModel(){}
};

// the factory function delegates to NodeModel::create_view
class ViewFactory
{
public:

    std::shared_ptr<NodeView> create(NodeModel const& nodeModel) {

        auto idx = std::type_index(typeid(nodeModel));

        if ( factories.contains(idx)) {
            return factories.at(idx)();
        }

        throw std::out_of_range("Not registered");
    }

    static ViewFactory& get_instance() 
    {
        static ViewFactory factory;
        return factory;
    }

    template <typename Model, typename F>
    void register_factory(F&& f)
    {
        factories.insert(
            {
                typeid(Model), 
                Factory(std::forward<F>(f))
            }
        );
    }

    ViewFactory(ViewFactory const&) = delete;
    ViewFactory(ViewFactory&&) = delete;
private:
    ViewFactory() = default;

    using Factory = std::function<std::shared_ptr<NodeView>()>;
    using Map = std::unordered_map<std::type_index, Factory>;
    Map factories;
};

// A concrete model/view implementation follows. 

struct ConcreteView : public NodeView
{
    void say_hello() const override 
    {
        std::cout << "Hello from ConcreteView\n";
    }

    // This is optional: You could also register a lambda or any other
    // factory as long as the signature matches
    static std::shared_ptr<NodeView> create()
    {
        return std::make_shared<ConcreteView>();
    }
};

struct ConcreteModel : public NodeModel
{};

int main()
{
  
    ConcreteModel x;

    ViewFactory::get_instance().register_factory<ConcreteModel>(
        &ConcreteView::create
    );

    auto view = ViewFactory::get_instance().create(x);
    view->say_hello();
}

https://godbolt.org/z/Wcs4hxE3e
注意,这里没有涉及到C20/C23的魔力。第二个例子只是为了方便而使用C++20,因为使用了unordered_map::contains方法。我不认为概念/自动参数对你的使用场景有多大用处:您可以摆脱一些与类型擦除工厂方法和动态调度有关的开销,但我假设您仍然需要能够在异构容器中存储模型和视图,因此无论如何您仍然需要多态性。

相关问题