第二十一章 Caché 使用和覆盖属性方法
简介

本章介绍属性方法,这是当使用OREF处理对象的属性时Caché使用的实际方法。

属性方法简介

属性具有许多与之自动关联的方法。这些方法不是通过标准继承继承的。而是,它们使用特殊的属性行为机制为每个属性生成一系列方法。

每个属性都从两个地方继承一组方法:

  • %Property类,它提供某些内置行为,例如Get(),Set()和验证代码。

  • 属性使用的数据类型类。这些方法很多都是方法生成器。

第二十一章 Caché 使用和覆盖属性方法_方法名

属性类是系统类。不能指定或修改属性行为。

例如,如果我们定义一个具有三个属性的Person类:

Class MyApp.Person Extends %Persistent
{
Property Name As %String;
Property Age As %Integer;
Property DOB As %Date;
}

编译的Person类具有为其每个属性自动生成的一组方法。这些方法继承自系统的Property类以及与该属性关联的数据类型类。这些生成的方法的名称是属性名称,该属性名称与继承的类中方法的名称连接在一起。

例如,与DOB属性关联的一些方法是:

/// d ##class(PHA.OP.MOB.Test).TestIsValid()
ClassMethod TestIsValid()
{
	s person=##class(User.MyClass).%OpenId("1||2")
	w person.DOB,!
 	Set x = person.DOBIsValid(person.DOB)
	w x,!
 	Write person.DOBLogicalToDisplay(person.DOB),!
}

其中IsValid()是属性类的方法,而LogicalToDisplay()是%Date数据类型类的方法。

DHC-APP>d ##class(PHA.OP.MOB.Test).TestIsValid()
65442
1
03/04/2020
文字属性的属性访问器

引用对象属性的Caché点语法是一组用于检索和设置值的访问器方法的接口。对于每个未计算的属性,只要代码引用oref.Prop(其中oref是一个对象,而Prop是一个属性),它就会被执行,就像调用系统提供的PropGet()或PropSet()方法一样。例如:

 Set person.DOB = x

就像调用以下方法一样:

Do person.DOBSet(x)
 Write person.Name

等同于

 Write person.NameGet()

在大多数情况下,看不到实际的PropGet()和PropSet()方法。直接在Caché虚拟机中实现对简单属性的访问,以实现最佳性能。但是,可以为特定属性提供PropGet()和PropSet()方法,只要该属性不是对象类型或多维的。如果定义这些方法,则Caché会在运行时自动调用它们。以下各节描述如何定义这些访问器方法。在自定义方法中,可以执行应用程序所需的任何特殊处理。方法,Caché在运行时自动调用它们。

请注意,Studio中“New Property Wizard”的最后一个屏幕提供了用于创建自定义Get()方法,Set()或两者的选项。如果使用这些选项,则Studio会使用适当的签名定义存根方法。
第二十一章 Caché 使用和覆盖属性方法_cache_02

Property aaa As %String;

Method aaaGet() As %String [ ServerOnly = 1 ]
{
	Quit ""
}

Method aaaSet(Arg As %String) As %Status [ ServerOnly = 1 ]
{
	Quit $$$OK
}

对象值属性的属性访问器

对于每个引用属性,都有SetObject()(使用OID值)和SetObjectId()(使用ID值)方法。
例如,要将特定的保存的Person对象分配为Car对象的所有者,请使用以下代码:

 Do car.OwnerSetObjectId(PersonId)

car是Car对象的OREF,PersonId是已保存的Person对象的ID。

还有GetObject()和GetObjectId()方法,它们分别获取与引用属性关联的OID或ID。

综上所述,各种方法是:

  • GetObject() 获取与属性关联的OID。对于名为prop的属性,方法名称为propGetObject()。
  • GetObjectId() 获取与属性关联的ID。对于名为prop的属性,方法名称为propGetObjectId()。
  • SetObject() 设置与属性关联的OID。对于名为prop的属性,方法名称为propSetObject()。
  • SetObjectId() 设置与属性关联的ID。对于名为prop的属性,方法名称为propSetObjectId()。
重写属性获取器方法

要覆盖属性的setter方法,请修改包含该属性的类并添加方法,如下所示:

  • 它必须具有名称PropertyNameSet,其中PropertyName是相应属性的名称。
  • 它采用一个参数,其中包含属性的值。

具体来说,这是设置属性时在SET命令中指定的值。

  • 它必须返回%Status值。
  • 若要设置此属性的值,此方法必须设置变量i%PropertyName。该名称区分大小写。

重要说明:在给定属性的此setter方法中,请勿使用…PropertyName语法引用该属性的值。如果尝试这样做,则结果是由一系列递归引用引起的错误。但是,可以使用…PropertyName引用其他属性

变量i%PropertyName是一个实例变量

请注意,不支持为对象类型的属性或多维属性覆盖访问器方法。同样,由于方法名称的最大长度为220个字符,因此无法为长度为218、219或220个字符的属性创建访问器方法。

例如,假设MyProp的类型为%String。我们可以定义以下setter方法:

Method MyPropSet(value as %String) As %Status
{
    if i%MyProp="abc" {
        set i%MyProp="corrected value"
    }
    quit $$$OK
}

下面显示了另一个示例,它是名为DefaultXmlns的属性的设置方法,该属性的类型为%String:

Method DefaultXmlnsSet(value As %String) As %Status
{
    set i%DefaultXmlns = value
    If ..Namespaces'="" Set ..Namespaces.DefaultXmlns=value
    quit $$$OK
}

请注意,此示例使用…PropertyName语法引用了同一对象的Namespaces属性。此用法不是错误,因为它不会引起任何递归。

Property name As %String;

Method nameGet() As %String [ ServerOnly = 1 ]
{
	
	Quit i%name
}

Method nameSet(Arg As %String) As %Status [ ServerOnly = 1 ]
{
	set i%name = Arg
	if i%name ="yaoxin" {
        set i%name="yaoxin is a bad guy"
    }
	Quit $$$OK
}

/// d ##class(PHA.OP.MOB.Test).TestGetSet()
ClassMethod TestGetSet()
{
	s three=##class(PHA.OP.MOB.TestThree).%New()
	d three.nameSet("yaoxin")
	w "three.nameGet(): "_three.nameGet(),!
	w "three.name: "_three.name,!
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestGetSet()
three.nameGet(): yaoxin is a bad guy
three.name: yaoxin is a bad guy
/// d ##class(PHA.OP.MOB.Test).TestGetSet()
ClassMethod TestGetSet()
{
	s three=##class(PHA.OP.MOB.TestThree).%New()
	d three.nameSet("yaoxin")
	s three.name="yaoxin1"
	w "three.nameGet(): "_three.nameGet(),!
	w "three.name: "_three.name,!
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestGetSet()
three.nameGet(): yaoxin1
three.name: yaoxin1
使用自定义访问器方法定义对象值属性

如前所述,不支持为对象类型的属性覆盖访问器方法。如果需要定义一个保存对象值的属性,并且需要定义自定义访问器方法,请使用%CacheString类型定义该属性。 这不是对象类,而是泛型类,并且允许重写此属性的访问器方法。 使用该属性时,请将其设置为等于所需类的实例。

例如,以下类包含属性Zip,其形式类型为%CacheString。属性描述指示该属性旨在作为Sample.USZipCode的实例。该类还定义ZipGet()和ZipSet()属性方法。

Class PHA.OP.MOB.TestThree Extends %RegisteredObject
{
/// Timestamp for viewing Zip
Property LastTimeZipViewed As %TimeStamp;

/// Timestamp for changing Zip
Property LastTimeZipChanged As %TimeStamp;

/// When setting this property, set it equal to instance of Sample.USZipCode.
/// The type is %CacheString rather than Sample.USZipCode, so that it's possible 
/// to override ZipGet() and ZipSet().
Property Zip As %CacheString;

Method ZipGet() As %CacheString [ ServerOnly = 1 ]
{
	w "1",!
    // get id, swizzle referenced object
    set id = i%Zip
    if (id '= "") {
		w "2",!
        set zip = ##class(Sample.USZipCode).%OpenId(id)
        set ..LastTimeZipViewed = $zdt($zts,3)
    }
    else {
		w "3",!
        set zip = ""
    }
    return zip
}

Method ZipSet(zip As %CacheString) As %Status [ ServerOnly = 1 ]
{
	w "4",!
    // set i% for new zip
    if ($isobject(zip) && zip.%IsA("Sample.USZipCode")) {
		w "5",!
        set id = zip.%Id()
        set i%Zip = id
        set ..LastTimeZipChanged = $zdt($zts,3)
    }
    else {
		w "6",!
        set i%Zip = ""
    }
    quit $$$OK
}
}
/// d ##class(PHA.OP.MOB.Test).TestSelfGetSet()
ClassMethod TestSelfGetSet()
{
	set demo=##class(PHA.OP.MOB.TestThree).%New()
	write demo.LastTimeZipChanged
	set zip=##class(Sample.USZipCode).%OpenId(1)
	set demo.Zip=zip
	w demo.LastTimeZipChanged
}

/// d ##class(PHA.OP.MOB.Test).SaveZIp()
ClassMethod SaveZIp()
{
	s zip = ##class(Sample.USZipCode).%New()
	s zip.ZipCode=1
	s zip.State="Y"
	s zip.City ="天津"
	s zip.ZipCode=1
	s zip.Longitude=2
	s zip.Latitude=3
	s sc=zip.%Save()
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestSelfGetSet()
4
5
03/05/2020 01:22:06

注意:

Method nameGet() As %String [ ServerOnly = 1 ]
{
	
	Quit ..name
}

如果你在属性方法里使用…语法,则会报错! 因为…语法其实就是递归nameGet,陷入死循环

 
DHC-APP> d ##class(PHA.OP.MOB.Test).TestGetSet()
 
<FRAMESTACK>

改成 i%name 即可