Резюме:
У меня есть структура, которая читается / записывается в файл.
Эта структура часто изменяется, и это приводит к тому, что моя read()
функция становится сложной.
Мне нужно найти хороший способ справиться с изменениями, сохраняя при этом низкое количество ошибок.
Оптимально, код должен облегчать обнаружение изменений между версиями.
Я продумал пару шаблонов, но не знаю, прошел ли я все возможные варианты.
Как вы увидите, код был в основном похож на C
, но я нахожусь в процессе превращения его в C++
.
Детали
Как я уже сказал, моя структура часто меняется (почти в каждой версии программы).
- Некоторые участники удаляются, некоторые добавляются, некоторые становятся более сложными. Это не простой случай, когда в структуре появляется новый член.
До настоящего времени изменения в структуре обрабатывались следующим образом:
- in 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 );
}
}
но это имеет тенденцию к дублированию кода между каждой из подфункций, поскольку в реальной жизни только 1-2 из ~ 20 членов структуры изменяются в каждой версии. Итак, остальные 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 );
}
}
Неудобство:
- Это означает, что вам необходимо обновлять
convert_to()
из всех Obj_vX
классов каждый раз, когда вы меняете класс 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 может использовать ее; однако каскадные преобразования уничтожили эти данные.
В старых версиях для создания объектов требуется больше времени.
Преимущество:
- Вам нужно писать только один
convert_to()
каждый раз, когда вы меняете класс Obj
. Однако одна ошибка в одном из преобразователей в линии может иметь более серьезные последствия и может нарушить целостность базы данных. У вас есть больше шансов найти такую ошибку.
Обеспокоенность:
- Может ли быть так, что при преобразовании после преобразования вы получаете слишком много шума в объектах более старых версий, что они ошибаются?
Вопрос:
Есть ли другие модели, которые лучше справляются с этим?
Те из вас, кто имел некоторый опыт работы с моими предложениями, что вы думаете о моих опасениях по поводу вышеуказанных реализаций?
Какие решения предпочтительнее?
Большое спасибо