方法生成器是一种特定类型的方法,它生成自己的运行时代码。同样,触发器生成器是生成自己的运行时代码的触发器。
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”。触发器中可用的值与方法生成器中的值略有不同。
方法生成器在编译类时生效。方法生成器的操作非常简单。编译类定义时,类编译器将执行以下操作:
请注意,原始方法签名(参数和返回类型)以及任何方法关键字值都用于生成的方法。如果将方法生成器指定为返回类型为%Integer,则实际方法的返回类型为%Integer。
触发器生成器的细节相似。
实现方法生成器的关键是了解执行方法生成器代码的上下文。如上一节所述,类编译器在解决类继承之后但在为该类生成代码之前调用方法生成器代码。当调用方法生成器代码时,类编译器使以下变量可用于方法生成器代码:
变量 | 描述 |
---|---|
%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”。触发器生成器中提供以下变量:
变量 | 描述 |
---|---|
%code, %class, %compiledclass, %parameter | 见上一章 |
%trigger | %Dictionary.TriggerDefinition类的实例。它包含触发器的原始定义。 |
%compiled%trigger | %Dictionary.CompiledTrigger类的实例。它包含正在生成的触发器的已编译定义。 |
要定义方法生成器,请执行以下操作:
定义一个方法并将其CodeMode关键字设置为“ objectgenerator”。
在方法的主体中,编写在编译类时生成实际方法的代码。此代码使用%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
请注意以下有关方法生成器代码的内容:
它使用%code对象的WriteLine方法将代码行写入包含该方法实际实现的流。(也可以使用Write方法来写没有行尾字符的文本)。
生成的代码的每一行都有一个前导空格字符。这是必需的,因为CachéObjectScript不允许在行的第一个空格内使用命令。如果我们的方法生成器正在创建Basic或Java代码,则情况并非如此。
当生成的代码行出现在字符串中时,必须格外小心,将引号字符加倍(“”)以转义引号字符。
要查找该类的属性列表,它使用%compiledclass对象。它可以使用%class对象,但随后它只会列出正在编译的类中定义的属性;它不会列出继承的属性。
它返回状态代码$$$$ OK,表示方法生成器成功运行。此返回值与该方法的实际实现无关。
可以生成不同语言的代码。为此,设置%code对象的Language属性以指定目标语言。
默认情况下,所生成代码的语言与用于编写代码生成器方法的语言相同(由Language关键字指定)。
默认情况下,方法生成器将创建一个“代码”方法(即,所生成方法的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