文章目录
什么是例程,子例程,标签,函数,标签,过程,类方法,静态方法,实例方法,对象方法
相信有很多小伙伴在刚接触ObjectScript
编程语言(M
语言)的时候,对一些“方法”的概念感到困惑,不同的人称呼这些“方法”各有不同。这些“方法”在功能上大体调用的都差不多,但是还有一些细微的区别。称呼“方法”的方式有很多,让人感到非常困惑,到底这些“方法”的区别是什么。这篇文章就带领大家搞清楚这些“方法”的概念与定义。
了解了这些“方法”的概念之后,就可以搞清楚关键字Not ProduceBlock
的原理与使用。如果不清楚Not ProduceBlock
作用,稍不注意就可能在使用方法过程当中引起一些严重的故障,例如死循环导致临时Global
无限增长把磁盘撑爆,导致正常业务宕机。
编程语言
这里先明确一个概念,我们使用的编程语言在ISC
的官方定义为ObjectScript
对象脚本语言,实际上ObjectScript
是底层M
(Mumps
)语言拓展而来。可类比Java
与Android
,C#
与.Net
。
MUMPS
语言,简称:M
技术,全称:Massachusetts General Hospital Utility Multi-Programming System,麻省总医院多用途程序设计系统;算起来也是一种古老的语言了,与FORTRAN
和COBOL
属于同时代的语言。因为这门语言最主要是用于医疗数据库方面,所以其应用并不像SQL Server
、Oracle
等那么广泛。
例程 - Routine
例程是可调用的用户编写的代码块,它是ObjectScript
程序,传统的编写M
程序都是用的例程,因为在古早年代面向对象编程还没有出现。
一般保存的.mac
文件为例程,mac
文件名称为例程名称。int
文件也称例程。包含ObjectScript
代码的文件均可称之为例程。
注:例程指的文件,而不是某个具体具体例程中的方法。执行例程指的是执行.mac
文件。
执行例程时,使用DO
命令,如下所示:
do ^routinename
routinename
- 为例程文件名称。
创建例程文件Routine.mac
包含如下代码:
w "执行例程文件",!
编写如下方法执行例程:
ClassMethod Routine()
{
d ^Routine
}
USER>d ##class(M.Method).Routine()
执行例程文件
注:例程只能去执行,不能获取例程返回值。
可能会见到如下执行方式:
s var = $$^RoutineName
这种方式并不是真正执行了例程,而是执行了例程的第一个方法。
创建例程文件Routine1.mac
包含如下代码:
注:首行代码必须为子例程标签方法名称,如果为其他调用时会发生<PARAMETER>+1^Routine
错误。
Routine()
ret "这是例程的第一个子例程方法!"
Routine1()
ret "这是例程的第二个子例程方法!"
注:如果直接用变量接收 .mac
文件名称返回值,默认调用 .mac
文件第一个子例程方法,并且该方法不能为标签或直接用例程返回变量。**
ClassMethod Routine1()
{
s var = $$^Routine1
w var,!
}
USER>d ##class(M.Method).Routine1()
这是例程的第一个子例程方法!
子例程 - Subroutine
子例程是例程内的命名代码块。通常,子例程以 LABEL
开头,可以接受参数,以 QUIT
语句结束,虽然以QUIT
命令结束,但是注意是不返回值的。
通常我们常说的例程方法实际上指的就是子例程。
要调用子例程,请使用以下语法:
d Subroutine^Routine
Subroutine
- 子例程名称Routine
- 例程名称
子例程的形式为:
Label(arg) scopekeyword // 作用域关键字
//code 代码
quit // 注意,QUIT没有参
Label
- 标签名称, 例程方法名称。arg
- 参数,是可选的以逗号分隔的参数列表。如果没有参数,则括号是可选的。scopekeyword
- 作用域关键字,可选的作用域关键字是Public
(子例程的默认值)或Private
。code
- 执行代码。quit
- 退出命令,但是没有返回值。
在Routine.mac
中添加SubRoutine
无参数子例程方法:
SubRoutine()
w "这是例程的第一个子例程方法!"
q
编写调用方法如下:
注:如果子例程没有参数可省略括号。
ClassMethod Routine2()
{
d SubRoutine^Routine
}
USER>d ##class(M.Method).Routine2()
这是例程子例程方法!
在Routine.mac
中添加SubRoutineParams
有参数子例程方法:
SubRoutineParams(str)
w "这是例程子例程方法!" _ "参数:"_str,!
q
编写调用方法如下:
ClassMethod Routine3()
{
d SubRoutine^Routine("yx")
}
ClassMethod Routine3()
{
d SubRoutineParams^Routine("yx")
}
注:因为子例程没有返回值,所以调用子例程时一般只去执行方法,一般使用do
命令或job
命令。
函数 - Function
函数是例程中的命名代码块。通常,函数以 LABEL
开头,可以接受参数,以 QUIT
语句结束,并且有返回值。这里与子例程不同。
也可以返回值。要调用函数,有以下有效的语法形式:
Label(args) scopekeyword
zcode
QUIT optionalreturnvalue
Label
- 标签名称, 例程方法名称。arg
- 参数,是可选的以逗号分隔的参数列表。如果没有参数,则括号是可选的。scopekeyword
- 作用域关键字,可选的作用域关键字是Public
(子例程的默认值)或Private
。code
- 执行代码。quit
- 退出命令,有返回值。
要调用函数,请使用以下语法:
d Function^RoutineName(params) /* 忽略返回值 */
s x = $$Function^RoutineName(params)
Function
- 函数名称RoutineName
- 例程名称params
- 参数$$
- 如果需要获取返回值必须包含$$
语法。
在Routine.mac
中添加Fuction
无参数方法,FuctionParams
有参数子例程方法:
Fuction()
w "这是例程函数方法!",!
q "这是例程函数方法!"
FuctionParams(str)
w "这是例程函数方法!"_ "参数:"_str,!
q "这是例程函数方法!"_ "参数:"_str
忽略返回值调用方式:
ClassMethod Routine4()
{
d Fuction^Routine
d FuctionParams^Routine("yx")
}
USER>d ##class(M.Method).Routine4()
这是例程函数方法!
这是例程函数方法!参数:yx
获取返回值调用方式:
ClassMethod Routine5()
{
s ret = $$Fuction^Routine
w ret,!
s ret = $$FuctionParams^Routine("yx")
w ret,!
}
USER>d ##class(M.Method).Routine5()
这是例程函数方法!
这是例程函数方法!
这是例程函数方法!参数:yx
这是例程函数方法!参数:yx
注:在子例程与函数中默认的方法作用域是Public
,如果想改为私有作用域可设置关键字Private
。
在Routine.mac
中添加FuctionPrivate
私有函数方法:
FuctionPrivate() Private
w "这是私有方法不允许调用",!
q "这是私有方法不允许调用"
调用该方法:
ClassMethod Routine6()
{
d FuctionPrivate^Routine
}
可观察到:调用私有子例程或函数方法时提示<NOLINE>
错误。
注:当方法添加关键字 PRIVATE
时,该例程以外的程序调用不到。会报错 <NOLINE>
错误。只能在当前 mac 中使用。
USER>d ##class(M.Method).Routine6()
d FuctionPrivate^Routine }
^
<NOLINE>zRoutine6+1^M.Method.1
- 在函数与子例程中,声明的局部变量默认是共有的,也就是说全局变量。这种情况很容易发生变量覆盖产生死循环。
FuctionPublicVar()
s x = 5
s y = 10
w "FuctionPublicVar中 x + y:" _ (x + y),!
d FuctionPublicVar1()
q x + y
FuctionPublicVar1()
w "FuctionPublicVar1中 x + y:" _ (x + y),!
q x + y
ClassMethod Routine12()
{
d FuctionPublicVar^Routine
}
USER>d ##class(M.Method).Routine12()
FuctionPublicVar中 x + y:15
FuctionPublicVar1中 x + y:15
为了将局部变量作用域当前方法内部需要在函数或子例程方法第一行声明new
命令开启新的堆栈来初始化变量。
标签 - Label
标签是例程内的命名代码块。不接受参数,无返回值。
要调用标签,请使用以下语法:
d Label^Routine
在Routine.mac
中添加Lable
标签方法:
Lable
w "这是一个标签方法",!
调用该方法:
ClassMethod Routine7()
{
d Lable^Routine
}
USER>d ##class(M.Method).Routine7()
这是一个标签方法
注:即使给标签加上了返回值,实际上也获取不到。
在Routine.mac
中添加LableValue
有返回值的标签方法:
LableValue
w "有返回值的标签方法",!
q "有返回值的标签方法"
调用该方法:
ClassMethod Routine8()
{
s ret = $$LableValue^Routine
w ret,!
}
由于标签没有括号,所以提示参数错误,不能获取返回值。
USER>d ##class(M.Method).Routine8()
LableValue
^
<PARAMETER>LableValue^Routine
常见的使用标签的方式:
- 在类方法里抽取重复的部分。
- 在
Query
里收取数据放入临时Global
中。
示例如下:
代码中output
就是标签的用法。没有参数没有回值。一般调用方式为 d label
实际上相当于goto
命令的使用。
ClassMethod QueryPersonByAgeExecute(ByRef qHandle As %Binary, pAge As %String = "", count As %Integer = "10") As %Status
{
s pid = $i(^CacheTemp) // 注释1
s qHandle = $lb(0, pid, 0) // 注释2
s index = 1 // 注释3
/* 业务逻辑代码 注释4 */
s id = ""
for {
s id = $o(^M.T.PersonD(id))
q:(id = "")
q:(id > count)
s data = ^M.T.PersonD(id)
s i = 1
s name = $lg(data, $i(i))
s age = $lg(data, $i(i))
continue:(age < pAge)
s no = $lg(data, $i(i))
d output
}
/* 业务逻辑代码 */
q $$$OK
output
s ^CacheTemp(pid, index) = $lb(id, age, name, no) // 注释6
s index = index + 1 // 注释7
}
另外还一点需要注意,定义的标签如果没有退出命令例如 quit
, retrun
命令 是可以顺序执行之后的标签,直到遇到退出命令。
在Routine.mac
中修改Lable
标签方法:
Lable
w "这是一个标签Lable方法",!
Lable1
w "这是一个标签Lable1方法",!
Lable2
w "这是一个标签Lable2方法",!
Lable3
w "这是一个标签Lable3方法",!
LableValue
w "有返回值的标签方法",!
q "有返回值的标签方法"
再次调用方法:
USER>d ##class(M.Method).Routine7()
这是一个标签Lable方法
这是一个标签Lable1方法
这是一个标签Lable2方法
这是一个标签Lable3方法
有返回值的标签方法
过程 - Procedure
Procedures
- 在网上翻译为: [计算机]过程; (为解决一个特殊问题而专门设计的)文字程序。也有的叫程序,这里我们称呼为过程更为严谨。
Procedures
过程是一种特殊的ObjectScript
方法。过程是在例程中功能最为强大的自定义代码块,也是最为推荐的形式。
Procedure
过程语法形式:
label([param[=default]][,...]) [[pubvar[,...]]] [access] {
code
}
-
label
- 过程名称。 -
param
- 变量。 -
default
- 参数的可选默认值。 -
pubvar
- 公共变量。 -
access
- 可选关键字,用于声明过程是公共的还是私有的。有两个可用值:public
,private
。 -
code
- 用大括号括起来的代码。
Procedure
过程也称为过程块ProcedureBlock
。
注:ProcedureBlock
是创建类时默认的关键字,也就是意味着类方法实际就是ProcedureBlock
。
要调用过程,请使用以下语法:
d Procedure^RoutineName(params) /* 忽略返回值 */
s x = $$Procedure^RoutineName(params)
- 默认情况下,过程是私有的,这意味着只能从同一例程中的其他地方调用。如果外部调用则会提示
<NOLINE>
错误。
在Routine.mac
中添加Procedure
过程方法:
Procedure(x, y) {
w "x + y = ", x + y,!
q x + y
}
ClassMethod Routine10()
{
s ret = $$Procedure^Routine(2, 8)
w ret,!
}
USER>d ##class(M.Method).Routine10()
s ret = $$Procedure^Routine(2, 8)
^
<NOLINE>zRoutine10+1^M.Method.1
- 如果过程需要外部调用,可以创建公共的过程,在过程名称后使用
Public
关键字。可以从其他例程调用公共过程。
Procedure(x, y) Public {
w "x + y = ", x + y,!
q x + y
}
USER>d ##class(M.Method).Routine10()
x + y = 10
10
- 过程的另一特点是声明公共变量。公共变量相当于全局变量,环境变量,可用于所有过程。即此过程调用的过程以及调用此过程的过程都可以使用全局变量。要定义公共变量,请将它们列在过程名称及其参数后面的方括号中。
在Routine.mac
中添加ProceduresPublicVar
、ProceduresPublicVar1
过程方法并声明公共变量a
,b
,局部变量c
,并调用ProceduresPublicVar1
过程方法:
ProceduresPublicVar(e, f) [a, b] Public{
s a = 10
s b = 20
s c = 30
w "e + f = ",e + f,!
d ProceduresPublicVar1(9,9)
}
ProceduresPublicVar1(g, h)[a, b] Public{
w "a:", $d(a),!
w "b:", $d(b),!
w "c:", $d(c),!
w g + h,!
w a + b,!
}
调用方法观察到ProceduresPublicVar1
中公共变量a
,b
是$d
判断是存在的,局部变量c
不存在。
ClassMethod Routine11()
{
d ProceduresPublicVar^Routine(5, 10)
}
USER>d ##class(M.Method).Routine11()
e + f = 15
a:1
b:1
c:0
18
30
注意:过程比以前在子例程,函数中提供的编码更先进。过程参数在过程内的作用域中自动是局部的。不需要 NEW
命令来确保它们不会覆盖其他值,因为它们是过程专用的,此外,公共变量的显式声明允许引用应用程序中的全局变量。
过程与其他方法区别
- 子例程
Subroutine
默认是公共的,不能返回值。 - 函数
Function
默认是公共的,局部变量默认是共有变量,会覆盖同名外部变量,并且必须具有返回值。 - 例程
Routine
是ObjectScript
程序。可以包括一个或多个过程、子例程和函数,以及三者的任意组合。 - 建议使用过程
Procedure
,因为可以简化控制变量范围作用域。但是,在现有代码中,可能还会看到函数和子例程,需要能够识别并区分。在新的编码中不应该在编写函数与子例程。
下表总结了例程、子例程、函数和过程之间的差异:
Routine | Subroutine | Function | Procedure | |
---|---|---|---|---|
可以接受参数 | no | yes | yes | yes |
可以返回值 | no | no | yes | yes |
可以在例程之外调用(默认情况下) | yes | yes | yes | no |
其中定义的变量在代码执行完成后可用 | yes | yes | yes | 取决于变量的性质 |
注:在日常用法中,通常称呼“子例程subroutine
”可以表示过程procedure
、函数function
或子例程subroutine
。
类方法 - ClassMethod
类方法是我们在日常开发中最常用的方法类型,它是可以直接调用的方法,在其他语言当中称为静态方法。
在类中定义类方法,使用如下格式:
ClassMethod MethodName(Arguments) as Classname [ Keywords]
{
//method implementation
}
-
MethodName
- 方法的名称,方法名最长180
个字符。 -
Arguments
- 参数以逗号分割。 -
Classname
- 可选的类名,表示此方法返回的值的类型。如果方法不返回值,则省略As Classname
部分。 -
Keywords
- 代表关键字。
要调用类方法的格式为:
##class(Package.Class).Method(Args)
-
Package.Class
- 完全限定类名。 -
Method
- 方法名。 -
Args
- 方法参数。 -
##class
- 调用前缀,不区分大小写。
如果调用的类在同一级包下,或使用 IMPORT
导入包。可以直接使用类名。
##class(Class).Method(Args)
在同一个类中的方法互相调用或调用父类继承的方法,使用以下表达式:
..Method(Args)
定义一个类方法:
ClassMethod ClassMethod(x As %Integer, y As %Integer) As %Integer
{
q x + y
}
调用该类方法:
USER>w ##class(M.Method).ClassMethod(5,10)
15
Not ProcedureBlock
关键字原理
此时我们查看类方法对应的int
文件:
可以发现在cls
定义的类方法实际上就是生成的int
例程的过程Procedure
。两者一一对应。也是为什么类方法默认的局部变量的作用域都是在自己的方法内部。
在定义一个类方法声明Not ProcedureBlock
关键字:
ClassMethod ClassMethod1(x As %Integer, y As %Integer) As %Integer [ ProcedureBlock = 0 ]
{
q x + y
}
此时我们查看类方法对应的int
文件:
可以发现在cls
定义的类方法加上Not ProcedureBlock
关键字后,生成的int
文件中的该方法不是Procedure
过程了,变成了函数Function
或子例程Subroutine
。前面小节讲过函数或子例程中局部变量为全局变量。也就是为什么在Not ProcedureBlock
的类方法中需要加new
命令原因了。
实例方法
实例方法实际上就对象方法,顾名思义调用该方法需要对象,有了对象我们就可以面向对象编程了。理解面向对象对于编程十分重要。
在类中定义实例方法,使用如下格式:
Method MethodName(arguments) as Classname [ Keywords]
{
//method implementation
}
注:实例方法只与对象类相关、因为没有对象的实例就不能执行实例方法,所以实例方法只有在对象类中定义时才有用。
要调用实例方法的格式为:
do obj.MethodName()
set value = obj.MethodName(args)
obj
- 声明的对象,使用%New()
来构造对象,相当于构造方法。MethodName
- 方法名称args
- 方法参数
创建一个简单的实例方法并调用:
Method Method(x As %Integer, y As %Integer) As %Integer
{
q x + y
}
USER>w ##class(M.Method).%New().Method(5,10)
15
这里需要注意一点对象实际上也是可以调用类方法的。
USER>w ##class(M.Method).%New().ClassMethod(5,10)
15
创造价值,分享学习,一起成长,相伴前行,欢迎大家提出意见,共同交流。