Как лучше всего сериализовать конфигурацию приложения Delphi?

Я сам отвечу на этот вопрос, но не стесняйтесь давать свои ответы, если вы быстрее меня или вам не нравится мое решение. Мне только что пришла в голову эта идея, и я хотел бы высказать свое мнение по этому поводу.

Цель: класс конфигурации, который можно читать (например, INI-файл), но без необходимости записывать (и адаптировать после добавления нового элемента конфигурации) методы загрузки и сохранения.

Я хочу создать класс вроде

TMyConfiguration = class (TConfiguration)
  ...
  property ShowFlags : Boolean read FShowFlags write FShowFlags;
  property NumFlags : Integer read FNumFlags write FNumFlags;
end;

Вызов TMyConfiguration.Save (унаследованный от TConfiguration) должен создать файл вроде

[Options]
ShowFlags=1
NumFlags=42

Вопрос: Как лучше всего это сделать?


person jpfollenius    schedule 18.08.2009    source источник


Ответы (7)


Это мое предлагаемое решение.

У меня базовый класс

TConfiguration = class
protected
  type
    TCustomSaveMethod = function  (Self : TObject; P : Pointer) : String;
    TCustomLoadMethod = procedure (Self : TObject; const Str : String);
public
  procedure Save (const FileName : String);
  procedure Load (const FileName : String);
end;

Методы загрузки выглядят следующим образом (соответственно метод сохранения):

procedure TConfiguration.Load (const FileName : String);
const
  PropNotFound = '_PROP_NOT_FOUND_';
var
  IniFile : TIniFile;
  Count : Integer;
  List : PPropList;
  TypeName, PropName, InputString, MethodName : String;
  LoadMethod : TCustomLoadMethod;
begin
  IniFile := TIniFile.Create (FileName);
  try
    Count := GetPropList (Self.ClassInfo, tkProperties, nil) ;
    GetMem (List, Count * SizeOf (PPropInfo)) ;
    try
      GetPropList (Self.ClassInfo, tkProperties, List);
      for I := 0 to Count-1 do
        begin
        TypeName  := String (List [I]^.PropType^.Name);
        PropName  := String (List [I]^.Name);
        InputString := IniFile.ReadString ('Options', PropName, PropNotFound);
        if (InputString = PropNotFound) then
          Continue;
        MethodName := 'Load' + TypeName;
        LoadMethod := Self.MethodAddress (MethodName);
        if not Assigned (LoadMethod) then
          raise EConfigLoadError.Create ('No load method for custom type ' + TypeName);
        LoadMethod (Self, InputString);
        end;
    finally
      FreeMem (List, Count * SizeOf (PPropInfo));
    end;
  finally
    FreeAndNil (IniFile);
  end;

Базовый класс может предоставлять методы загрузки и сохранения для типов delphi по умолчанию. Затем я могу создать такую ​​конфигурацию для своего приложения:

TMyConfiguration = class (TConfiguration)
...
published
  function  SaveTObject (P : Pointer) : String;
  procedure LoadTObject (const Str : String);
published
  property BoolOption : Boolean read FBoolOption write FBoolOption;
  property ObjOption : TObject read FObjOption write FObjOption;
end;

Пример настраиваемого метода сохранения:

function TMyConfiguration.SaveTObject (P : Pointer) : String;
var
  Obj : TObject;
begin
  Obj := TObject (P);
  Result := Obj.ClassName;  // does not make sense; only example;
end;       
person jpfollenius    schedule 18.08.2009
comment
Мне это кажется вполне нормальным. Что заставляет вас думать, что может быть более разумное решение? - person Jeroen Wiert Pluimers; 21.08.2009
comment
@Jeroen: Опыт, который в большинстве случаев, когда я спрашиваю здесь, я получаю много умных комментариев, предложений по улучшению и критики :) В дополнение к этому, я хотел поделиться этим фрагментом кода, чтобы другие могли потенциально получить выгоду. - person jpfollenius; 24.08.2009

Я использую XML для всего своего приложения как средство конфигурации. Это:

  • гибкий
  • будущее функциональное доказательство
  • легко читать с помощью любого текстового ридера
  • очень легко расширяется в приложении. Никаких модификаций классов не требуется

У меня есть библиотека XML, которая позволяет очень легко читать или изменять конфигурацию, даже не обращая внимания на недостающие значения. Теперь вы также можете сопоставить XML с классом внутри приложения для более быстрого доступа, если проблема в скорости или определенные значения читаются постоянно.

Я считаю, что другие методы настройки гораздо менее необязательны:

  • Ini-файл: нет глубокой структуры, гораздо менее гибкий
  • реестр: просто держитесь подальше от этого.
person Runner    schedule 18.08.2009
comment
Я делаю то же самое, но использую мастер привязки XML-данных Delphi для сопоставления файла с объектами. - person Craig Stuntz; 18.08.2009
comment
Дело в том, что я думаю, что файлы INI могут быть легко понятны даже пользователям, в то время как не все могут понять XML. Я знаю, что для этого у меня должны быть диалоговые окна настроек, но я бы хотел, чтобы кто-нибудь мог изменять более сложные настройки, не углубляясь в XML. - person jpfollenius; 18.08.2009
comment
XML-файлы действительно легко читать. Я говорю здесь о базовом XML. Просто узлы с атрибутами и значениями (текст). Он вряд ли менее читабелен по сравнению с INI. Ладно, у вас в тегах больше обшивки котла. Но если вы беспокоитесь о модификациях пользователей, создайте для них графический интерфейс. Вы не можете полагаться на то, что пользователь изменяет файл сам по себе. Даже INI. - person Runner; 18.08.2009
comment
Вы, наверное, правы, но в моем случае пользователи привыкли работать с файлами INI, и я хотел бы это сохранить. Если я использую XML таким простым способом (без глубины и т. Д.), В файлах будет много накладных расходов. - person jpfollenius; 18.08.2009
comment
Согласен, если вы используете такой XML, лучше оставаться с файлами INI. Это также сводится к инструментам. Если у вас есть подходящие инструменты, XML может стать очень и очень мощным, в противном случае он может быть просто неуклюжим. Я не сказал, что файлы INI плохие, просто они предлагают меньшую гибкость;) - person Runner; 18.08.2009
comment
Если пользователю нужно отредактировать файл конфигурации вручную, вы уже проиграли игру. - person Craig Stuntz; 19.08.2009
comment
Им не нужно. Они могут, если хотят настроить некоторые дополнительные параметры. У большинства нет проблем с редактированием INI-файла. - person jpfollenius; 19.08.2009
comment
Единственная причина, по которой пользователи когда-либо захотят это сделать, - это (правильная) причина, которую вы уже указали: я знаю, что у меня должны быть диалоги настроек для этого. Правильно. Сказать, что файлы INI достаточно хороши, - это отговорка. - person Craig Stuntz; 19.08.2009
comment
Ну, это для меня черно-белое. Большинство опытных пользователей не боятся изменять INI-файл, так зачем загромождать диалог настроек особыми и расширенными настройками? Кроме того, на поддержание актуальности диалогового окна настроек уходит много времени. Опять же: я не говорю здесь об основных настройках, я говорю о настройке параметров. - person jpfollenius; 20.08.2009
comment
Кропотливый? С помощью XML вы могли (должны) генерировать пользовательский интерфейс динамически! Файлы INI слишком ограничены для этого, а файлы XML - нет; у вас могут быть атрибуты для отображаемых имен, типов элементов управления и т. д. Странно, что вы пытаетесь одновременно сказать, что файлы XML слишком сложны для понимания пользователем и что у вас действительно должен быть пользовательский интерфейс (с чем я согласен), одновременно утверждая, что только опытные пользователи (которые понимают XML) когда-либо будут делать это, и что создание пользовательского интерфейса (почти бесплатного с конфигурацией XML) - это слишком много! - person Craig Stuntz; 20.08.2009
comment
Как я уже говорил выше: в настоящее время мы используем файлы INI, и это ничего не должно измениться. Я не против XML и согласен с вашим мнением, что все должно быть интегрировано в UI. Но на самом деле это был не мой вопрос. Мой вопрос заключался в том, как создать INI-файл, такой как файл конфигурации, поэтому, возможно, нам всем следует просто принять это предварительное условие. - person jpfollenius; 24.08.2009
comment
И не поймите меня неправильно: я ценю ваш вклад по этой теме. - person jpfollenius; 24.08.2009

Я предпочитаю создать интерфейс в моем модуле глобальных интерфейсов:

type
  IConfiguration = interface
    ['{95F70366-19D4-4B45-AEB9-8E1B74697AEA}']
    procedure SetConfigValue(const Section, Name,Value:String);
    function GetConfigValue(const Section, Name:string):string;
  end;

Затем этот интерфейс отображается в моей основной форме:

type
  tMainForm = class(TForm,IConfiguration)
  ...
  end;

Большую часть времени фактическая реализация находится не в основной форме, это просто заполнитель, и я использую ключевое слово реализации, чтобы перенаправить интерфейс на другой объект, принадлежащий основной форме. Дело в том, что ответственность за настройку делегирована. Каждому устройству все равно, хранится ли конфигурация в таблице, ini-файле, xml-файле или даже в реестре. Что это позволяет мне делать в ЛЮБОМ модуле, который использует модуль глобальных интерфейсов, - это делать вызов, подобный следующему:

var
  Config : IConfiguration;
  Value : string;
begin
  if Supports(Application.MainForm,IConfiguration,Config) then
    value := Config.GetConfiguration('section','name');
  ...      
end;

Все, что нужно, - это добавить FORMS и мой модуль глобальных интерфейсов к модулю, над которым я работаю. И поскольку он не ИСПОЛЬЗУЕТ основную форму, если я позже решу повторно использовать ее для другого проекта, мне не придется вносить никаких дальнейших изменений ... она просто работает, даже если схема хранения конфигурации полностью отличается.

Я обычно предпочитаю создать таблицу (если я имею дело с приложением базы данных) или XML-файл. Если это многопользовательская база данных, я создам две таблицы. Один для глобальной конфигурации, а другой для пользовательской конфигурации.

person skamradt    schedule 18.08.2009
comment
+1, потому что я согласен с большей частью того, что вы написали, и различия в основном связаны со стилем. Но ответ в его нынешнем виде не имеет ничего общего с вопросом, который я понял как лучший способ написать базовый класс, который может загружать себя и сохранять себя в постоянном хранилище без необходимости изменений в дочерних классах. . По крайней мере, это вопрос, на который ответил OP, заголовок просто не соответствует тексту вопроса. Тем не менее, smasher должен последовать вашему совету, а не жестко кодировать решение для INI-файла. - person mghie; 18.08.2009
comment
Я хочу, чтобы настройка моей конфигурации занимала как можно меньше времени, потому что она повторяется с каждым приложением. Хотя мне в какой-то мере нравится ваш подход, для меня этого недостаточно KISS. Простого глобального синглтона с бэкэндом XML и хорошим интерфейсом для доступа к нему в большинстве случаев достаточно. И более чем достаточно для небольших простых приложений. Даже полностью ориентированному на базу данных приложению по-прежнему нужен простой файл конфигурации для доступа к этим данным :) - person Runner; 18.08.2009
comment
Я не мог больше согласиться с комментарием Mghie. Вы предлагаете хорошую альтернативу глобальной переменной с использованием интерфейса и делегирования, но ничего не говорите о том, как выполняется сериализация. Было бы не проблема извлечь IPropertyStorer из моего решения, чтобы отделить конфигурацию от конкретной реализации (файл INI / XML / база данных). Но это не меняет положения о постоянном хранилище с минимальными усилиями. - person jpfollenius; 18.08.2009
comment
@mghie: Я обновил заголовок. Я согласен с тем, что было бы неплохо исключить материал из INI-файла. - person jpfollenius; 19.08.2009

По сути, вы запрашиваете решение для сериализации данного объекта (в вашем случае это конфигурация файлов ini). Для этого есть готовые компоненты, и вы можете начать поиск здесь и здесь.

person Tihauan    schedule 18.08.2009

Некоторое время назад я написал небольшой модуль для этой же задачи - сохранить / загрузить конфигурацию приложения в xml-файл.

Проверьте модуль Obj2XML.pas в нашей бесплатной библиотеке SMComponent: http://www.scalabium.com/download/smcmpnt.zip

person Community    schedule 19.08.2009

Это было бы для Java.

Мне нравится использовать java.util.Properties для чтения в файлах конфигурации или файлах свойств. Что мне нравится, так это то, что вы помещаете свой файл со строками так же, как показано выше (ключ = значение). Кроме того, он использует # (знак фунта) для строки с комментарием, как и во многих языках сценариев.

Итак, вы можете использовать:

ShowFlags=true
# this line is a comment    
NumFlags=42

и т.д

Тогда у вас просто есть такой код:

Properties props = new Properties();
props.load(new FileInputStream(PROPERTIES_FILENAME));
String value = props.getProperty("ShowFlags");
boolean showFlags = Boolean.parseBoolean(value);

Как это просто.

person Nick    schedule 18.08.2009
comment
Это не очень помогает, так как я просил Delphi, а предлагаемый вами класс специфичен для Java ... - person jpfollenius; 18.08.2009
comment
да сожалею об этом. Вы на самом деле не уточняли, поэтому я попробовал. Вопрос был сформулирован очень в общих чертах. Не видел ваш тег delphi. - person Nick; 18.08.2009

Ответ Nicks (с использованием свойств Java) имеет смысл: этот простой способ чтения и передачи конфигурации между частями приложения не вводит зависимости от специального класса конфигурации. Простой список ключей / значений может уменьшить зависимости между модулями приложения и упростить повторное использование кода.

В Delphi простая конфигурация на основе TStrings - простой способ реализовать конфигурацию. Пример:

mail.smtp.host=192.168.10.8    
mail.smtp.user=joe    
mail.smtp.pass=*******
person mjn    schedule 22.08.2009