2009-08-18 106 views
10

我会自己回答这个问题,但如果您比我快或者您不喜欢我的解决方案,请随时提供您的答案。我刚刚提出这个想法,并希望对此有一些看法。序列化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 

问题:什么是做的最好的方法这个?

回答

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; 

负载的方法是这样的(Save方法相应地):

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

这对我来说看起来相当不错。是什么让你觉得可能有更聪明的解决方案? – 2009-08-21 13:01:03

+0

@Jeroen:在大多数情况下,当我在这里问我的经验,我得到了很多聪明的评论,改进和批评的建议:)除此之外,我想分享这段代码,以便其他人可以受益。 – jpfollenius 2009-08-24 06:25:40

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 2009-08-18 12:45:06

+0

对此抱歉。你其实并没有指定,所以我给了它一个镜头。问题非常普遍。没有看到你的delphi标签。 – Nick 2009-08-18 13:31:17

+0

没问题。不管怎么说,还是要谢谢你。 – jpfollenius 2009-08-18 13:33:51

1

基本上你要求一个解决方案来序列化一个给定的对象(在你的情况下,配置到ini文件)。有现成的组件,你可以开始寻找herehere

6

我使用XML作为我的所有应用程序的配置手段。它是:

  • 灵活
  • 将来的功能证明
  • 容易用任何文本阅读器
  • 很容易的适用范围扩大到阅读。不需要类修改

我有一个XML库,它使得读取或修改配置非常容易,甚至不需要监视缺少的值。现在,您还可以将XML映射到应用程序内部的类,以便在速度问题或速度不断读取某些值的情况下更快地访问。

我发现其他配置方法远不如可选:

  • INI文件:未在深层结构,远不如灵活
  • 注册表:刚刚从保持距离。
+1

我的工作基本相同,但我使用Delphi的XML数据绑定向导将文件映射到对象。 – 2009-08-18 13:30:29

+1

问题是:我认为即使用户也可以很容易地理解INI文件,但并不是每个人都能够理解XML。我知道我应该为此设置对话框,但我希望任何人都能够更改高级设置而不必潜入XML。 – jpfollenius 2009-08-18 13:36:32

+1

XML文件非常易于阅读。这里我正在讨论基本的XML。只有具有属性和值(文本)的节点。与INI相比,它几乎不可读。好的,你在标签中有更多的“锅炉电镀”。但如果你担心用户修改,然后为他们制作一个GUI。您不能依赖用户自行修改文件。即使是一个INI。 – Runner 2009-08-18 13:51:47

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; 

大部分时间实际的实现不是主要形式,它只是一个占位符,我使用implements关键字将接口重定向到主窗体拥有的另一个对象。这一点是配置的责任被委托。每个单元不关心配置是否存储在表,ini文件,xml文件或甚至是注册表中。这里做的事情让我在使用全局接口单元中的任何单元做的就是像一个电话如下:

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

所有这一切需要的是增加的形式和我的全球接口单元,以我的工作单位上。而且因为它不使用mainform,所以如果我决定以后再使用它来做另一个项目时,我不必做任何进一步的修改......即使配置存储方案完全不同,它也能正常工作。

我的一般偏好是创​​建一个表(如果我正在处理数据库应用程序)或XML文件。如果它是一个多用户数据库应用程序,那么我将创建两个表。一个用于全局配置,另一个用于用户配置。

+0

+1,因为我同意你写的大部分内容,差异主要是风格问题。但是现在的答案与这个问题没有多大关系,我把它理解为:“编写一个基类的最佳方式是什么,它能够将自身加载并存储到持久存储中,而不需要更改后代类”。这至少是OP回答的问题,标题与问题文本不符。尽管如此,smasher应该遵循您的建议,而不是对INI文件解决方案进行硬编码。 – mghie 2009-08-18 17:25:12

+0

我希望我的配置设置能够根据需要尽可能缩短时间,因为它会在每个应用程序中重复使用。尽管我在某种程度上喜欢你的方法,但对我来说不足够。具有XML后端的简单全局单例以及访问它的良好界面在大多数情况下已经足够了。对于小型简单应用程序来说已经足够了。 即使完全面向数据库的应用程序仍然需要一个简单的配置文件来访问该数据:) – Runner 2009-08-18 18:24:05

+0

我完全同意mghie的评论。您提出了一个使用接口和委派的全局变量的替代方案,但您并未提到如何执行序列化。从我的解决方案中提取IPropertyStorer以将配置与具体实现(INI文件/ XML /数据库)分开是没有问题的。但是,这并没有改变关于持久存储的观点,只需很少的努力。 – jpfollenius 2009-08-18 19:09:32

0

尼克斯的答案(使用Java属性)有一个观点:这个简单的方式来阅读和周围传递应用程序不会引入的部件之间的配置依赖于特殊的配置类。一个简单的键/值列表可以减少应用程序模块之间的依赖关系,并使代码重用更容易。

在Delphi中,一个简单的基于TStrings的配置是一种实现配置的简单方法。例如:

mail.smtp.host=192.168.10.8  
mail.smtp.user=joe  
mail.smtp.pass=******* 
相关问题