【问题标题】:Design pattern for multiple output formats多种输出格式的设计模式
【发布时间】:2023-03-19 07:25:01
【问题描述】:

我有一个类结构,它代表(内部)我希望输出到文件的数据。

一些成员变量是数据类私有的,因此它可以自我管理并阻止事情出错。

然后我希望将这些数据输出为多种文件格式。我可以做类似的事情

savefile_formatA(DataClass* pDataClass, ofstream& fout);
savefile_formatB(DataClass* pDataClass, ofstream& fout);

除了函数需要随后查看DataClass 的私有成员变量。我当然可以只创建 savefile_formatXYZ() 朋友函数,但是我需要为每种不同的格式添加一个朋友声明。

有解决这类问题的标准设计模式吗?你会如何解决这个问题?

谢谢!

【问题讨论】:

  • 你的意思是什么格式?您是在考虑像 .txt .xls .doc 这样的通用格式还是每个类特定的私有格式?

标签: c++ oop design-patterns encapsulation file-format


【解决方案1】:

根据您的数据类的复杂性,您可能希望使用访问者模式。如果您有某种嵌套数据结构,那么 Visitor 可能就是您所需要的。

如果格式化是相对简单的事情,例如您要对逗号分隔列表等内容进行变体,那么您可以采用这样的方法。

您的格式化程序对象都实现了一个接口,例如(伪代码)

 IFormatter ( start(); addInt(name, value), addString(name, value) .... end() );

那么数据类有一个方法

  public void formatMyself( IFormatter formatter ) {

        formatter.start()
        formatter.addString("aField", myA);
        formatter.addInteger("bfield", myB);
        formatter.end();          
  }

这使得被格式化的类负责选择要格式化的数据,格式化程序负责格式的细节。

【讨论】:

    【解决方案2】:

    如果您需要从类的外部实现文件格式化和保存/加载,那么您只能使用公开可用的数据来完成。如果保存/加载需要处理非公共数据,如果重新加载类不能从公共数据中重建原始的非公共数据,那么要么是类本身,要么是该类的朋友。真的没有办法解决这个问题。

    您最多可以做的是使用朋友模板使编写新类型变得更容易。例如:

    class DataType
    {
    ...
    private:
        template<typename format> friend void SaveFile<format>(const DataType *, ofstream&);
    };
    

    format 模板类型将是空类型。所以如果你有 formatA 和 formatB,你会有空结构:

    struct FormatA {};
    struct FormatB {};
    

    然后,您需要做的就是为这些格式编写专门的 SaveFile 版本:

    template<> void SaveFile<FormatA>(const DataType *, ofstream&);
    template<> void SaveFile<FormatB>(const DataType *, ofstream&);
    

    他们将自动成为 DataType 的朋友。

    【讨论】:

    • 结合上面的访问者这是完美的解决方案。
    【解决方案3】:

    通常的解决方案是决定需要导出哪些数据, 并为其提供某种访问权限(可能是 getter 函数)。 从逻辑上讲,您不希望班级本身必须知道任何细节 关于格式,你不希望格式化程序知道任何事情 更多的是关于这个类而不是它必须格式化的数据。

    【讨论】:

    • 确实,我考虑过这一点,但数据可能很大,因此任何 getter 都需要通过引用/指针传递。但这也不会破坏封装吗?
    • @Dan,不。公共接口由您的班级很好地控制,而朋友不是。考虑您在未来的数据类中添加更多私有数据,但您不希望将其暴露给格式化程序。如果你使用朋友类,你没有办法阻止它。
    • 原文已删除:我在回答另一个问题。如果您将数据输出到外部支持,那么它无论如何都不是真正的私有。 getter 将返回 const 引用或值,因此它们不允许修改(与 friend 不同)。由于 getter 用于格式化,因此性能无论如何都不是问题。您可能希望使用所有数据定义一个人工结构,并且只需一个 getter 按值返回它。
    【解决方案4】:

    我认为您在这里遇到的问题是设计问题之一。无论如何,序列化到文件不应该修改该数据,那么为什么这些函数应该是私有的呢?如果您所做的只是检查数据并将其写出,那么您应该有一个足够的公共接口。

    【讨论】:

    • 什么都不应该修改数据,但封装的全部意义在于阻止人们不小心做他们不应该做的事情。
    • @Dan,但是如果您向您的出口商提供“朋友”访问权限,他们就不能做他们不应该做的事情吗?如果您需要传递引用,请传递 const 引用。
    【解决方案5】:

    对于您的(设计)问题,我能想到的最好的方法是双重解决方案:

    • 为每个类创建一个将其序列化为 XML 的函数
    • 制作从 xml 保存为您想要的任何格式的通用函数:

      savefile_formatA(XMLNode* pRootNode, ofstream& fout);
      

    这样,您只需为每个类创建一个序列化函数,您还可以以任意数量的格式进行序列化。

    【讨论】:

    • +1。好主意,对我当前的项目来说有点矫枉过正,但我​​会记住的。
    【解决方案6】:

    您可以将其设为 DataClass 上的方法并传入流中。

    【讨论】:

      【解决方案7】:

      不要总是求助于朋友函数,因为它很容易破坏你的类的封装。一旦成为朋友,无论您是否希望它看到,它都可以访问您的所有私人成员。

      在您的情况下,您可以简单地提供一些公共接口来将必要的数据返回给客户端,然后生成不同的格式。另外,有兴趣的可以看看著名的MVC pattern

      【讨论】:

      • 这是一个糟糕的答案。 Friend 只是从 one 类的角度打破了封装——所有其他客户端都保持封装状态。但是,当您使用公共接口返回数据时,everyone 的封装就会被破坏。
      • @DeadMG,事实并非如此。定义好的接口只影响当前类的封装,不影响客户端。由客户决定是否使用该接口。这并不一定意味着客户也应该改变他们的界面。
      • @DeadMG,公共接口的另一个好处是你可以很好地分别定义setter和getter,无论如何都不能被朋友控制。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-07
      • 1970-01-01
      • 2012-04-07
      • 2016-02-28
      • 2010-12-10
      • 1970-01-01
      • 2015-05-08
      相关资源
      最近更新 更多