c++ - 编写一个经常更改的类/结构

标签 c++ c design-patterns serialization oop

总结:
我有一个读取/写入文件的结构。
此结构经常更改,这导致我的 read() 函数变得复杂。

我需要找到一种处理变更的好方法,同时保持低错误数。 最理想的情况是,代码应该让人们很容易发现版本之间的变化。

我已经考虑了几种模式,但我不知道我是否已经考虑了所有可能的选项。

如您所见,代码大部分是 C 类的,但我正在将其转换为 C++


详情
正如我所说,我的结构经常变化(几乎在程序的每个版本中)。

  • 一些成员被删除,一些成员被添加,一些变得更复杂。新成员出现结构不是简单的情况。

到目前为止,对结构的更改已按如下方式处理:

  • version_1中,我使用了颜色映射表:

struct Obj {
    int color_index;  
};  

void Read_Obj( File *f, Obj *o ) {
    f->read( f, &o->color_index );
}

void Write_Obj( File *f, Obj *o ) {
    f->write( f, o->color_index );
}
  • 下一个版本中,我把它改成了[r,g,b]形式

struct Obj {
    int color_r;
    int color_g;
    int color_b;  
};  

void Read_Obj( File *f, Obj *o ) {

    if( f->version() == File::Version1 ) {
        int color_index;
        f->read( f, &color_index );
        ColorIndex_to_RGB( o, color_index ); // we used color maps back then
    }
    else {
        f->read( f, &o->color_r );
        f->read( f, &o->color_g );
        f->read( f, &o->color_b );
    }
}      

void Write_Obj( File *f, Obj *o ) {
    f->write( f, o->color_r );
    f->write( f, o->color_g );
    f->write( f, o->color_b );
}

[简要说明]

注意这里我知道可以用


void Read_Obj( File *f, Obj *o ) {

    if( f->version() == File::Version1 ) {
        Read_Obj_V1( f, o );
    }
    else {
        Read_Obj_V2( f, o );
    }
}      

但这往往会导致每个子函数之间的代码重复,因为在现实生活中,每个版本的 20 个结构成员中只有 1-2 个会发生变化。所以,其他 18 行保持不变。

当然,如果有充分的理由,我可以更改此政策

[简短说明结束]


现在这些结构变得复杂了,我需要将它们转换为类,并以更加面向对象的方式工作。

我见过一种模式,您使用一个类来读取每个旧版本,然后将数据转换为更新的类。


class Obj_v1 {
    int m_color_index;
    read( File *f ) {
        f->read( f, &m_color_index );
   }

   void convert_to( Obj * ) { /* code to convert the older object */  }
};

class Obj {
    int m_r;
    int m_g;
    int m_b;
    read( File *f ) {
        f->read( f, &m_r );
        f->read( f, &m_g );
        f->read( f, &m_b );
   }

};

void Read_Obj( File *f, Obj *o ) {

    if( f.version() == File::Version1 ) {
        Obj_v1 old();
        old.read( f );
        old.convert_to( o );
    }
    else {
        o.read( f );
    }
}      

void Write_Obj( File *f, Obj *o ) {
    o->write( f );
}

但是,有两种应对变化的策略:

策略 1:直接转化

void Read_Obj( File *f, Obj *o ) {

    if( f->version() == File::Version1 ) {
        Obj_v1 old();
        old.read( f );
        old.convert_to( o );
    }
    else if( f->version() == File::Version2 ) {
        Obj_v2 old();
        old.read( f );
        old.convert_to( o );
    }
    else {
        o.read( f );
    }
}      

缺点:

  • 这意味着您必须更新所有 Obj_vX 类的convert_to()每次您更改Obj 类。每次抛出错误的可能性太多。

好处:

  • 您始终能够使旧概念(结构)适应新概念 - 与级联策略(下一个)相比,其中一些信息可能会在此过程中丢失,因此无法使用。

策略 2:级联转化

void Read_Obj( File *f, Obj *o ) {

    Obj_v1 o1();
    Obj_v2 o2();

    if( f->version() == File::Version1 ) {
        o1.read( f );
        o1.convert_to( o2 );
        o2.convert_to( o );
    }
    else if( f->version() == File::Version2 ) {
        o2.read( f );
        o2.convert_to( o );
    }
    else {
        o.read( f );
    }
}      

缺点:

  • 一些信息可能存在于v1中,在v3中没有用,但v5可以利用它;然而,级联转换已经消除了这些数据。

  • 旧版本往往需要更长的时间来创建对象。

好处:

  • 每次更改Obj 类时,只需编写一个convert_to()。然而,线路中的一个转换器中的一个错误可能会产生更严重的影响,并可能破坏数据库的一致性。但是,您发现此类错误的机会增加了。

担心:

  • 会不会是旧版本对象中的转换后转换太多噪音,它们是错误的?

问题:

  • 有没有其他模式可以在这方面做得更好?

  • 那些对我的建议有一些经验的人,您如何看待我对上述实现的担忧?

  • 哪些是更好的解决方案?

非常感谢

最佳答案

void Read_Obj( File *f, Obj *o ) {
if( f->version() == File::Version1 ) {

if可以说是一个隐藏的开关/外壳。 C++ 中的 switch/case 通常可以与 polymorphism 互换。 .示例:

struct Reader {
   virtual void Read_Obj( File *f, Obj *o ) = 0;
   /* methods to read further objects */
}

struct ReaderV1 : public Reader {
   void Read_Obj( File *f, Obj *o ) { /* ... */ };
   /* methods to read further objects */
}

struct ReaderV2 : public Reader {
   void Read_Obj( File *f, Obj *o ) { /* ... */ };
   /* methods to read further objects */
}

然后在打开文件并检测版本号后实例化适当的 Reader 后代。这样一来,您将只在顶层代码中检查一个文件版本,而不是用检查污染所有低级代码。

如果代码在文件版本之间是通用的,为方便起见,您也可以将其放入阅读器基类中。

我强烈建议不要使用 class Obj_v1 的变体和 class Obj read()在哪里方法属于 Obj本身。这样一来,很容易导致循环依赖,而且让对象知道其持久呈现也不是一个好主意。 IME(以我的经验)最好让第 3 方阅读器类层次结构对此负责。 (如在 std::iostreamstd::stringoperator << 中一样:流不知道字符串,字符串不知道流,只有运算符 << 两者都知道。)

否则,我个人认为您的“策略 1”和“策略 2”之间没有什么大的区别。他们都使用 convert_to()我个人认为是肤浅的。应改用具有多态性的 IME 解决方案 - 自动将所有内容转换为对象的最新版本 class Obj , 没有中间体 class Obj_v1class Obj_v2 .由于使用多态性,每个版本都有一个专用的读取函数,因此很容易确保从读取的信息中重新创建正确的对象。

Are there any other patterns that do a better job at this? The ones of you that had some experience with my proposals, what do you think of my worries on the above implementations? Which are preferable solutions?

这正是多态性旨在解决的问题,也是我通常自己完成此类任务的方式。

这与 object serialization 有关,但我还没有看到能够支持同一类的多个版本的单个序列化框架(我的信息可能已过时)。

我个人确实多次使用以下序列化/反序列化类层次结构:

  • 抽象的阅读器界面(根据定义非常简洁)
  • 实用程序类实现从/向流读取和写入实际对象(丰富的、高度可重用的代码,也用于网络传输)
  • 阅读器接口(interface)的版本化实现(相对精简,重用胖工具类)
  • 编写器接口(interface)/类(我总是编写文件的最新版本。版本控制仅在读取期间使用。)

希望对您有所帮助。

关于c++ - 编写一个经常更改的类/结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3629177/

相关文章:

C99:记录线程安全的受限指针?

c - 将 X 索引的内存复制到 C 中单个数组中的多个位置的最佳方法是什么?

java - 如何在 Java 中处理多个退出点

.net - 微服务互通用什么.NET

c++ - 信号的总能量

c++ - QT ReplaceChild 方法

c++ - 变量名中的 __COUNTER__

c - 全局变量的替代方案 - GLUT C

oop - 一棵树,其中每个节点可以有多个父节点

c++ - 读取文件后如何构建不同对象的 vector