多种输出格式的设计模式

8

我有一个类结构体来表示(内部)我想要输出到文件的数据。

其中一些成员变量是私有的,属于数据类自身,以便它可以管理自己并防止出现问题。

然后,我希望将这些数据输出为多种文件格式。我可以像下面这样做:

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

除了需要查看 DataClass 的私有成员变量之外,函数需要的功能与之前相同。我当然可以将 savefile_formatXYZ() 设置为友元函数,但这样我就需要为每种不同的格式添加一个友元声明。
是否有一种标准的设计模式来解决这种问题?你会如何解决这个问题?
谢谢!

你指的是哪些格式?是像 .txt、.xls、.doc 这样的通用格式,还是每个类别都有特定的私有格式? - Ioan Paul Pirau
7个回答

10

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

如果格式相对简单,例如您正在生成逗号分隔列表的变体之类的内容,则可以采用以下方法。

您的格式化程序对象都实现了一个类似于(伪代码)的接口。

 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&);
};
< p > format 模板类型将是空类型。因此,如果您有 formatA 和 formatB,则会有空结构:

struct FormatA {};
struct FormatB {};

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

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

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

结合上面的访问者,这是完美的解决方案。 - Angel O'Sphere

0
通常的解决方案是决定需要导出哪些数据,并提供某种访问方式(可能是getter函数)。 从逻辑上讲,您不希望类本身必须了解任何有关格式的细节, 并且您也不希望格式化程序比其要格式化的数据更多地了解该类的任何信息。

的确,我考虑过这个问题,但如果数据量很大,任何getter函数都需要通过引用/指针传递。但这样做不会破坏封装性吗? - Dan
@Dan,不行。公共接口由您的类很好地控制,而友元则不是。考虑到您将来可能会在数据类中添加更多私有数据,但您不希望将其暴露给格式化程序。如果您使用友元类,则无法阻止它。 - Eric Z
1
如果您将数据输出到外部支持,那么它就不是真正的私有了。获取器将返回const引用或值,因此它们不允许修改(不像“friend”)。由于获取器是用于格式化的,因此性能不会成为问题;您可能需要定义一个人工结构来存储所有数据,并只有一个getter通过值返回它。 - James Kanze

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

1
什么都不应该修改数据,但封装的整个目的是防止人们意外地做一些不应该做的事情。 - Dan
@Dan,但是如果你给你的出口商提供了“friend”访问权限,他们不能做一些不应该做的事情吗?如果你需要传递引用,请传递const引用。 - Moo-Juice

0

不要总是借助友元函数,因为它很容易破坏你类的封装性。一旦成为友元,它就可以访问你所有的私有成员,无论你是希望它看到还是不希望。

在你的情况下,你可以简单地提供一些公共接口来返回必要的数据给客户端,这将产生不同的格式。此外,如果你感兴趣的话,你可以看看著名的MVC模式MVC pattern


这是一个糟糕的答案。从一个类的角度来看,Friend只会破坏封装性-所有其他客户端仍然被封装。但是,当您使用公共接口返回数据时,封装性就会对所有人都失效。 - Puppy
@DeadMG,这不是这样的。明确定义的接口只影响当前类的封装而不影响客户端。客户端可以决定是否使用该接口。这并不意味着客户端也应该更改其接口。 - Eric Z
@DeadMG,公共接口的另一个优点是你可以很好地分别定义设置器和获取器,无论朋友是否能控制。 - Eric Z

0
你可以将其作为 DataClass 的一个方法,并传入流。

0
我能想到的最好的解决方案是双重的,针对你的(设计)问题:
  • 为每个类编写一个将其序列化为 XML 的函数
  • 编写通用函数,可以将 XML 保存为任何你想要的格式:

    savefile_formatA(XMLNode* pRootNode, ofstream& fout);
    

这样,您只需要为每个类编写一个序列化函数,就可以在任意数量的格式中进行序列化。


不错的想法,对于我目前的项目有点过头了,但我会记在心里。 - Dan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接