方法是由类定义的可执行元素。Caché 支持两种类型的方法:实例方法和类方法。从类的特定实例调用实例方法,并通常执行与该实例相关的一些操作。类方法是无需引用任何对象实例即可调用的方法;这些在其他语言中称为静态方法。
方法通常指的是实例方法。更具体的类方法用于引用类方法。
因为没有对象的实例就不能执行实例方法,所以实例方法只有在对象类中定义时才有用。相反,可以在任何类中定义类方法。
要将类方法添加到类中,请在类定义中添加如下元素:
ClassMethod MethodName(Arguments) as Classname [ Keywords]
{
//method implementation
}
类可以是数据类型类、对象类,或者(不太常见的)无类型类。类名可以是完整的类名,也可以是简短的类名。(如果用IMPORT 关键字可以使用简称)
要将实例方法添加到类中,请使用与方法相同的语法,而不是使用ClassMethod:
Method MethodName(arguments) as Classname [ Keywords]
{
//method implementation
}
实例方法只与对象类相关。
方法可以接受任意数量的参数。方法定义必须指定它所接受的参数。还可以为每个参数指定类型和默认值。(在这个上下文中,type指的是任何类型的类,而不是特定的数据类型类。)
通用类方法定义:
ClassMethod MethodName(Arguments) as Classname [ Keywords]
{
//method implementation
}
这里参数可以有以下形式
argname1 as type1 = value1, argname2 as type2 = value2, argname3 as type3 = value3, [and so on]
通常,类型是数据类型类或对象类。
类名可以是完整的类名,也可以是简短的类名。(IMPORT关键字导入类可使用简名)
可以忽略这部分语法。如果你这样做了,也可以省略as部分。
每个值可以是一个字符串(“abc”或42),也可以是一个用大括号括起来的Caché ObjectScript表达式。例如:
ClassMethod Test(flag As %Integer = 0)
{
//method implementation
}
或
ClassMethod Test(time As %Integer = {$horolog} )
{
//method implementation
}
可以忽略这部分语法。如果这样做了,也可以省略等号(=)。
例如,下面是一个带有三个参数的Calculate()方法:
ClassMethod Calculate(count As %Integer, name, state As %String = "CA")
{
// ...
}
其中count和state分别声明为%Integer和%String。默认情况下,参数是%String数据类型,因此未指定类型的参数是%String。以上示例中的name就是这种情况。
方法定义还表明,对于可能使用该方法的程序员,如何传递每个参数。参数可以通过值或引用传递(ref)
通过引用传递特定的参数可能明智,也可能不明智。细节取决于方法实现。因此,在定义方法时,应该使用方法签名向其他程序员表明如何使用每个参数。
要指示应通过引用传递参数,请在方法签名中参数名称之前包含ByRef修饰符。
下面的示例使用了ByRef的两个参数:
/// Swap value of two integers
Method Swap(ByRef x As %Integer, ByRef y As %Integer)
{
Set temp = x
Set x = y
Set y = temp
}
类似地,为了指示参数应该通过引用传递,并且不希望传入值,在方法签名中在参数名之前包含Output修饰符。
例如:
Method CreateObject(Output newobj As MyApp.MyClass) As %Status
{
Set newobj = ##class(MyApp.MyClass).%New()
Quit $$$OK
}
可以定义一个接受可变数量参数的方法。为此,包括…在最后一个参数的名称之后,如下面的示例所示。这个例子还展示了如何使用这个特性。
ClassMethod MultiArg(Arg1... As %String)
{
Write "Invocation has ",
$GET(Arg1, 0),
" element",
$SELECT(($GET(Arg1, 0)=1):"", 1:"s"),
!
For i = 1 : 1 : $GET(Arg1, 0)
{
Write:($DATA(Arg1(i))>0) "Argument[", i , "]:",
?15, $GET(Arg1(i), ""), !
}
Quit
}
下面的终端会话展示了这个方法的行为:
SAMPLES>do ##class(VarNumArg.Demo).MultiArg("scooby","shaggy","velma","daphne","fred")
Invocation has 5 elements
Argument[1]: scooby
Argument[2]: shaggy
Argument[3]: velma
Argument[4]: daphne
Argument[5]: fred
注意:$GET(Arg1, 0) 如果为空,第二个参数是代表默认值
要定义一个方法以便它返回一个值,请在该方法中使用下列任一项(如果在Caché ObjectScript中实现该方法):
Return returnvalue
或
Quit returnvalue
其中returnvalue是方法返回的合适值。这应该与方法声明的返回类型一致。如果返回类型是数据类型类,则该方法应该返回一个文字值。如果返回类型是一个对象类,那么该方法应该返回该类的一个实例(特别是OREF)。
例如
ClassMethod Square(input As %Numeric) As %Numeric
{
Set returnvalue = input * input
Return returnvalue
}
返回对象
/// w ##class(PHA.OP.MOB.Test).FindPerson(2)
ClassMethod FindPerson(id As %String) As PHA.OP.MOB.Android
{
Set person = ##class(User.DHCPHARWIN).%OpenId(id)
Return person
}
DHC-APP>w ##class(PHA.OP.MOB.Test).FindPerson(2)
DHC-APP 2e1>w person
[email protected]
DHC-APP 2e1>zw person
person=
注意:Return与Quit用法相同
在创建方法时,可以选择实现语言。实际上,在一个类中,可以用不同的语言实现多个方法。所有方法互操作,而不考虑实现语言。
默认情况下,方法使用其所属类的language关键字指定的语言。对于这个关键字,默认是Caché (Caché ObjectScript)。其他选项有basic (Cache basic)、java (java)、javascript (javascript)、mvbasic (mvbasic)和tsql (tsql)。
注意:Caché ObjectScript Caché脚本 即M语言
可以通过为特定的方法设置语言关键字来覆盖它:
Class MyApp.Test {
/// A Basic method
Method TestB() As %Integer [ Language = basic]
{
'This is Basic
Print "This is a test"
Return 1
}
/// A Cache ObjectScript method
Method TestC() As %Integer [ Language = Caché]
{
// This is Cache ObjectScript
Write "This is a test"
Quit 1
}
}
Caché 支持四种类型的方法,类编译器处理不同:
代码方法的实现只是几行代码。这是最典型的方法类型,也是默认的。
方法实现可以包含对实现语言有效的任何代码。
注意:Caché提供了一组系统定义的方法来执行简单、常见的任务。如果用户定义的方法执行这些任务之一,则编译器不会为其生成任何可执行代码。相反,它将用户定义的方法与系统定义的方法关联起来,因此调用用户定义的方法将导致对系统定义的方法的调用,从而带来相关的性能好处。而且,调试器不会单步执行这样一个系统定义的方法。
表达式方法是一种方法,在某些情况下,类编译器可以使用指定表达式的直接内联替换来替换它。表达式方法通常用于需要快速执行速度的简单方法(如数据类型类中的方法)。
例如,可以将Dog类的Speak()方法从前面的示例转换为表达式方法:
/// w ##class(PHA.OP.MOB.Test).Speak()
ClassMethod Speak() As %String [ CodeMode = expression ]
{
"Woof, Woof"
}
DHC-APP>w ##class(PHA.OP.MOB.Test).Speak()
Woof, Woof
假设dog是指dog对象,则该方法可采用如下方法:
Write dog.Speak()
这可能导致生成以下代码:
Write "Woof, Woof"
为表达式方法的所有形式变量提供默认值是一个好主意。这可以防止由于运行时缺少实际变量而导致的潜在内联替换问题。
注意:Caché不允许在表达式方法中使用宏或引用调用参数。
调用方法是在现有Caché 程序周围创建方法包装器的一种特殊机制。在将遗留代码迁移到基于对象的应用程序时,这通常很有用。
Method Call() [ CodeMode = call ]
{
Tag^Routine
}
其中“Tag^例程”指定了程序例程中的一个标签名。
注意:CodeMode = call没有这个关键字 程序报错
Call()
s dog="小狗"
w dog,!
q dog
/// w ##class(PHA.OP.MOB.Test).Call()
ClassMethod Call() [ CodeMode = call ]
{
Call^PHA.MOB.TEST
}
DHC-APP>w ##class(PHA.OP.MOB.Test).Call()
小狗
注意:routine是宏程序 Tag^Routine
方法生成器实际上是一个由类编译器在类编译期间调用的小程序。它的输出是该方法的实际运行时实现。方法生成器提供了一种继承方法的方法,可以生成高性能的专门代码,这些代码根据继承类或属性的需要进行定制。在Caché库中,数据类型和存储类广泛使用方法生成器。
/// d ##class(PHA.OP.MOB.Test).MyMethod()
/// PHA.OP.MOB.Test
ClassMethod MyMethod() [ CodeMode = objectgenerator ]
{
Do %code.WriteLine(" Write """ _ %class.Name _ """")
Do %code.WriteLine(" Quit")
Quit $$$OK
}
DHC-APP 2d1> d ##class(PHA.OP.MOB.Test).MyMethod()
PHA.OP.MOB.Test
注意:必须有objectgenerator关键字 才可以用%class,%code 关键字
可以定义一个类方法(但不是实例方法),以便它也可以作为SQL存储过程使用。为此,在方法定义中包含SqlProc关键字。
该过程的默认名称为CLASSNAME_METHODNAME。要指定一个不同的名称,请指定SqlName关键字。
本节讨论如何在Caché ObjectScript中调用类方法。本节适用于所有的类。注意,下一章将讨论实例方法,因为它们只适用于对象类。
##class(Package.Class).Method(Args)
这个表达式调用给定的类方法并获得它的返回值(如果有的话)。可以将此表达式与DO和SET等命令一起使用,也可以将其用作另一个表达式的一部分。以下是一些变化:
do ##class(Package.Class).Method(Args)
set myval= ##class(Package.Class).Method(Args)
write ##class(Package.Class).Method(Args)
set newval=##class(Package.Class).Method(Args)_##class(Package2.Class2).Method2(Args)
可以忽略这个包。如果这样做,类编译器将确定要使用的正确包名 (IMPORT)
..MethodName(args)
可以使用DO命令来使用这个表达式。如果方法返回一个值,可以使用SET,或者将其用作另一个表达式的一部分。以下是一些变化:
do ..MethodName()
set value=..MethodName(args)
注意:不能在类方法中使用此语法来引用属性或实例方法,因为这些引用需要实例上下文。
$CLASSMETHOD(classname, methodname, Arg1, Arg2, Arg3, ... )
/// d ##class(PHA.OP.MOB.Test).CLASSMETHODTEST()
/// 输出 Woof, Woof
ClassMethod CLASSMETHODTEST()
{
set cls="PHA.OP.MOB.Test"
set clsmeth="Speak"
S RET= $CLASSMETHOD(cls,clsmeth)
q RET
}
如果给定的方法不存在,或者它是一个实例方法,Caché生成的<方法不存在>错误。如果给定的方法是私有的,缓存将生成<私有方法> 错误。
DHC-APP>w ##class(PHA.OP.MOB.API).Login("demo#1")
{"retVal":"success","retObject":{"userCode":"demo","userName":"Demo Group","userID":1,"rows":[{"locID":"312","locDesc":"煎药室","groupID":"176","groupDesc":"煎 药室"}]}}
DHC-APP>w ##class(PHA.OP.MOB.Business).Login("yf01#1")
W ##CLASS(PHA.OP.MOB.Business).Login("yf01#1")
^
将参数传递给方法的默认方式是按值传递。在这种技术中,只需将参数作为变量、文字值或其他表达式包含在方法调用中,如前面的示例所示。
也可以通过引用传递参数。
它的工作原理如下:系统有一个包含每个局部变量值的内存位置。变量的名称充当内存位置的地址。将局部变量传递给方法时,将通过值传递变量。这意味着系统复制了该值,因此原始值不会受到影响。你可以传递内存地址;这种技术称为引用调用。它也是传递多维数组作为参数的唯一方法。
在Caché ObjectScript中,若要通过引用传递参数,请在该参数之前加上"."。例如:
set MyArg(1)="value A"
set MyArg(2)="value B"
set status=##class(MyPackage.MyClass).MyMethod(.MyArg)
在本例中,通过引用传递一个值(一个多维数组),以便该方法可以接收该值。在其他情况下,通过引用传递参数是很有用的,这样就可以在运行方法之后使用它的值。例如:
set status=##class(MyPackage.MyClass).GetList(.list)
//use the list variable in subsequent logic
在其他情况下,可以为变量指定一个值,调用修改它的方法(并通过引用返回它),然后使用更改后的值。
/// d ##class(PHA.OP.MOB.Test).TestPassingArguments()
ClassMethod TestPassingArguments()
{
set MyArg(1)="value A"
set MyArg(2)="value B"
S RET=..TestPassingArguments1(.MyArg)
zw MyArg
q RET
}
ClassMethod TestPassingArguments1(ByRef MyArg)
{
set MyArg(3)="value C"
set MyArg(4)="value D"
b
q "TestPassingArguments1"
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestPassingArguments()
b
^
zTestPassingArguments1+3^PHA.OP.MOB.Test.1
DHC-APP 3e1>zw MyArg
MyArg(1)="value A"
MyArg(2)="value B"
MyArg(3)="value C"
MyArg(4)="value D"
DHC-APP 3e1>g
MyArg(1)="value A"
MyArg(2)="value B"
MyArg(3)="value C"
MyArg(4)="value D"
DHC-APP>
要将一个类的方法转换为另一个类的方法,语法如下(在cache ObjectScript中):
Do ##class(Package.Class1)Class2Instance.Method(Args)
Set localname = ##class(Package.Class1)Class2Instance.Method(Args)
可以同时转换类方法和实例方法。
例如,假设有两个类,MyClass.Up和MyClass.Down,,两个都有Go()方法。
MyClass.Up,这个方法如下
Method Go()
{
Write "Go up.",!
}
MyClass.Down,,这个方法如下
Method Go()
{
Write "Go down.",!
}
然后可以创建MyClass.Up的一个实例。并使用它来调用MyClass.Down。方法:
>Set LocalInstance = ##class(MyClass.Up).%New()
>Do ##class(MyClass.Down)LocalInstance.Go()
Go down.
在表达式中使用##类也是有效的
Write ##class(Class).Method(args)*2
没有设置一个等于返回值的变量。
更通用的方法是使用 m e t h o d 和 method和 method和CLASSMETHOD函数,它们分别是实例方法和类方法。这些在本章前面的章节中有描述。
Class PHA.OP.MOB.TestTwo Extends PHA.OP.MOB.Test
{
Method Go()
{
Write "Go down.",!
}
}
Class PHA.OP.MOB.Test Extends %RegisteredObject
{
Method Go()
{
Write "Go up.",!
}
/// d ##class(PHA.OP.MOB.Test).CastingMethod()
ClassMethod CastingMethod()
{
Set LocalInstance = ##class(PHA.OP.MOB.TestTwo).%New()
Do ##class(PHA.OP.MOB.Test)LocalInstance.Go()
}
}
DHC-APP>d ##class(PHA.OP.MOB.TestThree).CastingMethod()
Do ##class(PHA.OP.MOB.Test)LocalInstance.Go()
^
zCastingMethod+2^PHA.OP.MOB.TestThree.1 *super-class 'PHA.OP.MOB.Test' is not in sub-class 'PHA.OP.MOB.TestTwo' hierarchy
DHC-APP 2d1>k
DHC-APP 2d1>q
DHC-APP>d ##cla
注意必须是主子关系的类!
类从父类或父类继承方法(类和实例方法)。除了标记为Final的方法外,可以通过在该类中提供定义来覆盖这些定义。
/// d ##class(PHA.OP.MOB.Test).CLASSMETHODTEST()
/// 输出 Woof, Woof
ClassMethod CLASSMETHODTEST() [ Final ]
{
set cls="PHA.OP.MOB.Test"
set clsmeth="Speak"
S RET= $CLASSMETHOD(cls,clsmeth)
q RET
}
子类重写父类FINAL方法时会报错
Studio
---------------------------
错误 #5272: 无法更改最终'Method': 'CLASSMETHODTEST'
> 错误 #5030: 在编译类PHA.OP.MOB.TestTwo时出错
---------------------------
确定
---------------------------
如果这样做,请注意以下规则:
注意,如果一个参数没有指定类型,编译器会将该参数视为%String。因此,如果超类方法中的参数没有类型,则子类方法的相应参数可以是%String,可以是%String的子类,也可以没有类型。
如果方法签名在这方面不一致,其他开发人员就很难知道如何正确使用方法。但是,请注意,编译器不会发出错误。
如果的方法实现需要调用与父类中定义的同名方法,可以使用语法##super(),这将在小节中讨论。
在一个方法中,使用下面的表达式来调用在最近的父类中定义的同名方法:
##super()
可以用DO命令来使用这个表达式。如果方法返回一个值,可以使用SET,或者将其用作另一个表达式的一部分。以下是一些变化:
do ##super()
set returnvalue=##super()_"additional string"
注意:##super不区分大小写。还要注意,与本章中的其他特性不同,##super()可以在Basic 本方法中使用,也可以在ObjectScript方法中使用。
如果定义了一个方法,该方法应该调用超类的现有方法,然后执行一些附加的步骤,例如修改它的返回值,那么这是非常有用的。
super也适用于接受参数的方法。如果子类方法没有为参数指定默认值,请确保该方法通过引用父类传递参数。
例如,假设父类(MyClass.Up.SelfAdd())中的方法的代码是:
ClassMethod SelfAdd(Arg As %Integer)
{
Write Arg,!
Write Arg + Arg
}
则其输出为:
>Do ##Class(MyClass.Up).SelfAdd(2)
2
4
>
子类中的方法(MyClass.Down.SelfAdd())使用##super并通过引用传递参数:
ClassMethod SelfAdd(Arg1 As %Integer)
{
Do ##super(.Arg1)
Write !
Write Arg1 + Arg1 + Arg1
}
则其输出为:
>Do ##Class(MyClass.Down).SelfAdd(2)
2
4
6
>
在MyClass.Down.SelfAdd()中,注意参数名前面的句点"."。如果我们忽略".",并且在不提供参数的情况下调用方法,我们将收到一个 error。
DHC-APP> d ##class(PHA.OP.MOB.TestTwo).SelfAdd(3)
Write Arg,!
^
zSelfAdd+1^PHA.OP.MOB.Test.1 *Arg
注意 父类如果有参数,必须指定参数 否则报错。
##super只影响当前方法调用。如果该方法进行任何其他调用,则这些调用相对于当前对象或类,而不是父类。
例如,假设MyClass.Up有MyName()和CallMyName()方法:
Class MyClass.Up Extends %Persistent
{
ClassMethod CallMyName()
{
Do ..MyName()
}
ClassMethod MyName()
{
Write "Called from MyClass.Up",!
}
}
MyClass.Down 重写这些方法如下:
Class MyClass.Down Extends MyClass.Up
{
ClassMethod CallMyName()
{
Do ##super()
}
ClassMethod MyName()
{
Write "Called from MyClass.Down",!
}
}
然后调用CallMyName()方法会得到以下结果:
USER>d ##class(MyClass.Up).CallMyName()
Called from MyClass.Up
USER>d ##class(MyClass.Down).CallMyName()
Called from MyClass.Down
MyClass.Down.CallMyName() 和MyClass.Up.CallMyName() 输入不同是因为,因为它的CallMyName()方法包含了##super,所以调用了MyClass.Up.CallMyName()方法,然后调用未强制转换的MyClass.Down.MyName()方法。
在某些情况下,可能会发现有必要向父类中的方法添加新参数,从而产生比子类中的方法中定义的参数更多的参数。子类仍然会进行编译,因为(为了方便起见)编译器会将添加的参数附加到子类中的方法中。在大多数情况下,仍然应该检查扩展该方法的所有子类,编辑签名以说明附加的参数,并决定是否也要编辑代码。即使你不想编辑签名或代码,你仍然必须考虑两点:
ClassMethod SelfAdd(Arg As %Integer, ARG2 As %Integer = 2)
{
Write Arg,!
Write Arg + Arg+ARG2
}
/// d ##class(PHA.OP.MOB.TestTwo).SelfAdd(1)
ClassMethod SelfAdd(Arg1 As %Integer)
{
Do ##super(Arg1)
Write !
b
Write Arg1 + Arg1 + Arg1
}
注意:父类如果有修改参数,一定要添加默认值