本书介绍了如何在InterSystems IRIS®数据平台中创建和使用类,特别是定义对象的类。
如果您不熟悉类编程,本章旨在让您了解此类编程的工作原理。如果您熟悉类编程,您可能会发现浏览代码示例很有用,这样您就可以看到InterSystems IRIS中的类编程是什么样子了。
本章中的概念在很大程度上独立于语言,尽管示例使用ObjectScript。
1.1 对象和属性
在类编程中,一个关键概念是对象。对象是一组值的容器,这些值存储在一起或作为集合一起传递。对象通常对应于现实生活中的实体,例如患者、患者诊断、事务等等。
类定义通常是给定类型对象的模板。类定义具有包含这些对象的值的属性。例如,假设我们有一个名为 MyApp.Clinical.PatDiagnosis
的类;此类可以具有属性Date、EnteredBy、PatientID、DiagnosedBy和Code等。
通过创建类的实例来使用模板;这些实例是对象。例如,假设用户将患者诊断输入用户界面并保存该数据。基础代码将具有以下逻辑:
-
从患者诊断模板创建新的患者诊断对象。
-
根据需要设置对象属性的值。有些可能是必需的,有些可能具有默认值,有些可能基于其他值计算,有些可能是纯可选的。
-
保存对象。
此操作存储数据。
下面显示了使用ObjectScript的示例:
//创建对象
set diagnosis=##class(MyApp.Clinical.PatDiagnosis).%New()
//使用特殊变量设置几个属性
set diagnosis.Date=$SYSTEM.SYS.TimeStamp()
set diagnosis.EnteredBy=$username
//根据用户界面变量设置其他属性
set diagnosis.PatientID=patientid
set diagnosis.DiagnosedBy=clinicianid
set diagnosis.Code=diagcode
//保存数据
//下一行尝试保存数据并返回状态以指示行动是否成功
set status=diagnosis.%Save()
//始终检查返回的状态
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit status}
注意以下几点:
-
要引用对象的属性,可以使用语法
object_variable.propertiesname
,例如:diagnosis.DiagnosedBy
-
%New()
和%Save()
是MyApp.Clinical.PatDiagnosis
类的方法。
下一节将讨论方法的类型以及为什么要以不同的方式调用它们。
1.2 方法
方法是一个过程(在大多数情况下,InterSystems IRIS支持其他类型的方法,您将在下一章中看到)。方法可以相互调用,可以引用属性和参数。
类语言中有两种方法:实例方法和类方法。它们有不同的目的,以不同的方式使用。
1.2.1 实例方法
实例方法只有在从类的实例调用时才有意义,通常是因为您正在对该实例执行某些操作。例如:
set status=diagnosis.%Save()
例如,假设我们正在定义一个表示患者的类。在这个类中,我们可以定义实例方法来执行以下操作:
- 计算患者的BMI(体重指数)
- 打印汇总患者信息的报告
- 确定患者是否符合特定程序的要求
这些操作中的每一个都需要了解为患者存储的数据,这就是为什么大多数程序员会将它们作为实例方法来编写的原因。在内部,实例方法的实现通常引用该实例的属性。下面显示了引用两个属性的实例方法的示例定义:
Method GetBMI() as %Numeric
{
Set bmi=..WeightKg / (..HeightMeter*2)
Quit bmi
}
要使用此方法,应用程序代码可能包括以下行:
//根据id打开请求的患者
set patient=##class(MyApp.Clinical.PatDiagnosis).%OpenId(id)
//获取要在BMI显示字段中显示的值
set BMIDisplay=patient.GetBMI()
1.2.2 类方法
另一种类型的方法是类方法(在其他语言中称为静态方法)。要调用这种类型的方法,可以使用不引用实例的语法。例如:
set patient=##class(MyApp.Clinical.PatDiagnosis).%New()
编写类方法有三个非常普遍的原因:
-
您需要执行创建类实例的操作。
根据定义,此操作不能是实例方法。
-
您需要执行影响多个实例的操作。
例如,您可能需要将一组患者重新分配给不同的初级护理医生。
-
您需要执行不影响任何实例的操作。
例如,您可以编写一个方法,返回一天中的时间、随机数或以特定方式格式化的字符串。
1.2.3 变量作用域
方法通常设置变量的值。在几乎所有情况下,这些变量仅在该方法中可用。例如,考虑以下类:
Class GORIENT.VariableScopeDemo
{
ClassMethod Add(arg1 As %Numeric, arg2 As %Numeric) As %Numeric
{
Set ans=arg1+arg2
Quit ans
}
ClassMethod Demo1()
{
set x=..Add(1,2)
write x
}
ClassMethod Demo2()
{
set x=..Add(2,4)
write x
}
}
Add()
方法设置一个名为ans的变量,然后返回该变量中包含的值。
方法 Demo1()
使用参数1和2调用方法 Add()
,然后写入答案。方法 Demo2()
类似,但使用不同的硬编码参数。
如果方法 Demo1()
或 Demo2()
试图引用变量ans,则该变量在该上下文中未定义,InterSystems IRIS将抛出错误。
类似地, Add()
不能引用变量x。此外, Demo1()
中的变量x与 Demo2()
中变量x是不同的变量。
这些变量的范围有限,因为这是InterSystems IRIS类的默认行为(也是其他类语言中的常见行为)。
在类定义中,几乎完全通过将值作为参数包含到方法中来传递值。这是类编程中的惯例。这种约定简化了确定变量范围的工作。
相反,在编写例程时,有必要了解控制范围的规则。这些在使用ObjectScript中进行了讨论。
1.3 常量
有时,类可以轻松访问常量值是很有用的。在InterSystems IRIS类中,这样的值是一个类参数。其他语言则使用类常量这一术语。以下是一个示例:
Parameter MYPARAMETER = "ABC" ;
类常量在编译时获取值,以后不能更改。
您的方法可以引用常量;这就是定义常量的原因。例如:
set myval=..#MYPARAMETER * inputvalue
1.4 类定义和类型
下面是一个类定义的示例,我们将使用它来讨论类定义中的类型:
Class MyClass Extends %Library.Persistent
{
Parameter MYPARAMETER = "ABC" ;
Property DateOfBirth As %Library.Date;
Property Home As Sample.Address;
Method CurrentAge() As %Library.Integer
{
//details
}
ClassMethod Addition(x As %Library.Integer, y As %Library.Integer) As %Library.Integer
{
//details
}
}
这个类定义定义了一个参数( MYPARAMETER
)、两个属性( DateOfBirth
和 Home
)、一个实例方法( CurrentAge()
)和一个类方法( Addition()
)。
在类编程中,可以在以下关键位置指定类型:
-
对于类本身。Extends后面的元素是一种类型。
每种类型都是类的名称。
-
对于参数。在本例和其他情况下,As后面的元素是一种类型。
-
对于属性。对于Home属性,类型是一个本身包含属性的类。
在这种情况下,类型具有对象值。在这里的示例中,这是一个对象值属性。
对象值属性可以包含其他对象值属性。
-
用于方法的返回值。
-
用于方法使用的任何参数的值。
1.5 继承
在大多数基于类的语言中,一个主要特性是继承:一个类可以从其他类继承,从而获得其他类的参数、属性、方法和其他元素。参数、属性、方法和其他元素统称为类成员。
1.5.1 术语和基础
当类A从类B继承时,我们使用以下术语:
-
类A是类B的子类。或者,类A扩展了类B。
有时有人说A类是B类的一个子类。
-
B类是A类的超类。
有时有人说A类是子类,B类是父班。这个术语很常见,但可能会误导人,因为在讨论SQL表时,父级和子级这两个词的使用意义截然不同。
当一个类从其他类继承时,它获取这些其他类的类成员,包括超类自身继承的成员。子类可以重写继承的类成员。
一个类的多个超类可以定义同名的方法、同名的属性等。因此,有必要制定规则来决定哪个超类提供了子类中使用的定义。
在InterSystems IRIS类库中,超类通常有不同的用途,并且具有不同名称的成员,成员名称的冲突并不常见。
1.5.2 示例
以下是InterSystems IRIS的示例:
/// 查找FilePath目录中的文件,并将所有与FileSpec通配符匹配的文件提交给用于在InterSystems IRIS中处理的相关业务服务
Class EnsLib.File.InboundAdapter Extends (Ens.InboundAdapter, EnsLib.File.Common)
这个例子只是为了演示一个类如何组合来自不同超类的逻辑。这个 EnsLib.File.InboundAdapter
可类中继承了两个执行完全不同任务的类:
Ens.InboundAdapter
,其中包含“入站适配器”的基本逻辑,这是InterSystems IRIS中的一个概念。EnsLib.File.Common
,其中包含处理给定目录中的文件集的逻辑。
在 EnsLib.File.InboundAdapter
,方法使用这两个类及其超类的逻辑。
1.5.3 继承类成员的使用
当您在编辑工具中看到类的定义时,您不会看到它包含的继承成员,但代码可以引用它们。
例如,假设类A有两个属性,每个属性都有一个默认值,如下所示:
Class Demo.A
{
Property Prop1 as %Library.String [InitialExpression = "ABC"];
Property Prop2 as %Library.String [InitialExpression = "DEF"];
}
B类可能是这样的:
Class Demo.B Extends Demo.A
{
Method PrintIt()
{
Write ..Prop1,!
Write ..Prop2,!
}
}
如前所述,子类可以覆盖继承的类成员。例如,类C也可以从类A继承,但可以覆盖其属性之一的默认值:
Class Demo.C Extends Demo.A
{
Property Prop2 as %Library.String [InitialExpression = "GHI"];
}
1.5.4 子类的使用
如果类B继承自类A,则可以在可以使用类A实例的任何位置使用类B的实例。
例如,假设您有如下实用程序方法:
ClassMethod PersonReport(person as MyApp.Person) {
//print a report that uses properties of the instance
}
您可以使用 MyApp.Person
的实例作为此方法的输入。您还可以使用 MyApp.Person
的任何子类的实例。例如:
//id variable is set earlier in this program
set employee=##class(MyApp.Employee).%OpenId(id)
do ##class(Util.Utils).PersonReport(employee)
类似地,方法的返回值(如果它返回值)可以是指定类型的子类的实例。例如,假设 MyApp.Eyee
和 MyApp.Peater
都是 MyApp.Person
的子类。您可以定义如下方法:
ClassMethod ReturnRandomPerson() as MyApp.Person
{
Set randomnumber = $RANDOM(10)
If randomnumber > 5 {
set person=##class(MyApp.Employee).%New()
}
else {
set person=##class(MyApp.Patient).%New()
}
quit person
}
1.6 类作为方法的容器
如前所述,类定义通常是对象的模板。另一种可能性是类是一组属于一起的类方法的容器。在这种情况下,您永远不会创建此类的实例。您只能在其中调用类方法。
例如,请参阅InterSystems IRIS%SYSTEM
包中的类。
1.7 抽象类
定义抽象类也很有用。抽象类通常定义通用接口,并且不能实例化。类中的方法定义声明方法的签名,但不声明其实现。
定义一个抽象类来描述接口。然后您或其他开发人员创建子类,并在这些子类中实现方法。实现必须与抽象类中指定的签名匹配。该系统使您能够开发多个目的略有不同但接口相同的并行类。出于这个原因,许多系统类都有公共接口。
即使类不是抽象的,也可以指定方法是抽象的。