<?xml version='1.0' encoding="utf-8"?>
      <rss version='2.0'>
      <channel>
      <title>Форум на Исходниках.RU</title>
      <link>https://forum.sources.ru</link>
      <description>Форум на Исходниках.RU</description>
      <generator>Форум на Исходниках.RU</generator>
  	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=367544&amp;view=findpost&amp;p=3235472</guid>
        <pubDate>Fri, 09 Nov 2012 23:02:17 +0000</pubDate>
        <title>Сериализация объектов</title>
        <link>https://forum.sources.ru/index.php?showtopic=367544&amp;view=findpost&amp;p=3235472</link>
        <description><![CDATA[--Ins--: Ну что ж, решил поделиться с вами своими наработками в области сериализации в Delphi. Данная тема всегда вызывала у меня интерес и со временем мой взгляд на то, как это должно быть, эволюционировал и в итоге пришел к тому, что будет продемонстировано ниже. Хочу заметить, что методика, используемая мной, чем-то напоминает .NET-овскую сериализацию, правда менее наворочена, однако была разработана независимо и любые совпадения - случайность :) Так же замечу, что приведенная реализация в версиях Delphi 2010 и старше может быть с легкостью модернизирована до такой степени, что код сериализуемых объектов в принципе будет переносим на платформу .NET - для этого предлагается научить сериализатор работать с атрибутами классов и их членов (SerializableAttribute, NonSerializedAttribute и т.д.).<br>
<br>
<strong class='tag-b'>Определения</strong><br>
Что же такое сериализация? Сериализация - процесс перевода какой-либо структуры данных (например объекта) в последовательность байтов. Обратный процесс восстановления структуры данных из последовательности байт называется десериализация. Сериализация используется в тех случаях, когда нужно сохранить объект в файл, память, базу данных, или передать по сети, с возможностью в дальнейшем восстановить объект в исходном состоянии.<br>
<br>
<strong class='tag-b'>Особенности</strong><br>
Итак, прежде чем приступить к рассмотрению, краткий обзор особенностей данной реализации:<ul class="tag-list"><li>Любой объект может быть сериализован. Для этого класс должен предоставить информацию о сериализуемых параметрах, а саму сериализацию на основе предоставленной информации выполняет специальный объект - сериализатор.</li><li>Предоставление информации для сериализатора возможно двумя путями. Первый - это объявить published-свойства. Такое объявление приведет к записи значения этого свойства при сериализации. Свойство может иметь объектных тип, в этом случае для записи этого свойства-объекта так же будет применен сериализатор, что позволяет использовать сериализатор для записи не только одиночных объектов, но и графов объектов. Второй способ применяется в случае, когда записываемые параметры нельзя или нецелесообразно предоставлять в виде published-свойств. В этом случае объект может реализовать специальный интерфейс настраиваемой сериализации, который позволяет записывать и читать произвольные дополнительные данные.</li><li>Отсутствует привязка к конкретному формату хранения данных. Объект только лишь объявляет, какие параметры необходимо сериализовать, но не уточняет в каком виде. Вопрос о конечном формате данных зависит от типа используемого сериализатора. Если был использован сериализатор XML, то данные будут записаны в XML-виде, если бинарный сериализатор - то в бинарном виде. Один и тот же объект можно записать в файлы разных форматов просто применив к нему разные типы сериализаторов</li><li>Существует механизм уведомлений объектов о процессе сериализации/десериализации. Данный механизм может быть использован для осуществления каких-либо действий над объектом до или после сериализации/десериализации</li><li>Реализован механизм управления ревизиями (версиями). Если структура данных объекта претерпела изменения в процессе развития программы, система позволит объектам читать файлы старых форматов, и наоборот - записывать данные в старых форматах в целях совместимости.</li></ul><br>
Итак, перейдем к рассмотрению реализации.<br>
<strong class='tag-b'>Дерево состояния объекта</strong><br>
Сериализация в предоставляемой библиотеке реализована в два этапа, с использованием промежуточного дерева состояния между объектом и потоком данных. Т.е. при записи параметры объекта сначала записываются в промежуточное дерево и лишь потом - в файл или поток, а при чтении - сначала из файла или потока формируется дерево состояния, а лишь потом по нему восстанавливается объект. Такое разделение процессов сериализации/десериализации позволяет:<ul class="tag-list"><li>Отвязаться от конкретного формата данных используя как промежуточную стадию одинаковое для всех форматов дерево</li><li>Реализовать в базовом классе сериализаторов операции по формированию дерева из состояния объекта и наоборот - восстановлению объекта из дерева, а операции записи дерева в поток и чтения дерева из потока переложить на потомков</li><li>Использовать запись в дерево и чтение из дерева вообще не используя никакие конкретные форматы данных - удобно если не нужно длительно хранить состояние объекта, а скажем скопировать или клонировать объект путем записи одним объектом своего состояния в дерево, а вторым - чтения из этого дерева. В данных операциях трата времени и ресурсов на формирование данных опеределенного типа (XML и т.д.) совершенно излишне.</li></ul><br>
Дерево состояния объекта представлено интерфейсом ITreeNode и классом TTreeNode, этот интерфейс реализующий. Вот объявление ITreeNode:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&nbsp;&nbsp;// Базовый интерфейс узла дерева</div><div class="code_line">&nbsp;&nbsp;ITreeNode = interface(IUnknown)</div><div class="code_line">&nbsp;&nbsp;[&#39;{9CD610C0-9F03-405B-B655-86B49E582DD3}&#39;]</div><div class="code_line">&nbsp;&nbsp; &nbsp;// Имя узла</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetName: WideString; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure SetName(const Value: WideString); safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property Name: WideString read GetName write SetName;</div><div class="code_line">&nbsp;&nbsp; &nbsp;// Родитель</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetParent: ITreeNode; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property Parent: ITreeNode read GetParent;</div><div class="code_line">&nbsp;&nbsp; &nbsp;// Список дочерних подузлов</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetNodesCount: Integer; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetNode(Index: Integer): ITreeNode; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetNodeByName(AName: WideString): ITreeNode; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetNodeIndex(AName: WideString): Integer; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property NodesCount: Integer read GetNodesCount;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property Nodes[Index: Integer]: ITreeNode read GetNode;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property NodeByName[AName: WideString]: ITreeNode read GetNodeByName;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property NodeIndex[AName: WideString]: Integer read GetNodeIndex;</div><div class="code_line">&nbsp;&nbsp; &nbsp;// Список параметров узла</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetParamsCount: Integer; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetParamName(Index: Integer): WideString; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetParamValue(Index: Integer): OleVariant; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure SetParamValue(Index: Integer; const Value: OleVariant); safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetParamValueByName(AName: WideString): OleVariant; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure SetParamValueByName(AName: WideString; const Value: OleVariant); safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetParamIndex(AName: WideString): Integer; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property ParamsCount: Integer read GetParamsCount;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property ParamName[Index: Integer]: WideString read GetParamName;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property ParamValue[Index: Integer]: OleVariant read GetParamValue write</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;SetParamValue;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property ParamValueByName[AName: WideString]: OleVariant read GetParamValueByName</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;write SetParamValueByName;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property ParamIndex[AName: WideString]: Integer read GetParamIndex;</div><div class="code_line">&nbsp;&nbsp; &nbsp;// Управление узлами</div><div class="code_line">&nbsp;&nbsp; &nbsp;function AddNode(AName: WideString): ITreeNode; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;function InsertNode(Index: Integer; AName: WideString): ITreeNode; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure DeleteNode(Index: Integer); overload; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure DeleteNode(AName: WideString); overload; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure DeleteNode(Node: ITreeNode); overload; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure ClearNodes; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;function NodeExists(AName: WideString): Boolean; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure Assign(const Node: ITreeNode); safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure Merge(const Node: ITreeNode); safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;// Управление параметрами</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure AddParam(AName: WideString; const AValue: OleVariant); safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure InsertParam(Index: Integer; AName: WideString; const</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;AValue: OleVariant); safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure DeleteParam(Index: Integer); overload; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure DeleteParam(AName: WideString); overload; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure ClearParams; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;function ParamExists(AName: WideString): Boolean; safecall;</div><div class="code_line">&nbsp;&nbsp; &nbsp;// Полная очистка</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure Clear; safecall;</div><div class="code_line">&nbsp;&nbsp;end;</div></ol></div></div></div></div><script>preloadCodeButtons('1');</script><br>
Объявление выглядит громоздким, но ничего сложного в этом типе нет. Любой узел дерева может содержать дочерние узлы (Nodes) и параметры (Params). Узлы и параметры имеют строковое имя, параметры имеют кроме имени вариантное значение. Методы интерфейса - это методы управления узлами и параметрами. Типы WideString и OleVariant использованы для совместимости интерфейса с COM/OLE.<br>
При сериализации, данные объекта содержатся в узлах и параметрах дерева.<br>
<br>
<strong class='tag-b'>Класс сериализатора</strong><br>
Как уже было сказано выше, чтобы записать или прочитать состояние объекта из файла или потока, к нему нужно применить методы сериализатора. Сериализатор - это класс, который содержит методы записи/чтения состояния объекта. Базовым для всех типов сериализаторов является класс  TSerializer. Вот его объявление:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&nbsp;&nbsp;// Базовый класс сериализатора объектов</div><div class="code_line">&nbsp;&nbsp;TSerializer = class(TObject)</div><div class="code_line">&nbsp;&nbsp;public</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure SaveToNode(AObject: TObject; const Node: ITreeNode;</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;PreferStr: Boolean = True; Revision: Integer = -1);</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure LoadFromNode(AObject: TObject; const Node: ITreeNode);</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure SaveToStream(const Node: ITreeNode; Stream:</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;TStream); overload; virtual; abstract;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure LoadFromStream(const Node: ITreeNode;</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;Stream: TStream); overload; virtual; abstract;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure SaveToStream(AObject: TObject; Stream: TStream;</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;PreferStr: Boolean = True; Revision: Integer = -1); overload;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure LoadFromStream(AObject: TObject; Stream: TStream); overload;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure SaveToFile(const Node: ITreeNode; FileName: String); overload;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure LoadFromFile(const Node: ITreeNode;</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;FileName: String); overload;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure SaveToFile(AObject: TObject; FileName: String;</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;PreferStr: Boolean = True; Revision: Integer = -1); overload;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure LoadFromFile(AObject: TObject; FileName: String); overload;</div><div class="code_line">&nbsp;&nbsp;end;</div></ol></div></div></div></div><br>
Методы класса можно разделить на группы.<ul class="tag-list"><li>Методы записи. Начинаются с SaveTo</li><li>Методы чтения. Начинаются с LoadFrom</li></ul>По типу хранилища:<ul class="tag-list"><li>Работающие с файлом. Заканчиваются на File. Вызывают методы работы со Stream, передавая параметром TFileStream</li><li>Работающие с потоком. Заканчиваются на Stream.</li><li>Работающие с деревом состояния. Заканчиваются на Node.</li></ul>По типу аргумента:<ul class="tag-list"><li>Работающие с объектом. Первый параметр типа TObject. При реализации формируют дерево и вызывают методы, работающие с ITreeNode</li><li>Работающие с деревом состояния. Первый параметр типа ITreeNode</li></ul>Только два метода этого класса абстрактные - это методы, формирующие данные в потоке (TStream) по дереву и наоборот - дерево по данным из потока, т.е. методы отвечающие за формат хранения данных. Все остальные методы реализованы в классе TSerializer<br>
Потомки класса перекрывают эти методы, обеспечивая хранение данных в соответствии с заданным форматом. Реализованы два потомка - для бинарного формата и для XML.<br>
<br>
<strong class='tag-b'>Сериализация объекта</strong><br>
Для примера сериализации рассмотрим класс, содержащий настройки программы.<br>
<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">// Класс, содержащий настройки приложения</div><div class="code_line">TApplicationSettings = class(TPersistent)</div><div class="code_line">...</div><div class="code_line">published</div><div class="code_line">&nbsp;&nbsp;property ShowSplash: Boolean read FShowSplash write FShowSplash;</div><div class="code_line">&nbsp;&nbsp;property SplashTimeout: Integer read FSplashTimeout write FSplashTimeout;</div><div class="code_line">&nbsp;&nbsp;property UserProfile: String read FUserProfile write FUserProfile;</div><div class="code_line">end;</div></ol></div></div></div></div><br>
<br>
Класс объявляет три опубликованных свойства, что автоматически означает, что эти свойства будут записываться и читаться при сериализации/десериализации. Код записи этого класса в файл с помощью сериализатора будет выглядеть следующим образом:<br>
<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">var</div><div class="code_line">&nbsp;&nbsp;Settings: TApplicationSettings;</div><div class="code_line">begin</div><div class="code_line">&nbsp;&nbsp;Settings := TApplicationSettings.Create;</div><div class="code_line">&nbsp;&nbsp;try</div><div class="code_line">&nbsp;&nbsp; &nbsp;Settings.ShowSplash := True;</div><div class="code_line">&nbsp;&nbsp; &nbsp;Settings.SplashTimeout := 2000;</div><div class="code_line">&nbsp;&nbsp; &nbsp;Settings.UserProfile := &#39;user.dat&#39;;</div><div class="code_line">&nbsp;&nbsp; &nbsp;// Записываем состояние объекта в файл &#39;Settings.xml&#39;</div><div class="code_line">&nbsp;&nbsp; &nbsp;TXmlSerializer.SaveToFile(Settings, &#39;Settings.xml&#39;);</div><div class="code_line">&nbsp;&nbsp;finally</div><div class="code_line">&nbsp;&nbsp; &nbsp;Settings.Free;</div><div class="code_line">&nbsp;&nbsp;end;</div><div class="code_line">end;</div></ol></div></div></div></div><br>
<br>
Записанный файл будет выглядеть так:<br>
<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&#60;?xml version=&quot;1.0&quot; encoding=&quot;windows-1251&quot;?&#62;</div><div class="code_line">&#60;TApplicationSettings&#62;</div><div class="code_line">&nbsp;&nbsp;&#60;ShowSplash&#62;True&#60;/ShowSplash&#62;</div><div class="code_line">&nbsp;&nbsp;&#60;SplashTimeout&#62;2000&#60;/SplashTimeout&#62;</div><div class="code_line">&nbsp;&nbsp;&#60;UserProfile&#62;user.dat&#60;/UserProfile&#62;</div><div class="code_line">&#60;/TApplicationSettings&#62;</div></ol></div></div></div></div><br>
<br>
Сериализатор позволяет сохранять не только published-свойства простых типов, но и свойства объектных типов, в этом случае объектное свойство будет сериализовано во вложенный узел. Например, если свойства ShowSplash и SplashTimeout перенести в объектное свойство Splash: TSplashSettings, у которого Show и Timeout будут published-cвойствами, то результирующий XML будет таким:<br>
<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&#60;?xml version=&quot;1.0&quot; encoding=&quot;windows-1251&quot;?&#62;</div><div class="code_line">&#60;TApplicationSettings&#62;</div><div class="code_line">&nbsp;&nbsp;&#60;Splash&#62;</div><div class="code_line">&nbsp;&nbsp; &nbsp;&#60;Show&#62;True&#60;/Show&#62;</div><div class="code_line">&nbsp;&nbsp; &nbsp;&#60;Timeout&#62;2000&#60;/Timeout&#62;</div><div class="code_line">&nbsp;&nbsp;&#60;/Splash&#62;</div><div class="code_line">&nbsp;&nbsp;&#60;UserProfile&#62;user.dat&#60;/UserProfile&#62;</div><div class="code_line">&#60;/TApplicationSettings&#62;</div></ol></div></div></div></div><br>
<br>
Директива stored так же позволяет управлять сериализуемостью свойства.<br>
<br>
<strong class='tag-b'>Настраиваемая сериализация</strong><br>
Как уже было сказано выше, не всегда возможно или целесообразно предоставлять сериализуемые данные в виде published-свойств. В этом случае класс может реализовать интерфейс ICustomSerialize. Вот его объявление:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&nbsp;&nbsp;// Интерфейс настраиваемой сериализации</div><div class="code_line">&nbsp;&nbsp;ICustomSerialize = interface(IInterface)</div><div class="code_line">&nbsp;&nbsp;[&#39;{BB7DC15E-3D67-4183-B758-F3CDF493A513}&#39;]</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure CustomSerialize(const Node: ITreeNode; Revision: Integer);</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure CustomDeSerialize(const Node: ITreeNode);</div><div class="code_line">&nbsp;&nbsp;end;</div></ol></div></div></div></div><br>
<br>
Для примера уберем свойства ShowSplash и SplashTimeout из секции published, и сериализуем их, используя названные механизмы.<br>
<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">// Класс, содержащий настройки приложения</div><div class="code_line">TApplicationSettings = class(TInterfacedPersistent,</div><div class="code_line">&nbsp;&nbsp;ICustomSerialize)</div><div class="code_line">...</div><div class="code_line">protected</div><div class="code_line">&nbsp;&nbsp;// ICustomSerialize</div><div class="code_line">&nbsp;&nbsp;procedure CustomSerialize(const Node: ITreeNode; Revision: Integer);</div><div class="code_line">&nbsp;&nbsp;procedure CustomDeserialize(const Node: ITreeNode);</div><div class="code_line">public</div><div class="code_line">&nbsp;&nbsp;property ShowSplash: Boolean read FShowSplash write FShowSplash;</div><div class="code_line">&nbsp;&nbsp;property SplashTimeout: Integer read FSplashTimeout write FSplashTimeout;</div><div class="code_line">published</div><div class="code_line">&nbsp;&nbsp;property UserProfile: String read FUserProfile write FUserProfile;</div><div class="code_line">end;</div><div class="code_line">&nbsp;</div><div class="code_line">implementation</div><div class="code_line">&nbsp;&nbsp;</div><div class="code_line">procedure TApplicationSettings.CustomSerialize(const Node: ITreeNode;</div><div class="code_line">&nbsp;&nbsp;Revision: Integer);</div><div class="code_line">var</div><div class="code_line">&nbsp;&nbsp;SplashNode: ITreeNode;</div><div class="code_line">begin</div><div class="code_line">&nbsp;&nbsp;SplashNode := Node.AddNode(&#39;Splash&#39;);</div><div class="code_line">&nbsp;&nbsp;SplashNode.AddParam(&#39;Show&#39;, ShowSplash);</div><div class="code_line">&nbsp;&nbsp;SplashNode.AddParam(&#39;Timeout&#39;, SplashTimeout);</div><div class="code_line">end;</div><div class="code_line">&nbsp;</div><div class="code_line">procedure TApplicationSettings.CustomDeserialize(const Node: ITreeNode);</div><div class="code_line">var</div><div class="code_line">&nbsp;&nbsp;SplashNode: ITreeNode;</div><div class="code_line">begin</div><div class="code_line">&nbsp;&nbsp;SplashNode := Node.NodesByName[&#39;Splash&#39;];</div><div class="code_line">&nbsp;&nbsp;ShowSplash := SplashNode.ParamValueByName[&#39;Show&#39;];</div><div class="code_line">&nbsp;&nbsp;SplashTimeout := SplashNode.ParamValueByName[&#39;Timeout&#39;];</div><div class="code_line">end;</div></ol></div></div></div></div><br>
<br>
Результат будет такой же, как и в предыдущем примере. <br>
<br>
<strong class='tag-b'>Сериализация массивов, списков и коллекций</strong><br>
Интерфейс настраиваемой сериализации наиболее часто нужен для реализации коллекций или свойств-массивов. Можно даже специально реализовать класс, инкапсулирующий список или массив, реализующий ICustomSerialize, и потом просто объявлять его как published для записи коллекции как составной части объекта. Реализация такого класса не представляет особой сложности - можно рассматривать его в качестве домашнего задания :)<br>
<br>
<strong class='tag-b'>Уведомления о сериализации</strong><br>
Любой сериализуемый объект может реализовать интерфейс ISerializeNotify.<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&nbsp;&nbsp;// Интерфейс уведомления о сериалицации</div><div class="code_line">&nbsp;&nbsp;ISerializeNotify = interface(IInterface)</div><div class="code_line">&nbsp;&nbsp;[&#39;{9ED646CF-1C5E-48B2-A5C5-3191A8758BDB}&#39;]</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure BeginSave;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure EndSave;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure BeginLoad;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure EndLoad;</div><div class="code_line">&nbsp;&nbsp;end;</div></ol></div></div></div></div><br>
Реализация этого интерфейса приведет к тому, что его методы будут вызываться при сериализации/десериализации (до и после), что позволит объекту обработать эти события, если это необходимо.<br>
<br>
<strong class='tag-b'>Контроль версий объектов</strong><br>
Последнее что осталось рассмотреть - это контроль версий. Бывают случаи, когда программа используется долго и структура объектов меняется. Тем не менее, хочется чтобы новые программы успешно читали файлы старых форматов. Для решения этой задачи предлагается следующий механизм. Сразу же хочу заметить, что если изменение в структуре объекта - это просто появление нового свойства, то вероятнее всего прибегать к этому механизму не придется. Если в дереве состояния не найдется записи о новом свойстве, то ошибки времени выполнения не возникнет, а значение свойства останется прежним, каким было до десериализации. Но если же структура объекта меняется значительно, то избежать использования данных механизмов проблематично.<br>
Итак, чтобы реализовать в сериализуемом объекте поддержку контроля версий, необходимо выполнить следующие шаги:<ul class="tag-list"><li>Реализовать в объекте интерфейс IRevision.</li><li>Создать контроллер ревизии (потомок класса TRevisionController) для каждой версии объекта</li><li>Зарегистрировать контроллер в менеджере ревизий (TRevisionsManager)</li></ul>Интерфейс IRevision:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&nbsp;&nbsp;// Интерфейс управления ревизиями</div><div class="code_line">&nbsp;&nbsp;IRevision = interface(IInterface)</div><div class="code_line">&nbsp;&nbsp;[&#39;{8370E426-0A0E-4B69-B180-7A8E5067A59F}&#39;]</div><div class="code_line">&nbsp;&nbsp; &nbsp;function GetRevision: Integer;</div><div class="code_line">&nbsp;&nbsp; &nbsp;property Revision: Integer read GetRevision;</div><div class="code_line">&nbsp;&nbsp; &nbsp;procedure WriteRevision(const Node: ITreeNode; Revision: Integer);</div><div class="code_line">&nbsp;&nbsp; &nbsp;function ReadRevision(const Node: ITreeNode): Integer;</div><div class="code_line">&nbsp;&nbsp;end;</div></ol></div></div></div></div><br>
Довольно простой интерфейс, содержит всего три метода: GetRevision должен вернуть номер текущей ревизии объекта. Метод WriteRevision - записывает ревизию в дерево состояния (например можно записать в параметр &quot;Revision&quot;). Метод ReadRevision - читает номер ревизии объекта из дерева. В последнем методе можно предусмотреть случай, когда номер ревизии не записан в дерево, т.е. файл был сохранен когда разных ревизий еще не было, и возвращать нулевой номер ревизии.<br>
Номер ревизии можно указать в Save-методах сериализатора - это приведет к тому, что объект будет записан в соответствии с указанной ревизией.<br>
Второй этап, который необходимо реализовать - это создать контроллер ревизии. Класс контроллера объявлен так:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">&nbsp;&nbsp;// Базовый класс контроллеров ревизии</div><div class="code_line">&nbsp;&nbsp;TRevisionController = class(TObject)</div><div class="code_line">&nbsp;&nbsp;protected</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure Upgrade(const Node: ITreeNode; AObject:</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;TObject); virtual; abstract;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class procedure Downgrade(const Node: ITreeNode; AObject:</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;TObject); virtual; abstract;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class function GetNestedObjectRevision(AObject: TObject; Revision: Integer):</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;Integer; virtual;</div><div class="code_line">&nbsp;&nbsp;public</div><div class="code_line">&nbsp;&nbsp; &nbsp;class function Revision: Integer; virtual; abstract;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class function PrevRevision: Integer; virtual;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class function NextRevision: Integer; virtual; </div><div class="code_line">&nbsp;&nbsp; &nbsp;class function ObjectClass: TClass; virtual; abstract;</div><div class="code_line">&nbsp;&nbsp; &nbsp;class function Critical: Boolean; virtual; </div><div class="code_line">&nbsp;&nbsp; &nbsp;class function RevisionName: String; virtual;</div><div class="code_line">&nbsp;&nbsp;end;</div></ol></div></div></div></div><br>
Рассмотрим методы этого класса:<ul class="tag-list"><li>Revision - возвращает номер ревизии, для которой реализован контроллер. Должен быть перекрыт</li><li>PrevRevision - номер предыдущей ревизии. В базовой реализации возвращает Revision - 1</li><li>NextRevision - номер следующей ревизии. В базовой реализации возвращает Revision + 1</li><li>ObjectClass - возвращает класс объекта, для которого реализован контроль ревизий (класс объекта, который изменил свою структуру). Должен быть перекрыт</li><li>Critical - описательный метод, с его помощью можно указать что изменения критические, т.е. не поддерживаются предыдущие версии. В базовой реализации возвращает False</li><li>RevisionName - описательный метод, позволяет указать текстовое описание ревизии (можно использовать в окнах диалога сохранения)</li><li>Upgrade - один из двух важнейших методов. В своей реализации должен преобразовать дерево, записанное в формате предыдущей ревизии, в дерево, записанное в соответствии с форматом текущей ревизии. Т.е. добавить в дерево новые параметры или изменить структуру записи старых в соответствии с новым форматом. Используется менеджером ревизий при чтении файлов старых форматов. Должен быть перекрыт.</li><li>Downgrade - один из двух важнейших методов. В своей реализации должен преобразовать дерево, записанное в соответствии с форматом текущей ревизии, в дерево, записанное в формате предыдущей ревизии. Используется менеджером ревизий при записи файлов старых форматов. Должен быть перекрыт.</li><li>GetNestedObjectRevision - опциональный метод, в случае если записываемый объект содержит вложенные объекты, позволяет указать номер ревизии для них.</li></ul><br>
Ну и когда класс контроллера ревизии написан, его необходимо зарегистрировать в менеджере ревизий:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">var</div><div class="code_line">&nbsp;&nbsp;Mgr: TRevisionsManager;</div><div class="code_line">begin</div><div class="code_line">&nbsp;&nbsp;Mgr := TRevisionsManager.GetInstance;</div><div class="code_line">&nbsp;&nbsp;Mgs.RegisterController(MyRevisionControllerClass);</div></ol></div></div></div></div><br>
<br>
Ну вот собственно и все. Файлы исходных кодов классов прилагаются, замечания/предложения приветствуются<br>
<span class="b-attach" data-size="8782" data-hits="19" data-attach-id="16043" data-attach-post-id="3077520">
			<span class="b-attach__title"></span><a class='b-attach-link' href='https://forum.sources.ru/index.php?act=Attach&amp;type=post&amp;id=3077520&amp;attach_id=16043' title='Скачать файл' target='_blank'>Serialization.rar</a> (, : 19)
		</span>]]></description>
        <author>--Ins--</author>
        <category>Общие вопросы</category>
      </item>
	
      </channel>
      </rss>
	