Cериализация для начинающих - Часть 2 - Управление версиями

ОГЛАВЛЕНИЕ

Управление версиями

Когда вы обновляете ваш продукт в течение его жизненного цикла, вы можете счесть необходимым изменять структуру CFoo путем добавления или удаления элементов данных. Если вы просто выпустили новую версию CFoo, попытка чтения старой версии объекта из хранилища данных приведет к сбою. Обычно это недопустимо. Любая версия CFoo должна иметь возможность восстанавливать себя из более старой сериализованной версии. Другими словами, метод сериализации CFoo всегда должен быть обратно совместимым. Это легко достигается с помощью контроля версий объекта. Так же как добавляется сигнатура объекта, можно добавить целочисленную константу, которая определяет номер версии объекта.

   class CFoo

  {
    ...
    // Методы
    public:
      ...
      CString getSignature();
      int     getVersion();

    // Элементы данных (члены-данные)
      ..
    protected:
      static const CString  Signature;  // сигнатура объекта
      static const int      Version;    // версия объекта

  };

Версия объекта объявлена в Foo.cpp.

  // Статические константы
  const CString CFoo::Signature = "FooObject";
  const int     CFoo::Version = 1;

Затем изменяем метод serialize(), чтобы сериализовать версию после сериализации сигнатуры, и перед сериализацией элементов (членов) данных объекта. Если встречается более новая версия, пытаемся прочитать неподдерживаемую версию объекта. В этом случае просто возвращается статус UNSUPPORTED_VERSION (неподдерживаемая версия).

  int CFoo::serialize
    (CArchive* pArchive)

  {
    int nStatus = SUCCESS;
    bool bSignatureRead = false;
    bool bVersionRead = false;
 
    // Сериализация объекта ...
    ASSERT (pArchive != NULL);
    TRY

    {
      if (pArchive->IsStoring()) {
         // Запись сигнатуры и версии
         (*pArchive) << getSignature();
         (*pArchive) << getVersion();
 
         // Запись имени и идентификатора сотрудника
         (*pArchive) << m_strName;
         (*pArchive) << m_nId;

      }

      else {
         // Чтение сигнатуры – если она неверная, выводится сообщение об ошибке
         CString strSignature;
         (*pArchive) >> strSignature;
         bSignatureRead = true;

         if (strSignature.Compare (getSignature()) != 0) {
            return (INVALID_DATAFILE);
         }

         // Чтение версии – если она не поддерживается, выводится сообщение об ошибке
         int nVersion;
         (*pArchive) >> nVersion;
         bVersionRead = true;
         if (nVersion > getVersion()) {
            return (UNSUPPORTED_VERSION);

         }
 
         // Чтение имени и идентификатора сотрудника
         (*pArchive) >> m_strName;
         (*pArchive) >> m_nId;

      }
    }

    CATCH_ALL (pException)

    {
      nStatus = bSignatureRead && bVersionRead ? ERROR : INVALID_DATAFILE;
    }

    END_CATCH_ALL
    return (nStatus);

  }

Версия 1 объекта CFoo содержит 2 элемента данных -  CString (m_strName) и целое число (m_nId). Если добавить третий элемент (напрмер: int m_nDept) в версию 2, то нужно определить, как m_nDept будет инициализироваться при чтении более старой версии объекта. В данном примере при инициализации m_nDept присваивается значение -1, в предположении, что код подразделения сотрудника неизвестен.

 class CFoo

  {
    ...
    // Элементы данных
    public:
      CString  m_strName;  // имя сотрудника
      int      m_nId;      // идентификатор сотрудника
      int      m_nDept;    // код подразделения (-1 = неизвестный)

  };

Также необходимо увеличить номер версии объекта в файле Foo.cpp до 2.

  const int CFoo::Version = 2;

В итоге, изменяется часть метода serialize(), читающая объект, так что m_nDept при инициализации присваивается значение -1, если читается более старая версия файла данных. Заметьте, что файл всегда сохраняется как самая последняя версия.

  int CFoo::serialize

    (CArchive* pArchive)
  {
    ...
    // Сериализация объекта...
    ASSERT (pArchive != NULL);
    TRY

    {
      if (pArchive->IsStoring()) {
        ...
         // Запись имени, идентификатора и кода подразделения сотрудника
         (*pArchive) << m_strName;
         (*pArchive) << m_nId;
         (*pArchive) << m_nDept;
      }
      else {
        ...
         // Чтение имени и идентификатора сотрудника
         (*pArchive) >> m_strName;
         (*pArchive) >> m_nId;
         // Чтение кода подразделения (новое в версии 2)
         if (nVersion >= 2) {
            (*pArchive) >> m_nDept;
         }

         else {
            m_nDept = -1; // неизвестный
         }

      }
    }

    CATCH_ALL (pException)
    {
      nStatus = bSignatureRead && bVersionRead ? ERROR : INVALID_DATAFILE;
    }

    END_CATCH_ALL
    return (nStatus);

  }

Заключение

До сих пор речь шла о надежной поддержке сериализации простых объектов – то есть содержащих легко упорядочиваемые типы данных. В Части 3 рассматривается, как упорядочивать любые типы объектов.

Автор: Ravi Bhavnani