目录
%RegisteredObject 类是 Caché 中的基本对象 API 。对象类是继承自 %RegisteredObject
的任何类。使用对象类,可以执行以下操作:
- 创建类的实例。这些实例称为对象。
- 设置这些对象的属性。
- 调用这些对象的方法(实例方法)。
类 %Persistent
和 %SerialObject
是 %RegisteredObject
的子类.
一、OREF 基础知识
创建对象时,系统会创建一个内存中结构,该结构包含有关该对象的信息,并创建一个 OREF
(对象引用),它是指向该结构的指针。
对象类提供了几种创建 OREF 的方法。使用任何对象类时,都会广泛使用 OREF。在指定对象的属性值、访问对象的属性值以及调用对象的实例方法时,可以使用它们。请看以下示例:
SAMPLES>set person=##class(Sample.Person).%New()
SAMPLES>set person.Name="Carter,Jacob N."
SAMPLES>do person.PrintPerson()
Name: Carter,Jacob N.
- 在第一步中,我们调用
Sample.Person
类的%New()
方法。它创建一个对象并返回一个指向该对象的OREF
。我们将变量person
设置为等于此OREF
。 - 在下一步中,我们设置对象的Name属性。
- 在第三步中,我们调用对象的
PrintPerson()
实例方法。
注意:OREF
是瞬态的;该值仅在对象位于内存中时存在,并且不能保证在不同的调用中保持不变。
1.1 INVALID OREF 错误
在简单表达式中,如果尝试设置属性、访问属性或调用非 OREF
变量的实例方法,则会收到<INVALID OREF>
错误。例如:
DHC-APP>w p.name
W p.name
^
<INVALID OREF>
1.2 判断是否为OREF
Caché
提供了一个函数 $ISOBJECT
,您可以使用它来测试给定变量是否具有 OREF
。如果变量包含 OREF
,则此函数返回 1
,否则返回 0
。
if ($ISOBJECT(P)) w p.name
1.3 OREF、作用域和内存
任何给定的 OREF
都是指向内存中对象的指针,其他 OREF 也可能指向该对象。也就是说,OREF
(变量)与内存中对象不同(尽管在实践中,术语 OREF 和对象经常互换使用)。
请注意以下几点:
OREF
仅在特定名称空间内有效;因此,如果存在现有的OREF并且当前命名空间发生了更改,则来自先前命名空间的任何OREF都不再有效。如果尝试使用其他命名空间中的OREF
,可能不会立即出现错误,但结果不能被视为有效或可用,并可能在当前命名空间中造成灾难性的结果。Caché
自动管理内存中的结构,如下所示。对于每个内存中的对象,Caché都会维护一个引用计数——对该对象的引用数。每当将变量或对象属性设置为引用对象时,其引用计数都会自动递增。当一个变量停止引用一个对象时(如果它超出范围、被终止或被设置为新值),该对象的引用计数就会递减。当此计数为0时,对象将自动销毁(从内存中删除),并调用其%OnClose()
方法(如果存在)。
例如,请考虑以下方法:
Method Test()
{
Set person = ##class(Sample.Person).%OpenId(1)
Set person = ##class(Sample.Person).%OpenId(2)
}
- 此方法创建
Sample.Person
的实例。并将对它的引用放置到变量person
中。 - 然后它将创建另一个实例,并将
person
的值替换为对它的引用。此时,第一个对象不再被引用并被销毁。 - 在方法的最后,
person
超出了作用域范围,第二个对象被销毁。
1.4 删除 OREF
如果需要,要删除 OREF,请使用 KILL 命令:
kill OREF
其中 OREF
是包含 OREF 的变量。此命令删除变量。如果没有对该对象的进一步引用,则此命令还会从内存中删除该对象。
1.5 OREF、SET 命令和系统函数
对于某些系统函数(例如,$Piece
、$Extract
和 $List
),Caché 支持可用于修改现有值的替代语法。此语法将函数与 SET
命令组合在一起,如下所示:
SET function_expression = value
其中 function_expression
是对系统函数的调用,带有参数,value
是值。例如,以下语句将颜色列表字符串的第一部分设置为等于“Magenta”:
SET $PIECE(colorlist,",",1)="Magenta"
USER>s a="1,2,3"
USER>s $p(a,",",1)=4
USER>w a
4,2,3
二、创建新对象
若要创建给定对象类的新实例,请使用该类的类方法 %New()
。此方法创建一个对象并返回一个 OREF
。示例如下:
Set person = ##class(MyApp.Person).%New()
%New()
方法接受一个参数,默认情况下会忽略该参数。如果存在,则此参数将传递给类的 %OnNew()
回调方法(如果已定义)。如果定义了 %OnNew()
,则它可以使用该参数以某种方式初始化新创建的对象。有关详细信息,请参阅“实现回调方法”。
如果有复杂的要求,这些要求会影响如何创建给定类的新对象,则可以提供用于创建该类实例的替代方法。这样的方法将调用 %New()
,然后根据需要初始化对象的属性。这种方法有时称为工厂方法。
三、查看对象内容
WRITE
命令为 OREF
写入以下形式的输出:
n@Classname
其中 Classname
是类的名称,n
是一个整数,指示该类在内存中的特定实例。例如:
SAMPLES>write p
8@Sample.Person
如果将 ZWRITE
命令与 OREF
一起使用,则 Caché
将显示有关关联对象的详细信息:
SAMPLES>zwrite p
p=<OBJECT REFERENCE>[8@Sample.Person]
+----------------- general information ---------------
| oref value: 1
| class name: Sample.Person
| %%OID: $lb("3","Sample.Person")
| reference count: 2
+----------------- attribute values ------------------
| %Concurrency = 1 <Set>
| DOB = 33589
| Name = "Clay,George O."
| SSN = "480-57-8360"
+----------------- swizzled references ---------------
| i%FavoriteColors = "" <Set>
| r%FavoriteColors = "" <Set>
| i%Home = $lb("5845 Washington Blvd","St Louis","NM",55683) <Set>
| r%Home = "" <Set>
| i%Office = $lb("3413 Elm Place","Pueblo","WI",98532) <Set>
| r%Office = "" <Set>
| i%Spouse = ""
| r%Spouse = ""
+-----------------------------------------------------
请注意,此信息显示对象属性的类名、OID
、引用计数和当前值(在内存中)。在 swizzled
引用部分中,名称以 i%
开头的项是实例属性
。(名称以 r%
开头的项目仅供内部使用)
四、确定对象类型
给定一个对象,%RegisteredObject
类提供用于确定其继承的方法.
4.1 %Extends()
若要检查对象是否继承自特定超类,请调用其 %Extends()
方法,并将该超类的名称作为参数传递。如果此方法返回 1,则实例继承自该类。如果它返回 0,则实例不会从该类继承。例如:
SAMPLES>set person=##class(Sample.Person).%New()
SAMPLES>w person.%Extends("%RegisteredObject")
1
SAMPLES>w person.%Extends("Sample.Person")
1
SAMPLES>w person.%Extends("Sample.Employee")
0
4.2 %IsA()
若要检查对象是否具有特定类作为其主超类,请调用其 %IsA()
方法,并将该超类的名称作为参数传递。如果此方法返回 1,则该对象确实具有给定的类作为其主超类。
4.3 %ClassName()和最具体的类型类 (MSTC)
尽管对象可能是多个类的实例,但它始终具有最具体的类型类 (MSTC: the Most Specific Type Class
)。当一个对象是该类的实例而不是该类的任何子类的实例时,该类被称为该对象的最具体类型。
例如,研究生继承自学生,学生继承自人类,对于由命令创建的实例:
set MyInstance1 = ##class(MyPackage.Student).%New()
set MyInstance2 = ##class(MyPackage.GradStudent).%New()
MyInstance1
具有学生作为其 MSTC,因为它是两者的实例:人和学生,但不是研究生。MyInstance2
具有研究生作为其 MSTC,因为它是三者的实例:研究生,学生和人.
以下规则也适用于对象的 MSTC:
- 对象的 MSTC 仅基于主继承。
- 不可实例化的类永远不能是对象的 MSTC。如果对象类是抽象的,则该对象类是不可实例化的。
若要确定对象的 MSTC,请使用 %ClassName()
方法,该方法继承自 %RegisteredObject
classmethod %ClassName(fullname As %Boolean) as %String
其中 fullname
是一个布尔参数,其中 1 指定方法返回包名和类名,0(默认值)指定方法仅返回类名。例如:
write myinstance.%ClassName(1)
同样,可以使用 %PackageName()
仅获取包的名称。
五、克隆对象
若要克隆对象,请调用该对象的 %ConstructClone()
方法。此方法创建新的 OREF
。
以下终端会话演示了这一点:
SAMPLES>set person=##class(Sample.Person).%OpenId(1)
SAMPLES>set NewPerson=person.%ConstructClone()
SAMPLES>w
NewPerson=<OBJECT REFERENCE>[2@Sample.Person]
person=<OBJECT REFERENCE>[1@Sample.Person]
SAMPLES>
在这里,您可以看到 NewPerson
变量使用的 OREF
与原始 person
对象不同。NewPerson
是 person
的克隆(或者更准确地说,这些变量是指向单独但相同对象的指针)。
相比之下,请考虑以下终端会话:
SAMPLES>set person=##class(Sample.Person).%OpenId(1)
SAMPLES>set NotNew=person
SAMPLES>w
NotNew=<OBJECT REFERENCE>[1@Sample.Person]
person=<OBJECT REFERENCE>[1@Sample.Person]
请注意,此处两个变量都引用相同的 OREF
。也就是说,NotNew
不是人的克隆。
六、引用实例的属性
若要引用实例的属性,可以执行以下任一操作:
- 创建关联类的实例,并使用点语法引用该实例的属性。
- 在关联类的实例方法中,使用相对点语法,如下所示:
set value=..PropName
write ..PropName
- 若要访问属性(其中属性名称直到运行时才确定),请使用
$PROPERTY
函数。如果属性是多维的,则属性名称后面的以下参数将用作访问属性值的索引。签名是:
$PROPERTY (oref, propertyName, subscript1, subscript2, subscript3... )
其中 oref
是 OREF,则 propertyName
的计算结果为关联类中属性方法的名称。此外,subscript1
、subscript2
、subscript3
是属性的任何下标的值; 仅为多维属性指定这些属性。
七、调用实例的方法
若要调用实例的方法,可以执行以下任一操作:
- 创建关联类的实例,并使用点语法调用该实例的方法。
- 在实例方法中,若要调用该类的另一个实例方法(可以是继承的方法),请使用以下表达式:
do ..MethodName()
set value=..MethodName(args)
- 若要执行实例方法,其中方法名称直到运行时才确定,请使用
$METHOD
函数:
$METHOD(oref, methodname, Arg1, Arg2, Arg3, ... )
其中 oref
是 OREF,methodname
的计算结果为关联类中实例方法的名称,Arg1
、Arg2
、Arg3
等是该方法的任何参数。
八、从实例中获取类名
若要获取类的名称,请使用 $CLASSNAME
函数:
$CLASSNAME(oref)
其中 oref
是 OREF。
九、$this 变量(当前实例)
$this
语法提供当前实例的 OREF
句柄,例如,用于将其传递给另一个类或另一个类引用当前实例的方法的属性。当实例引用其属性或方法时,相对点语法速(..
)度更快,因此是首选。
注意:
$this
不区分大小写;因此,$this
、$This
、$THIS
或任何其他变体都具有相同的值。
Method CalcTax() As %Numeric
{
Set TaxRate = ##class(Accounting.Utils).GetTaxRate($this)
Write "The tax rate for ",..City,", ",..State," is ",TaxRate*100,"%",!
Set TaxableSubtotal = ##class(Accounting.Utils).GetTaxableSubTotal($this)
Write "The taxable subtotal for this order is $",TaxableSubtotal,!
Set Tax = TaxableSubtotal * TaxRate
Write "The tax for this order is $",Tax,!
}
- 该方法的第一行使用
##Class
语法来调用另一个方法;它使用$this
语法将当前对象传递给该方法。 - 该方法的第二行使用 相对点语法
..
来获取City
和State
属性的值。 - 第三行上的操作与第一行上的操作类似。
GetTaxRate()
方法可以使用传入实例的句柄来获取各种属性的句柄,用于获取和设置其值.
ClassMethod GetTaxRate(OrderBeingProcessed As Accounting.Order) As %Numeric
{
Set LocalCity = OrderBeingProcessed.City
Set LocalState = OrderBeingProcessed.State
// code to determine tax rate based on location and set
// the value of OrderBeingProcessed.TaxRate accordingly
Quit OrderBeingProcessed.TaxRate
}
十、i%PropertyName(实例变量)
本节介绍实例变量,除非重写属性的访问器方法,否则不需要引用这些变量;请参阅“使用和重写属性方法”一章。
创建任何类的实例时,系统都会为该类的每个非计算属性创建一个实例变量。实例变量保存属性的值。对于属性 PropName
,实例变量命名为 i%PropName
,并且此变量名称区分大小写。这些变量在类的任何实例方法中都可用。
例如,如果某个类具有属性 Name
和 DOB
,则实例变量 i%Name
和 i%DOB
在该类的任何实例方法中都可用。
在内部,Caché
还使用名称为 r%PropName
和 m%PropName
的其他实例变量,但不支持直接使用这些变量。
实例变量分配了进程专用的内存中存储。请注意,这些变量不保存在局部变量符号表中,并且不受 Kill 命令的影响。
十一、验证对象
%RegisteredObject
类提供了一种验证实例属性的方法。如果满足以下所有条件,则对象有效:
- 所有必需的属性都有值。
(若要使属性成为必需属性,请使用Required
关键字。
如果属性的类型为%Stream
,则该流不能为null
流。也就是说,如果%IsNull()
方法返回 0,则认为该属性具有值。 - 每个属性的值(如果不是
null
)对于关联的属性定义有效。
例如,如果属性的类型为%Boolean
,则值“abc”
无效,但值0
和1
有效。 - 每个文本属性的值(如果不是 null)都遵循属性定义定义的所有约束。术语约束是指对属性值应用约束的任何属性关键字。例如,MAXLEN、MAXVAL、MINVAL、VALUELIST 和 PATTERN 都是约束;请参阅“定义和使用文本属性”一章。例如,对于 %String 类型的属性,默认值为 MAXLEN,这会将属性的长度限制为不超过 50 个字符。
- (对于对象值属性)对象的所有属性都遵循上述规则。
- (对于持久性对象)该对象不违反任何 SQL 约束,例如给定字段的唯一性约束。
- 若要确定给定对象是否有效,请调用其
%ValidateObject()
方法。如果此方法返回 1,则对象有效。如果它返回错误状态,则该对象无效。示例如下:
#Include %occStatus
set person=##class(Sample.Person).%New()
set person.DOB="December 12 1990"
set status=person.%ValidateObject()
write !, "First try"
if $$$ISERR(status) {
do $system.OBJ.DisplayError(status)
} else {
write !, "Object is valid"
}
set person.Name="Ellsworth,Myra Q."
set person.SSN="000-00-0000"
set person.DOB=$zdateh("December 12 1990",5)
set status=person.%ValidateObject()
write !!, "Second try"
if $$$ISERR(status) {
do $system.OBJ.DisplayError(status)
} else {
write !, "Object is valid"
}