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

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

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

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

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

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

Ответ 1

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

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

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;       

Ответ 2

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

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

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

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

  • Файл Ini: нет структуры глубины, гораздо менее гибкая
  • : просто держитесь подальше от этого.

Ответ 3

Мой предпочтительный метод - создать интерфейс в моем глобальном интерфейсе:

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;

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

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

Все, что необходимо, добавляет FORMS и мой модуль глобальных интерфейсов к блоку, над которым я работаю. И поскольку он не использует основную форму, если я решил позже использовать его для другого проекта, мне не нужно делать никаких дальнейших изменений... он просто работает, даже если схема хранения конфигурации полностью отличается.

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

Это будет для Java.

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

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

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);

Легко, как это.

Ответ 7

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

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

mail.smtp.host=192.168.10.8    
mail.smtp.user=joe    
mail.smtp.pass=*******