第十六章 Caché 定义方法和触发器生成器

第十六章 Caché 定义方法和触发器生成器

方法生成器是一种特定类型的方法,它生成自己的运行时代码。同样,触发器生成器是生成自己的运行时代码的触发器。

介绍

Caché的强大功能是定义方法生成器的能力:类编译器调用这些小程序以生成方法的运行时代码。类似地,触发器编译器由类编译器调用,并为触发器生成运行时代码。

方法生成器在Caché类库中广泛使用。例如,%Persistent类的大多数方法都实现为方法生成器。这样就可以为每个持久类提供定制的存储代码,而不是效率较低的通用代码。大多数Caché数据类型类方法也都实现为方法生成器。同样,这使这些类能够提供依赖于使用它们的上下文的自定义实现。

可以在自己的应用程序中使用方法和触发器生成器。对于方法生成器,一种常见用法是定义一个或多个实用程序父类,这些父类为使用它们的子类提供专门的方法。

这些实用程序类中的方法生成器根据使用它们的类的定义(属性,方法,参数值等)创建特殊代码。Caché库中提供的%Populate和%XML.Adaptor类就是该技术的一个很好的例子。

基础

方法生成器只是Caché类的方法,其CodeMode关键字设置为“ objectgenerator”:

Class PHA.OP.MOB.Test Extends %RegisteredObject
{
/// d ##class(PHA.OP.MOB.Test).TestBasic()
ClassMethod TestBasic() [ CodeMode = objectgenerator ]
{
    
    Do %code.WriteLine(" Write """ _ %class.Name _ """")
    Do %code.WriteLine(" Write """ _ %class.Methods _ """")
    Do %code.WriteLine(" Quit")
    Quit $$$OK
}
}

编译类MyApp.MyClass时,它以带有以下实现的MyMethod方法结束:

DHC-APP> d ##class(PHA.OP.MOB.Test).TestBasic()
PHA.OP.MOB.Test

注意:上例中CodeMode的值为“ objectgenerator”,因为此方法生成器使用首选的基于对象的方法生成器机制。在Caché的第5版之前,有一个不同的首选机制,其中CodeMode的值为“ generator”。保留了较旧的机制以保持兼容性,而新的应用程序应使用“ objectgenerator”

还可以定义触发器生成器。为此,在触发器的定义中使用CodeMode =“ objectgenerator”。触发器中可用的值与方法生成器中的值略有不同。

生成器如何工作的

方法生成器在编译类时生效。方法生成器的操作非常简单。编译类定义时,类编译器将执行以下操作:

  1. 它为类解析继承(构建所有继承成员的列表)。
  2. 它列出了所有指定为方法生成器的方法(通过查看每个方法的CodeMode关键字)。
  3. 它从所有方法生成器收集代码,将其复制到一个或多个临时例程中,然后进行编译(这使执行方法生成器代码成为可能)。
  4. 它创建一组瞬态对象,这些对象代表正在编译的类的定义。这些对象可用于方法生成器代码。
  5. 它为每个方法生成器执行代码。
    如果存在,编译器将通过查看每个方法的GenerateAfter关键字的值来安排其调用方法生成器的顺序。在方法之间可能存在编译器时序依赖性的情况下,此关键字为提供了一些控制。
  6. 它将每个方法生成器的结果(代码行以及对其他方法关键字的任何更改)复制到已编译的类结构中(用于生成该类的实际代码)。

请注意,原始方法签名(参数和返回类型)以及任何方法关键字值都用于生成的方法。如果将方法生成器指定为返回类型为%Integer,则实际方法的返回类型为%Integer。

  1. 它通过将方法生成器生成的代码与来自所有非方法生成器方法的代码组合在一起,生成该类的可执行代码。

触发器生成器的细节相似。

方法生成器可用的值

实现方法生成器的关键是了解执行方法生成器代码的上下文。如上一节所述,类编译器在解决类继承之后但在为该类生成代码之前调用方法生成器代码。当调用方法生成器代码时,类编译器使以下变量可用于方法生成器代码:

变量 描述
%code %Stream.MethodGenerator类的实例。这是向其中编写方法代码的流。
%class %Dictionary.ClassDefinition类的实例。它包含该类的原始定义。
%method %Dictionary.MethodDefinition类的实例。它包含方法的原始定义。
%compiledclass %Dictionary.CompiledClass类的实例。它包含要编译的类的已编译定义。因此,它包含有关继承已解决的类的信息(例如,所有属性和方法的列表,包括从超类继承的属性和方法)。
%compiledmethod %Dictionary.CompiledMethod类的实例。它包含所生成方法的编译定义。
%parameter 一个数组,其中包含按参数名称索引的所有类参数的值。例如,%parameter(“ MYPARAM”)包含当前类的MYPARAM类参数的值。提供此变量是使用%class对象提供的参数定义列表的一种更简便的选择。
/// d ##class(PHA.OP.MOB.Test).TestBasic()
ClassMethod TestBasic() [ CodeMode = objectgenerator ]
{
    s className=%code.WriteLine(" Write """ _ %class.Name _ """")
    w className,!
    s methodName=%code.WriteLine(" Write """ _ %method.Name _ """")
    w methodName,!
    Do %code.WriteLine(" Write """ _ %class.Name _ """")
    Do %code.WriteLine(" Write """ _ %method.Name _ """")
    Do %code.WriteLine(" Quit")
    Quit $$$OK
}
DHC-APP> d ##class(PHA.OP.MOB.Test).TestBasic()
PHA.OP.MOB.TestTestBasicPHA.OP.MOB.TestTestBasic

触发发生器可用的值

像方法一样,触发器可以定义为生成器。也就是说,可以在触发器的定义中使用CodeMode =“ objectgenerator”。触发器生成器中提供以下变量:

<center>添加可用于触发器生成器的变量</center>

变量 描述
%code, %class, %compiledclass, %parameter 见上一章
%trigger %Dictionary.TriggerDefinition类的实例。它包含触发器的原始定义。
%compiled%trigger %Dictionary.CompiledTrigger类的实例。它包含正在生成的触发器的已编译定义。

定义方法生成器

要定义方法生成器,请执行以下操作:

  1. 定义一个方法并将其CodeMode关键字设置为“ objectgenerator”。

  2. 在方法的主体中,编写在编译类时生成实际方法的代码。此代码使用%code对象写出代码。使用其他可用对象作为输入来决定要生成的代码。

以下是方法生成器的示例,该方法生成器创建一个列出其所属类的所有属性的名称的方法:

/// d ##class(PHA.OP.MOB.Test).ListProperties()
ClassMethod ListProperties() [ CodeMode = objectgenerator ]
{
    For i = 1:1:%compiledclass.Properties.Count() {
        Set prop = %compiledclass.Properties.GetAt(i).Name
        Do %code.WriteLine(" Write """ _ prop _ """,!")
    }
    Do %code.WriteLine(" Quit")
    Quit $$$OK
}

该生成器将创建一个实现类似于以下内容的方法:

DHC-APP>d ##class(PHA.OP.MOB.Test).ListProperties()
%%OID
Colors
Count
Doctors
PropName
name
objectList
testTwo
 

请注意以下有关方法生成器代码的内容:

  1. 它使用%code对象的WriteLine方法将代码行写入包含该方法实际实现的流。(也可以使用Write方法来写没有行尾字符的文本)。

  2. 生成的代码的每一行都有一个前导空格字符。这是必需的,因为CachéObjectScript不允许在行的第一个空格内使用命令。如果我们的方法生成器正在创建Basic或Java代码,则情况并非如此。

  3. 当生成的代码行出现在字符串中时,必须格外小心,将引号字符加倍(“”)以转义引号字符。

  4. 要查找该类的属性列表,它使用%compiledclass对象。它可以使用%class对象,但随后它只会列出正在编译的类中定义的属性;它不会列出继承的属性。

  5. 它返回状态代码$$$$ OK,表示方法生成器成功运行。此返回值与该方法的实际实现无关。

其他语言的方法生成器

可以生成不同语言的代码。为此,设置%code对象的Language属性以指定目标语言。

默认情况下,所生成代码的语言与用于编写代码生成器方法的语言相同(由Language关键字指定)。

在方法生成器中指定CodeMode

默认情况下,方法生成器将创建一个“代码”方法(即,所生成方法的CodeMode关键字设置为“代码”)。可以使用%code对象的CodeMode属性更改此设置。
例如,以下方法生成器将生成一个ObjectScript表达式方法:

Method Double(%val As %Integer) As %Integer [ CodeMode = objectgenerator ]
{
    Set %code.CodeMode = "expression"
    Do %code.WriteLine("%val * 2")
}

生成器方法和子类

本节讨论在其定义类的子类中特定于生成器方法的主题。
当然,有必要在编译超类之后编译所有子类。

子类中的方法重新生成

当对定义生成器方法的类进行子类化时,Caché使用与本章前面介绍的编译规则相同的编译规则。但是,如果生成的代码看起来与超类生成的代码相同,则Caché不会在子类中重新编译方法。此逻辑不考虑两个类的包含文件是否相同。如果方法使用包含文件中定义的宏,并且子类使用其他包含文件,则Caché不会在子类中重新编译该方法。但是,可以强制在每个类中重新编译generator方法。为此,请为该方法指定方法关键字ForceGenerate。在某些情况下,可能需要此关键字。

在父类中调用方法

需要一个子类来使用为父类生成的方法,而不是本地生成的方法,请在子类中执行以下操作:定义generator方法,使其仅返回$$$ OK,如以下示例所示:

ClassMethod Demo1() [ CodeMode = objectgenerator ]
{
    quit $$$OK
}

删除生成的方法

可以从子类中删除生成的方法,以使其无法在该类中调用。为此,在父类中定义generator方法时,请包括检查当前类名称并仅在所需方案中生成代码的逻辑。例如:

ClassMethod Demo3() [ CodeMode = objectgenerator ]
{
    if %class.Name="RemovingMethod.ClassA" {
        Do %code.WriteLine(" Write !,""Hello from class: " _ %class.Name _ """")
    }
    quit $$$OK
}

如果尝试在任何子类中调用此方法,则会收到错误。

/// d ##class(PHA.OP.MOB.Test).TestBasic()
ClassMethod TestBasic() [ CodeMode = objectgenerator ]
{
    s className=%code.WriteLine(" Write """ _ %class.Name _"aaaa"_ """")
    w className,!
    s methodName=%code.WriteLine(" Write """ _ %method.Name _"aaab" _ """")
    w methodName,!
    
    w "aaaa",!
    Do %code.WriteLine(" Write """ _ %class.Name _"aaac" _ """")
    Do %code.WriteLine(" Write """ _ %method.Name  _"aaad"_ """")

    Do %code.WriteLine(" Quit")
   
    Quit $$$OK
}

请注意,此逻辑与上一节中所述的逻辑有细微不同。如果给定类中的生成器方法存在但实现为空,则使用超类的方法(如果有)代替。但是,如果给定类中的生成器方法没有为给定子类生成代码,则该方法在该子类中不存在,因此无法调用。

注意:编译时:不能用SQLCODE

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,681评论 6 517
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,205评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 169,421评论 0 362
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,114评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,116评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,713评论 1 312
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,170评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,116评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,651评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,714评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,865评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,527评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,211评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,699评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,814评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,299评论 3 379
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,870评论 2 361

推荐阅读更多精彩内容