You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

通用序列化接口的设计模式选型:面向DAG通用对象库的自定义序列化实现方案咨询

你的现有方案确实存在明显的设计瑕疵——空的Serialized基类本质是一个无意义的标记接口,强制用户做向下转型更是违反了面向对象的封装原则,而且用户需要编写的样板代码太多,使用门槛很高。接下来我会给你几个更优雅的替代方案,覆盖不同的使用场景:


方案1:策略模式+通用序列化接口抽象

这个方案的核心是把序列化逻辑完全从Node类中剥离,定义一个与具体序列化库无关的抽象策略接口,让用户实现这个接口来适配他们喜欢的序列化工具。

首先定义抽象的DagSerializer接口:

class Node; // 前置声明

class DagSerializer {
public:
    virtual ~DagSerializer() = default;

    // 序列化单个Node的核心方法,用户需实现如何将Node信息写入序列化结构
    virtual void serialize_node(const Node& node) = 0;
    // 处理Node之间的关联(父/子关系)
    virtual void serialize_edge(const Node& parent, const Node& child) = 0;
};

然后修改Node类,提供遍历DAG并调用序列化策略的方法:

class Node {
public:
    // 保留原有的深度优先遍历访问者方法
    template <typename Visitor>
    void accept(Visitor& v) const {
        v.visit(*this);
        for(auto& child_weak : children) {
            if(auto locked_child = child_weak.lock()) {
                locked_child->accept(v);
            }
        }
    }

    // 新增:接受序列化策略的入口方法
    void serialize(DagSerializer& serializer) const {
        struct SerializationVisitor {
            DagSerializer& serializer;
            std::unordered_set<const Node*> visited; // 防止DAG循环引用重复序列化

            void visit(const Node& node) {
                if(visited.count(&node)) return;
                visited.insert(&node);
                
                serializer.serialize_node(node);
                // 序列化父节点关联
                for(const auto& parent : node.parents) {
                    serializer.serialize_edge(*parent, node);
                }
                // 序列化子节点关联
                for(const auto& child_weak : node.children) {
                    if(auto child = child_weak.lock()) {
                        serializer.serialize_edge(node, *child);
                    }
                }
            }
        };

        SerializationVisitor visitor{serializer};
        accept(visitor);
    }

protected:
    virtual ~Node(){}
private:
    std::vector<std::weak_ptr<Node>> children;
    std::vector<std::shared_ptr<Node>> parents;
};

用户只需实现DagSerializer适配目标序列化库,比如yaml-cpp:

#include <yaml-cpp/yaml.h>

class YamlDagSerializer : public DagSerializer {
public:
    YamlDagSerializer(YAML::Node& root) : root_(root) {
        root_["nodes"] = YAML::Node(YAML::NodeType::Sequence);
        root_["edges"] = YAML::Node(YAML::NodeType::Sequence);
    }

    void serialize_node(const Node& node) override {
        // 用dynamic_cast访问派生类成员,或给Node加自定义类型标识优化
        if(const auto* my_node = dynamic_cast<const MyNode<int>*>(&node)) {
            YAML::Node yaml_node;
            yaml_node["id"] = reinterpret_cast<uintptr_t>(&node); // 用地址做唯一标识
            yaml_node["value"] = my_node->value;
            root_["nodes"].push_back(yaml_node);
        }
    }

    void serialize_edge(const Node& parent, const Node& child) override {
        YAML::Node edge;
        edge["parent_id"] = reinterpret_cast<uintptr_t>(&parent);
        edge["child_id"] = reinterpret_cast<uintptr_t>(&child);
        root_["edges"].push_back(edge);
    }

private:
    YAML::Node& root_;
};

使用方式非常简洁:

YAML::Node yaml_root;
YamlDagSerializer serializer(yaml_root);
my_dag_root->serialize(serializer);
// yaml_root中已包含序列化后的DAG数据

优点:

  • 完全解耦DAG库与序列化库,DAG库无需依赖任何序列化工具
  • 用户实现逻辑清晰,仅需编写两个核心方法
  • 避免了不安全的向下转型(dynamic_cast可控,或可通过自定义类型标识优化)

缺点:

  • 需要手动处理DAG循环引用(通过visited集合),这是序列化DAG的固有问题

方案2:增强访问者模式,序列化逻辑完全外置

你的原方案已经用到访问者模式,可以通过双重分发扩展它,让访问者直接处理不同派生类的序列化,无需在Node中添加虚serialize方法。

首先修改Node类和访问者基类,支持双重分发:

// 抽象访问者基类
class NodeVisitor {
public:
    virtual ~NodeVisitor() = default;
    // 为每个可能的Node派生类声明visit方法(或用模板优化)
    virtual void visit(class MyNode<int>& node) = 0;
    virtual void visit(class MyNode<std::string>& node) = 0;
};

class Node {
public:
    // 纯虚accept方法,触发双重分发
    virtual void accept(NodeVisitor& visitor) = 0;

protected:
    virtual ~Node(){}
private:
    std::vector<std::weak_ptr<Node>> children;
    std::vector<std::shared_ptr<Node>> parents;
};

派生类实现accept方法:

template <typename T>
class MyNode : public Node {
public:
    void accept(NodeVisitor& visitor) override {
        visitor.visit(*this);
        // 遍历子节点继续序列化
        for(auto& child_weak : children) {
            if(auto child = child_weak.lock()) {
                child->accept(visitor);
            }
        }
    }

    T value;
};

用户编写序列化访问者,比如YAML版本:

#include <yaml-cpp/yaml.h>

class YamlSerializationVisitor : public NodeVisitor {
public:
    YamlSerializationVisitor(YAML::Node& root) : root_(root) {
        root_["nodes"] = YAML::Node(YAML::NodeType::Sequence);
        root_["edges"] = YAML::Node(YAML::NodeType::Sequence);
        visited_.reserve(64);
    }

    void visit(MyNode<int>& node) override {
        if(visited_.count(&node)) return;
        visited_.insert(&node);

        // 序列化当前节点
        YAML::Node yaml_node;
        yaml_node["id"] = reinterpret_cast<uintptr_t>(&node);
        yaml_node["type"] = "int_node";
        yaml_node["value"] = node.value;
        root_["nodes"].push_back(yaml_node);

        serialize_edges(node);
    }

    void visit(MyNode<std::string>& node) override {
        if(visited_.count(&node)) return;
        visited_.insert(&node);

        YAML::Node yaml_node;
        yaml_node["id"] = reinterpret_cast<uintptr_t>(&node);
        yaml_node["type"] = "string_node";
        yaml_node["value"] = node.value;
        root_["nodes"].push_back(yaml_node);

        serialize_edges(node);
    }

private:
    void serialize_edges(Node& node) {
        // 序列化父节点关联
        for(const auto& parent : node.parents) {
            YAML::Node edge;
            edge["parent_id"] = reinterpret_cast<uintptr_t>(parent.get());
            edge["child_id"] = reinterpret_cast<uintptr_t>(&node);
            root_["edges"].push_back(edge);
        }
        // 序列化子节点关联
        for(const auto& child_weak : node.children) {
            if(auto child = child_weak.lock()) {
                YAML::Node edge;
                edge["parent_id"] = reinterpret_cast<uintptr_t>(&node);
                edge["child_id"] = reinterpret_cast<uintptr_t>(child.get());
                root_["edges"].push_back(edge);
            }
        }
    }

    YAML::Node& root_;
    std::unordered_set<Node*> visited_;
};

使用方式:

YAML::Node yaml_root;
YamlSerializationVisitor visitor(yaml_root);
my_dag_root->accept(visitor);

优点:

  • 序列化逻辑完全与Node类解耦,DAG库无需任何序列化相关代码
  • 利用双重分发,类型安全,无需向下转型
  • 用户可根据派生类灵活定制序列化逻辑

缺点:

  • 抽象访问者基类需要提前知晓所有可能的派生类(可通过模板访问者缓解此问题)

方案3:CRTP实现静态多态的序列化

如果用户不需要运行时切换序列化库,而是编译时就确定工具,CRTP(奇异递归模板模式)是最优选择——它避免了虚函数开销,同时让派生类轻松接入序列化。

首先定义CRTP基类:

template <typename Derived>
class SerializableNode : public Node {
public:
    // 静态多态的serialize方法,调用派生类的具体实现
    template <typename Serializer>
    void serialize(Serializer& serializer) const {
        static_cast<const Derived*>(this)->serialize_impl(serializer);
        // 遍历子节点序列化
        for(const auto& child_weak : children) {
            if(auto child = child_weak.lock()) {
                if(auto serializable_child = dynamic_cast<const SerializableNode*>(child.get())) {
                    serializable_child->serialize(serializer);
                }
            }
        }
    }

protected:
    // 派生类需实现的具体序列化逻辑
    virtual void serialize_impl(auto& serializer) const = 0;
};

用户派生类继承CRTP基类:

#include <yaml-cpp/yaml.h>

class MyIntNode : public SerializableNode<MyIntNode> {
public:
    int value;

protected:
    void serialize_impl(YAML::Node& serializer) const override {
        YAML::Node yaml_node;
        yaml_node["id"] = reinterpret_cast<uintptr_t>(this);
        yaml_node["value"] = value;
        serializer["nodes"].push_back(yaml_node);

        // 序列化父/子关系
        for(const auto& parent : parents) {
            YAML::Node edge;
            edge["parent_id"] = reinterpret_cast<uintptr_t>(parent.get());
            edge["child_id"] = reinterpret_cast<uintptr_t>(this);
            serializer["edges"].push_back(edge);
        }
    }
};

使用方式:

YAML::Node yaml_root;
yaml_root["nodes"] = YAML::Node(YAML::NodeType::Sequence);
yaml_root["edges"] = YAML::Node(YAML::NodeType::Sequence);

my_int_node->serialize(yaml_root);

优点:

  • 无虚函数开销(可将serialize_impl改为非虚方法进一步优化)
  • 编译时类型检查,安全性更高
  • 用户仅需实现一个具体序列化方法

缺点:

  • 不支持运行时切换序列化库,必须编译时确定

总结

你的原方案确实存在设计缺陷,核心问题是试图用空基类统一不同序列化库的返回类型,违反了面向对象设计原则。三个方案各有优劣:

  • 若需完全解耦、支持运行时切换序列化库,选策略模式
  • 若希望序列化逻辑完全外置、类型安全,选增强访问者模式
  • 若追求性能、编译时确定序列化库,选CRTP静态多态

另外还可以考虑适配器模式:为不同序列化库编写适配器,统一它们的API,DAG库仅与通用适配器接口交互,用户只需选择对应适配器即可。

内容的提问来源于stack exchange,提问作者joergbrech

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 14:12:34