2009-08-18 6 views
10

Я сам отвечу на этот вопрос, но не стесняйтесь давать ответы, если вы быстрее меня или вам не нравится мое решение. Я только придумал эту идею и хотел бы получить некоторые мнения по этому поводу.Каков наилучший способ сериализации конфигурации приложения Delphi?

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

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

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

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

[Options] 
ShowFlags=1 
NumFlags=42 

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

ответ

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; 

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

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;  
+0

Это выглядит вполне нормально для меня.Почему вы думаете, что может быть более разумное решение? –

+0

@ Jeroen: Опыт, который в большинстве случаев, когда я спрашиваю здесь, я получаю много умных комментариев, предложений по улучшению и критике :) В дополнение к этому, я хотел поделиться этим фрагментом кода, чтобы другие могли потенциально извлечь выгоду. – jpfollenius

0

Это было бы для 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); 

просто.

+0

Не очень помогает с тех пор, как я просил Delphi, и класс, который вы предлагаете, является специфичным для Java ... – jpfollenius

+0

да простите об этом. Вы на самом деле не указали, поэтому я сделал это. Вопрос был сформулирован в целом. Не видел тэг delphi. – Nick

+0

без проблем. Спасибо, в любом случае. – jpfollenius

1

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

6

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

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

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

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

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

Я делаю то же самое, но я использую Delphi XML Data Binding Wizard для сопоставления файла с объектами. –

+1

Дело в том, что я думаю, что файлы INI могут быть легко поняты даже пользователям, хотя не каждый может понять XML. Я знаю, что для этого мне нужны диалоги настроек, но я бы хотел, чтобы кто-нибудь смог изменить более сложные настройки без погружения в XML. – jpfollenius

+1

XML-файлы очень легко читаются. Я говорю об основном XML здесь. Просто узлы с атрибутами и значениями (текст). Это вряд ли менее читаемо по сравнению с INI. Хорошо, у вас есть больше «котельного покрытия» в тегах. Но если вы беспокоитесь о модификации пользователей, тогда создайте для них графический интерфейс. Вы не можете полагаться на пользователя, чтобы самостоятельно изменить файл. Даже ИНИ. – Runner

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; 

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

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

+0

+1, потому что я согласен с большей частью того, что вы написали, и различия в основном связаны с проблемами стиля. Но ответ в его нынешнем виде не имеет особого отношения к вопросу, который я понял как «лучший способ написать базовый класс, который способен загружать себя и хранить себя в постоянном хранилище без необходимости изменений в потомстве классы». Это, по крайней мере, вопрос, который ответил OP, название просто не соответствует тексту вопроса. Тем не менее, smasher должен следовать вашим рекомендациям вместо жесткого кодирования решения INI-файла. – mghie

+0

Я хочу, чтобы моя настройка конфигурации занимала столько времени, сколько необходимо, потому что она повторяется с каждым приложением. Хотя мне нравится ваш подход в какой-то степени, для меня это не KISS. Простой глобальный синглтон с поддержкой XML и хорошим интерфейсом для доступа к нему в большинстве случаев достаточно хорош. И более чем достаточно для небольших простых приложений. Даже полностью ориентированное на базу данных приложение по-прежнему нуждается в простом файле конфигурации для доступа к этим данным :) – Runner

+0

Я больше не мог согласиться с комментарием mghie. Вы предлагаете хорошую альтернативу глобальной переменной, используя интерфейс и делегирование, но вы ничего не говорите о том, как выполняется сериализация. Было бы нецелесообразно извлекать IPropertyStorer из моего решения, чтобы отделить конфигурацию от конкретной реализации (INI-файл/XML/база данных). Но это не меняет точки постоянного хранения с минимальными усилиями. – jpfollenius

1

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

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

0

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

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

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