Difference between revisions of "Introduction to Classes and Objects(类与对象)"
(Created page with "(以下内容译自Category:Tutorials中的Introduction to Classes and Objects。) 本文的目的是概要介绍Visual Prolog中的面向对象的概念,并对相关...") |
(→继承) |
||
(One intermediate revision by the same user not shown) | |||
Line 216: | Line 216: | ||
==继承== | ==继承== | ||
当实现user类时,当然想要利用已有的那个person类。假设我们想要user类与person类差不多,只不过是还需要处理口令。这样,我们可以让user类从person类中 inherit (继承)person那部分的实现。下面的代码就可以做到这个要求: | |||
<vip>implement user | <vip>implement user | ||
Line 286: | Line 286: | ||
==参考== | ==参考== | ||
[[Category: | [[Category:Tutorials部分中文译文]] |
Latest revision as of 02:48, 28 February 2017
(以下内容译自Category:Tutorials中的Introduction to Classes and Objects。)
本文的目的是概要介绍Visual Prolog中的面向对象的概念,并对相关语法做些举例说明。
对象模型
在Visual Prolog的 object model(对象模型) 中,主要的语义实体是 objects(对象)、对象类型及类。而涉及这些实体的主要语法概念则是接口、类的声明和实现。
interface(接口) 是一组命名了的谓词声明,它描述了对象的“界面”(因而称之为interface),也就是从对象外部访问对象的通道。接口就代表了对象类型(object types)。
来看下面这个接口定义:
interface person predicates getName : () -> string Name. setName : (string Name). end interface person
这是一个叫做person的接口的定义。所有person类型的对象都有两个谓词:getName 和 setName,如同上面声明的那样。
接口只是定义对象的类型,而类才创建对象。类有declaration(声明)和implementation(实现)。构造(constructs)person对象的类,可以像这样:
class person : person constructors new : (string Name). end class person
这是名为person的类的声明,这个类构造person类型的对象。 该类有个名为new的构造器(constructor),只要给它一个 Name,它就会构造一个(person类型的)对象。
类还需要有实现,可以是这样的:
implement person facts name : string. clauses new(Name) :- name := Name. clauses getName() = name. clauses setName(Name) :- name := Name. end implement person
它是person类的实现。实现必须为各公共谓词及构造器提供定义,在这里就是 new、getName 和 setName。实现也可以局部地声明和定义额外的实体,这些实体只能在实现内部访问。这个例子中,类声明了一个名为name的事实变量,它用于保存人的姓名。
每个对象有自己的事实变量name的实例,而上面子句的代码会引述特定的事实变量实例。我们称这样的谓词是对象谓词(object predicates)而那个事实是对象事实(object fact)。
类实体
类还可以有类中所有对象共享的实体。 我们仍用上面的例子,再扩充一下,加上一些代码对创建了多少个person对象来计数。每创建一个对象,计数就加一。
先扩充一下类的声明,增加一个谓词 getCreatedCount,它会返回当前的计数值。
class person : person constructors new : (string Name). predicates getCreatedCount : () -> unsigned Count. end class person
注意,公用的类谓词(class predicates)是在类声明中声明的,而公用的对象谓词(object predicates)则是在接口中声明的。这是一个没有特例的规则:不可能在类声明中声明对象谓词,也不可能在接口中声明类谓词。
增加的谓词还需要有类实现中的定义,这里还需要一个事实来保存计数值,这个事实必须是类事实(class fact,它是由所有对象共享的)。在类的实现(implementation)中可以声明和定义私有的对象实体及私有的类实体。要声明类实体,需要把相应的声明段加上class关键字。总之,类person的实现可以是这样:
implement person class facts createdCount : unsigned := 0. clauses getCreatedCount() = createdCount. facts name : string. clauses new(Name) :- name := Name, createdCount := createdCount+1. clauses getName() = name. clauses setName(Name) :- name := Name. end implement person
上面,增加了一个被初始化为零的类事实createdCount;增加了一个返回createdCount当前值的谓词getCreatedCount;还在构造器中增加对createdCount加一的代码。
注意一下构造器,两个赋值形式上是一样的,但一个更新的是对象的状态而另一个则改变的是类的状态。
模块
类有一个特殊的变种,它根本不产生对象,这样的类更像是一个模块。不构造对象的类(或简单说模块),声明时就不需要对象类型了:
class io % 这里没有类型 predicates write : (string ToWrite). write : (unsigned ToWrite). end class io
这样不产生对象的类显然不能有对象实体,也不能有构造器。
创建与访问
利用上面的代码,再建立一个可创建对象并用io类(这里不用管它是如何实现的)写出person姓名的目标(goal)。
goal P = person::new("John"), Name = P:getName(), io::write(Name).
上面的第一行,调用person类中的构造器 new ,创建的对象约束给变量P。再下一行,将在P上调用对象谓词 getName 的结果约束变量 Name。最后一行,调用io类中的类谓词来写。
注意一下,类中的名称引用时用双冒号,如 person::new;而引用对象谓词时用单个冒号,如 P:getName 。
还需要注意,尽管构造器的声明不像是个函数,但它是一个返回对象的函数,而返回类型则包含在类声明中。
接口是对象的类型
上面说过,接口就是对象的类型。 This should be taken literally, you can use interfaces in the same places where you can use non-object types. 例如,在下面的谓词声明中:
class mail predicates sendMessage : (person Recipient, string Message). end class mail
谓词 mail::sendMessage 以 person 和 string 作为参数。
多重实现
可以建立若干完全不同的类,这些类都可以创建person对象,这只需要声明和实现多个类来构造person对象。类的实现可以大不同,如,创建一个类把person保存在一个数据库中。它可以这样声明:
class personInDB : person constructors new : (string DatabaseName, string Name). end class personInDB
在这里,我们不去关心这样或那样的实际实现,不过我们还是给出下面的代码,显示对某个对象类型,可以有完全不同的实现。
implement personInDB facts db : myDatabase. personID : unsigned. clauses new(DatabaseName, Name) :- db := myDatabase::getDB(DatabaseName), personID := db:storePerson(Name). clauses getName() = db:getPersonName(personID). clauses setName(Name) :- db:setPersonName(personID, Name). end implement personInDB
可以看到,不光是内部的行为完全不同了,而且内部的状态也有了完全不同的结构与内容。
包容多态
相同类型的对象可以用在相同的关联中,而不用去管各自的实现有多大差别。例如,可以用上面的mail类给某个person发送消息,不管这个person是由person类构造的还是personInDB类构造的都行:
goal P1 = person::new("John"), mail::sendMessage(P1, "Hi John, ..."), P2 = personInDB::new("Paul"), mail::sendMessage(P2, "Hi Paul, ...").
这个特性就是所谓包容:一个类构造的对象与另一个类构造的对象都可以适应特定关联,只要这两个对象都具有关联所要求的类型。
可以看到,谓词 mail::sendMessage 可以用任一个person类的对象,因而从某种意义上说它是多态的(包容多态)。
支持 (类型的扩展)
我们再来假设一下,程序要处理特殊的一类人:程序用户。用户有姓名,而且他们还有口令。我们可以对用户创建一个新的对象类型/接口,它可以声明说用户是带有口令的person。为此,要使用supports(支持)限定符:
interface user supports person predicates trySetPassword : (string Old, string New, string Confirm) determ. validatePassword : (string Password) determ. end interface user
它说:user supports(支持) person ,这有两个功效:
- 这意味着 user 对象要提供在 person 接口中声明的谓词(即 getName 和 setName)
- 这还意味着user类型的对象也是person类型的对象,因而也就可以用在需要person对象的关联中。假设我们有如下的user类:
class user : user constructors new : (string Name, string Password). end class user
那么,这个类的对象也可以被 mail::sendMessage 使用:
goal P = user::new("Benny", "MyCatBobby"), mail::sendMessage(P, "Hi Benny, ...").
一个接口可以支持若干个其它接口,这就是说:
- 该类型对象必须提供所支持接口中的所有谓词
- 该类型对象具有所有所支持接口的类型。
一个接口也可以支持一个或多个自身支持其它接口的接口,如此等等。在这样的情况下:
- 该类型对象必须提供它所直接和间接支持接口的所有谓词
- 该类型对象具有所有直接和间接支持接口的类型。
supports 限定符会产生一个亚层,我们称 user 是 person 的子类。
对象:根对象 超类型
一个接口没有显式地支持任何其它接口时,隐含地意味支持接口object。object是一个隐含定义的接口,没有内容(也就是没有谓词)。任何对象都直接或间接地支持object接口,所以任何对象都具有object类型。因此,object是所有对象类型的超类。
继承
当实现user类时,当然想要利用已有的那个person类。假设我们想要user类与person类差不多,只不过是还需要处理口令。这样,我们可以让user类从person类中 inherit (继承)person那部分的实现。下面的代码就可以做到这个要求:
implement user inherits person facts password : string. clauses new(Name, Password) :- person::new(Name), password := Password. clauses trySetPassword(Old, New, Confirm) :- validatePassword(Old), New = Confirm, password := New. clauses validatePassword(Password) :- password = Password. end implement user
上面的实现说它 inherits person,如此一来,就会有如下效果:
- 每个构造出来的user对象都内含了person对象
- user类可以直接由person类继承所有person接口的谓词。继承谓词时,并不是直接说它的实现,而是说要继承的实现所在的类。
The inheritance can to a certain extend be explained as syntactic sugaring. At least I could have achieved exactly the same effect with the following code (the clauses for the password predicates remains as above):
implement user facts person : person. password : string. clauses new(Name, Password) :- person := person_class::new(Name), password := Password. clauses getName() = person:getName(). clauses setName(Name) :- person:setName(Name). ... end implement user_class
上面的代码,我们没有继承person类的什么东西,而是自行创建 person 对象并把它保存在事实变量中。也没有继承 getName 和 setName 的代码而是直接实现了这些谓词,它们只不过是把工作委托给了事实变量中的对象。带有关键字 delegate 的段可以用来便捷这样的委托。
这个代码功能上很相似,但还是有显著的差别:
- 首先,代码还是多写了些
- person对象没有内含在user对象中,而是引用了它(还有,这里有两个内存分配块而不是一个)。
- 在后面这个情况下,我们可以 动态地 改变事实变量中的值为其它的对象,这只需要给事实变量赋值为新的对象,比如,类personInDB的一个对象。要注意,第二种实现在调用过程中有间接的一层。Visual Prolog处理这样的间接关系是相当有效率的,不过它处理继承会更有效率。
Visual Prolog具有多重继承特性,也就是说,可以同时继承多个类。
小结
上面,我们介绍了Visual Prolog中对象系统的基本概念。对象系统中还有其它一些有意思的特征,这里没有说到,比如:
- 对象可以在实现中进一步支持接口
- 用于内存回收的终结程序
- 对象谓词值,a seamless counterpart to C# delegates.