Introduction to Classes and Objects（类与对象）

（以下内容译自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 ，这有两个功效：

class user : user constructors new : (string Name, string Password). end class user
 * 这意味着 user 对象要提供在 person 接口中声明的谓词（即 getName 和 setName）
 * 这还意味着user类型的对象也是person类型的对象，因而也就可以用在需要person对象的关联中. 假设我们有如下的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，如此一来，就会有如下效果：

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):
 * 每个构造出来的user对象都内含了person对象
 * user类可以直接由person类继承所有person接口的谓词. 继承谓词时，并不是直接说它的实现，而是说要继承的实现所在的类.

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 的段可以用来便捷这样的委托.

这个代码功能上很相似，但还是有显著的差别：

Visual Prolog具有多重继承特性，也就是说，可以同时继承多个类.
 * 首先，代码还是多写了些
 * person对象没有内含在user对象中，而是引用了它（还有，这里有两个内存分配块而不是一个）.
 * 在后面这个情况下，我们可以 动态地 改变事实变量中的值为其它的对象，这只需要给事实变量赋值为新的对象，比如，类personInDB的一个对象. 要注意，第二种实现在调用过程中有间接的一层. Visual Prolog处理这样的间接关系是相当有效率的，不过它处理继承会更有效率.

小结
上面，我们介绍了Visual Prolog中对象系统的基本概念. 对象系统中还有其它一些有意思的特征，这里没有说到，比如：


 * 对象可以在实现中进一步支持接口
 * 用于内存回收的终结程序
 * 对象谓词值，a seamless counterpart to C# delegates.