Delphi属性的现实世界例子在哪里?

14

我知道通过TMS Aurelius,我们可以使用“新”的2010属性功能在运行时将数据库表字段序列化为对象属性,例如,我不是这种深度面向对象模式的专家,所以我查看了TMS源代码,但无法理解如何自己实现它,无论是对于DB还是XML。

因此,我搜索了所有谷歌上关于Delphi Attributes的结果,所有人都发布了声明示例,然后甚至在展示他们的示例之前就停止了。

那么,在哪里可以找到真实世界的示例,说明如何投影、声明、编码和使用这些增强的类在一个表单/执行代码中?

有人在这里分享一个示例或者知道一篇完整的好文章吗?

编辑1:

答案应该有一个带有TButtonTForm,当点击时,执行一些创建的属性类的用途,不要仅仅展示属性和类接口,因为像我之前说过的那样,有许多这样的声明示例。


有些文本作者会花心思创建一个完整的文件,其中包括方法的声明和构造以及属性的有趣用法,但却没有给出使用示例。 - NaN
我非常感兴趣 - 一段时间前遇到了同样的“问题”,从那以后就从未使用过属性,因为我看不到它们的好处。 - Jan Doggen
4
我认为根本问题在于,许多长期使用 Delphi 的程序员太习惯于没有它们,以至于我们还没有找到很多地方可以明确地从中获益。对于许多第三方组件供应商来说,广泛使用属性会防止他们针对旧版本的 Delphi 用户,进一步限制了他们的市场。 - afrazier
1
请查看此链接:http://docwiki.embarcadero.com/RADStudio/XE4/de/Authentifizierung_und_Autorisierung#Rollen - Franz
1
@Jan,这取决于你的问题领域:典型的应用程序员可能不会明确定义自己的属性。主要是在开发通用的东西时,在运行时与传统数据类型关联的附加信息(元数据)操作的工具,比如通用的ORM框架。 - pf1957
@Franz,现在Embarcadero上的示例很清楚了。例如,我们可以在网页控制器类中使用,该类是控制器的派生类,需要进行身份验证,并且每个PageController可以具有属性注释,告诉哪个级别的用户必须显示此页面。 - NaN
3个回答

12
我必须说,我不太清楚您需要什么样的示例。在我的看法中,http://docwiki.embarcadero.com/RADStudio/Rio/en/Overview_of_Attributes 中应该包含了您所需的一切,但前提是您具备注解和/或方面编程的基本知识。
示例取决于特定软件作者使用属性的方式/目的。您提到了ORM系统:这里的典型用法是对表示DB实体的类成员进行注释,并添加后端框架中必要的附加信息以进行DB操作。假设您有一个DB实体,其中包含字段COMPANY CHAR(32)NOT NULL,并且您想在Delphi类中表示它:
TSomeDBEntity = class(...)
  FCDS: TClientDataset;
  ...
  constructor Create;
  ... 
  [TCharColumn('COMPANY', 32, false)]
  property CompanyName: string read GetCompanyName write SetCompanyName;
end;

然后您将使用构造函数定义属性TCharColumn。
constructor TCharColumn.Create(const AFieldName:string; ALength:integer; ANullable:boolean);
begin
  inherited;
  FName := AFieldName;
  FLength := ALength;
  FNullable := ANullable;
end;

这种注释的使用可能类似于这样:
FCDS := TClientDataset.Create(nil);
RttiContext := TRttiContext.Create;
try
  RttiType := RttiContext.GetType(self.ClassType);
  Props := RttiType.GetProperties;
  for Prop in Props do
    begin
      Attrs := Prop.GetAttributes;
      case Prop.PropertyType.TypeKind of
        tkUString:
          begin
            for Attr in Attrs do
              if Attr is TCharColumn then
              begin
                ColAttr := TCharColumn(Attr);
                FCDS.FieldDefs.Add(ColAttr.FName, ftString, ColAttr.FLength, not ColAttr.FNullable);
              end;
          end;
        else
          //... ;
      end;
    end;
finally
  RttiContext.Free;
end;

这段程序演示了如何根据 Delphi 中的注释在运行时定义数据集中的字段。由于缺乏命名参数,我们受到一定限制,因此使用参数列表不够灵活,例如在 Java 中(比较 TMS Aurelius 注释集 http://www.tmssoftware.com/site/manuals/aurelius_manual.pdfhttp://www.techferry.com/articles/hibernate-jpa-annotations.html)。

从我在TMS ORM网站上看到的内容来看,代码在开始时并不知道表中的字段是哪些,因此当我们使用Create方法时,框架将读取数据库并允许我们键入MyString := MyORM.TableName.TableField;,而在您的示例中,您必须声明[TCharColumn('COMPANY', 32, false)]。我是否误解了属性的真正用途? - NaN
传统创建数据库表的方法是编写SQL命令CREATE TABLE,然后使用SQL操作该表。另一方面,在ORM中,您不使用SQL命令(除了where子句)。数据库表是带注释的类(包含属性),您通过ORM操作数据。我不知道TMS,但例如Hibernate在启动时检查cfg如何处理有关您的注释的RDBMS中的DB模式:validate:生产,对diff抛出异常,update:尝试更新RDBMS中的DB模式(不能更新所有内容),createcreate-drop:删除现有的DB,包括数据,并创建新的DB模式。 - pf1957

11

如果你想声明自己的属性,可以这样做:

type
  TDisplayLabelAttribute = class(TCustomAttribute)
  private
    FText: string;
  public
    constructor Create(const aText: string);
    property Text: string read FText write FText;
  end;

属性是一个普通的类,它有TCustomAttribute作为其祖先。您可以像平常一样实现它:

implementation

constructor TDisplayLabelAttribute.Create(const aText: string);
begin
  FText := aText;
end;

现在属性已经被声明和实现,您可以直接使用它:

[DisplayLabel('My Class')]
TMyClass = class
end;

现在您已经声明和实现了一个属性,并将其用于为某个类添加显示标签。最终阶段是使用该属性,因为您已经用它来装饰一个类。使用该属性的代码不驻留在属性或装饰的类中,而是在将使用装饰的服务层中实现。

假设我们有一个类,返回一个可能的类显示标签:

type
  TArtifactInspector = class
  public
    class function DisplayLabelFor(aClass: TClass): string;
  end;

该方法将检查一个类并返回其显示标签,如果存在的话。否则它将返回一个空字符串:

implementation

uses
  Rtti;

class function TArtifactInspector.DisplayLabelFor(aClass: TClass): string;
var
  rttiContext: TRttiContext;
  rttiType: TRttiType;
  attribute: TCustomAttribute;
begin
  rttiContext := TRttiContext.Create;
  try
    rttiType := rttiContext.GetType(aClass);
    for attribute in rttiType.GetAttributes do
      if attribute is TDisplayLabelAttribute then
        Exit(TDisplayLabelAttribute(attribute).Text);
    Result := '';
  finally
    rttiContext.Free;
  end; // try to recover and return the DisplayLabel
end;

1
在这里找到另一个例子 http://robstechcorner.blogspot.de/2009/10/ini-persistence-rtti-way.html - Franz
那么,它替代了在TObject.Create方法中设置对象属性的需要,是吗?我认为对于Delphi用户目前来说这并不是必要的,但对于Java用户来说确实非常必要,因为他们没有像我们一样真正好用的可视化设计工具。 - NaN
@EASI:我展示的代码只是一个例子。即使在Delphi中,属性也有很多用途。例如,您可以使用信息装饰类,以执行其实例的持久性。在这种情况下,对象检查器将毫无帮助。属性是语言中的重要改进,即使对于Delphi也是如此。许多程序员通常忽略它们,仅依赖于Delphi的可视部分。但是,当涉及到代码时,属性可以产生很大的差异。我建议您开放心态! - AlexSC
@EASI:使用属性的一个非常真实的例子是安全系统。我必须构建一个应用程序,使用户可以部分访问表单功能。例如,两个用户可以在应用程序中打开一个表单并选择记录,但其中一个用户不能删除它们。如何处理?有很多解决方案,但我使用的是声明一个“授权给”属性,用于将表单的某些部分与某个角色关联起来。具有此角色的用户将被允许执行删除操作。其他人则不行! - AlexSC
我不太清楚TDisplayLabelAttribute类如何与[DisplayLabel('My Class')]建立连接?如果是[TDisplayLabel('My Class')],我会理解,但实际上并不是这样。请问有人能为我解惑吗? - tcxbalage
显示剩余5条评论

9

不确定问题是要求属性使用的真实世界示例还是如何使用属性将数据库表序列化为对象。下面的示例是一个虚构的简单示例(但仍然是一个示例),展示如何使用属性记录对象属性的更改。

定义您的自定义属性

//By convention attributes are *not* prefixed with a `T` 
//and have the word `Attribute` in their name
LoggableAttribute = class(TCustomAttribute)
  private
    FDescription : String;
  public
    constructor Create(Description: String);
    property Description: String read FDescription;
  end;

使用属性的类TProduct的“hello world”示例

TProduct = Class(TObject)
   private
    FPrice: Double;
    FDescription: String;
    ..
   public  
    [LoggableAttribute('Product Price')]
    property Price : Double read FPrice write SetPrice;
    [Loggable('Product Description')]   {the `Attribute` part is optional}
    property Description : String read FDescription write SetDescription;
    property IsDirty : Boolean read FIsDirty;
  End;

任何具有“可记录属性”的类都可以传递给此方法,以遍历其属性并记录它们。
procedure LogChanges(LoggableClass: TObject);
var
 c : TRttiContext;
 t : TRttiType;
 p : TRttiProperty;
 a : TCustomAttribute;
 Value : TValue;
begin
 c := TRttiContext.Create;    
 try
   t := c.GetType(LoggableClass.ClassType);
   for p in t.getProperties do
     for a in p.GetAttributes do
       if a is TLoggableProperty then begin
         Value := p.GetValue(LoggableClass);   
         // log to db.. 
         AddLogEntry(p.Name, TLoggableProperty(a).Description, Value.ToString);
       end;
 finally
   c.Free;
 end;

结束;

使用示例:

var
 P : TProduct;
begin    
 P := TProduct.Create; 
 P.LoadPropertiesFromDB;
 ...
 ... User edits price ...    
 ... 
 P.Price := 499.99;
 ...
 ... Save product to DB 
 if P.IsDirty then  // save and log
   LogChanges(P);

也许我应该把问题改成“如何使用属性将数据库表序列化为对象”... :-) - NaN
按照惯例,属性名称前不需要加上“T”。此外,在注释中可以省略名称中的“Attribute”部分。因此,名为“LoggableAttribute”的属性可以像这样使用:[Loggable('test')] - Johan
什么是TLoggableProperty? - Mohamad
@Mohamad:这是声明属性的类,用于装饰其他语言结构。 - AlexSC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接