gpt4 book ai didi

java - 模式 : Create and translate between data objects and wire formats

转载 作者:IT老高 更新时间:2023-10-28 23:20:47 26 4
gpt4 key购买 nike

我倾向于只使用我已经在应用程序代码的各个地方使用的映射器模式。但我认为在这种特殊情况下它实际上可能不是最合适的:

任务:

  • 我需要根据给定的规范实现数据对象。规范为每个对象类型定义了多个版本,因此我有一个类 CarV1 和 CarV2,代表规范的每个版本。
  • 我需要在类(在本例中为 C++,但问题是关于通用设计)和线格式(Json、Protocol Buffers)之间转换这些模型,反之亦然。
  • 对象的构建相当简单。

  • 正如我所说,我通常会使用映射器模式,定义映射器接口(interface)和具体映射器以在每种格式之间进行映射。在这种情况下,我征求您的意见有两件事:
  • 我只会使用映射器模式在两种且只有两种类型的格式之间进行映射,例如一个数据库对象和一个模型类。在这种情况下,我已经有了第三种格式,在不久的将来,我可能必须添加更多格式以进行转换。
  • 版本控制在映射之上增加了一些复杂性,我认为两者之间需要另一个间接性。

  • 我读过翻译模式 [1],但从未使用过它。我认为它在某种程度上适合,但不完全适合。

    我也考虑过抽象工厂。这将允许创建类似的对象(在我的例子中版本化对象)。但它不太适合对象表示之间的映射。

    我应该使用什么模式,为什么?

    [1] http://www.iro.umontreal.ca/~keller/Layla/translator.pdf

    最佳答案

    实现目标

    我们将编写一个自动翻译器。 假设我们有一个代表我们的有线格式的对象:

    JsonObject wire_data;

    为方便起见,我们可以想象我们的 JsonObject有一个 add_field成员函数:
    wire_data.add_field("name", "value"); 

    然而 JsonObject的实际接口(interface)实际上无关紧要,本文的其余部分不依赖于它以任何特定方式实现。

    我们希望能够编写这个函数:
    template<class Car>
    void add_car_info(JsonObject& object, Car car) {
    // Stuff goes here
    }

    具有以下约束:
  • Car有一个字段,例如Car::getMake() , 我们的函数 add_car_info应该自动将该字段添加到 json 对象
  • Car没有字段,我们的函数不需要做任何事情。
  • 我们的实现不应该依赖于 Car派生自任何事物,或成为任何事物的基类
  • 我们的实现应该使添加新字段变得微不足道,而不会破坏向后兼容性。

  • 具有四个独立 Car 类的示例

    假设您有四个汽车类。它们都不共享基类;他们公开的领域各不相同;并且您将来可能会添加更多汽车类别。
    struct Car1
    {
    std::string getMake() { return "Toyota"; }
    std::string getModel() { return "Prius"; }
    int getYear() { return 2013; }
    };
    struct Car2
    {
    std::string getMake() { return "Toyota"; }
    int getYear() { return 2017; };
    };
    struct Car3
    {
    std::string getModel() { return "Prius"; }
    int getYear() { return 2017; }
    };
    struct Car4
    {
    long long getSerial() { return 2039809809820390; }
    };

    现在,
    JsonObject wire_data;
    Car1 car1;
    add_field(wire_data, car1);

    应该相当于
    Car1 car1; 
    wire_data.add_field("car make", car1.getMake());
    wire_data.add_field("car model", car1.getModel());
    wire_data.add_field("year", car1.getYear());

    尽管
    Car2 car2;
    add_field(wire_data, car2);

    应该相当于
    Car2 car2; 
    wire_data.add_field("car make", car2.getMake());
    wire_data.add_field("year", car2.getYear());

    我们如何实现 add_car_info以一种通用的方式?

    弄清楚哪些汽车具有哪些领域是一个棘手的问题,尤其是因为 C++没有动态反射,但我们可以使用静态反射来实现(而且效率也会更高)!

    现在,我将把功能委托(delegate)给一个代表翻译器的对象。
    template<class Car>
    void add_car_info(JsonObject& wire_object, Car car) {
    auto translator = getCarTranslator();

    // This lambda adds the inputs to wire_object
    auto add_field = [&](std::string const& name, auto&& value) {
    wire_object.add_field(name, value);
    };
    // Add the car's fields.
    translator.translate(add_field, car);
    }

    它看起来像 translator对象只是踢,可以在路上,但是有一个 translator对象将使书写变得容易 translator用于汽车以外的东西。

    我们如何实现神奇的翻译器?

    让我们从 getCarTranslator 开始.对于汽车,我们可能会关心四件事:制造型号、年份和序列号。
    auto getCarTranslator() {
    return makeTranslator(READ_FIELD("car make", getMake()),
    READ_FIELD("car model", getModel()),
    READ_FIELD("year", getYear()),
    READ_FIELD("serial", getSerial()));
    }

    我们在这里使用了一个宏,但我保证它是唯一的一个,而且它不是一个复杂的宏:
    // This class is used to tell our overload set we want the name of the field
    class read_name_t
    {
    };

    #define READ_FIELD(name, field) \
    overload_set( \
    [](auto&& obj) -> decltype(obj.field) { return obj.field; }, \
    [](read_name_t) -> decltype(auto) { return name; })

    我们在两个 lambda 表达式上定义了一个重载集。其中一个获取对象的字段,另一个获取用于序列化的名称。

    为 lambda 实现重载集

    这是非常直接的。我们只是创建一个继承自两个 lambda 表达式的类:
    template <class Base1, class Base2>
    struct OverloadSet
    : public Base1
    , public Base2
    {
    OverloadSet(Base1 const& b1, Base2 const& b2) : Base1(b1), Base2(b2) {}
    OverloadSet(Base1&& b1, Base2&& b2)
    : Base1(std::move(b1)), Base2(std::move(b2))
    {
    }
    using Base1::operator();
    using Base2::operator();
    };

    template <class F1, class F2>
    auto overload_set(F1&& func1, F2&& func2)
    -> OverloadSet<typename std::decay<F1>::type, typename std::decay<F2>::type>
    {
    return {std::forward<F1>(func1), std::forward<F2>(func2)};
    }

    使用一点点 SFINAE 实现翻译器类

    第一步是创建一个读取单个字段的类。它包含一个进行读取的 lambda。如果我们可以应用 lambda,我们就应用它(读取字段)。否则,我们不应用它,什么也不会发生。
    template <class Reader>
    class OptionalReader
    {
    public:
    Reader read;
    template <class Consumer, class Object>
    void maybeConsume(Consumer&& consume, Object&& obj) const
    {
    // The 0 is used to dispatch it so it considers both overloads
    maybeConsume(consume, obj, 0);
    }

    private:
    // This is used to disable maybeConsume if we can't read it
    template <class...>
    using ignore_t = void;

    // This one gets called if we can read the object
    template <class Consumer, class Object>
    auto maybeConsume(Consumer& consume, Object& obj, int) const
    -> ignore_t<decltype(consume(read(read_name_t()), read(obj)))>
    {
    consume(read(read_name_t()), read(obj));
    }

    // This one gets called if we can't read it
    template <class Consumer, class Object>
    auto maybeConsume(Consumer&, Object&, long) const -> void
    {
    }
    };

    翻译器需要一堆可选的应用程序,然后依次应用它们:
    template <class... OptionalApplier>
    class Translator : public OptionalApplier...
    {
    public:
    // Constructors
    Translator(OptionalApplier const&... appliers)
    : OptionalApplier(appliers)... {}

    Translator(OptionalApplier&&... appliers)
    : OptionalApplier(appliers)... {}

    // translate fuction
    template <class Consumer, class Object>
    void translate(Consumer&& consume, Object&& o) const
    {
    // Apply each optional applier in turn
    char _[] = {((void)OptionalApplier::maybeConsume(consume, o), '\0')...};
    (void)_;
    }
    };

    制作 makeTranslator现在功能真的很简单。我们只是带了一堆读者,然后用它们来制作 optionalReader s。
    template <class... Reader>
    auto makeTranslator(Reader const&... readers)
    -> Translator<OptionalReader<Reader>...>
    {
    return {OptionalReader<Reader>{readers}...};
    }

    结论

    这是一个很长的帖子。我们必须建立很多基础设施才能让一切正常工作。但是,它使用起来非常简单,除了我们希望使用的字段外,它不需要任何关于我们将它应用于哪些类的知识。

    我们可以很容易地为很多东西编写翻译器!

    图像翻译器示例

    例如,这里有一个图片和图像的翻译器,它也考虑了图片的宽度和高度等不同的通用名称。

    请记住,提供给翻译器的任何图像类都可以选择实现这些方法中的任何一个。
    auto getImagesTranslator() {
    // Width and height might be implemented as `getWidth` and `getHeight`,
    // Or as `getRows` and `getCols`
    return makeTranslator(READ_FIELD("width", getWidth()),
    READ_FIELD("height", getHeight()),
    READ_FIELD("width", getCols()),
    READ_FIELD("height", getRows()),
    READ_FIELD("location", getLocation()),
    READ_FIELD("pixel format", getPixelFormat()),
    READ_FIELD("size", size()),
    READ_FIELD("aspect ratio", getAspectRatio()),
    READ_FIELD("pixel data", getPixelData()),
    READ_FIELD("file format", getFileFormat()));
    }

    Here's the complete implementation

    关于java - 模式 : Create and translate between data objects and wire formats,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55367816/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com