Introduction to Classes and Objects(类与对象)

From wiki.visual-prolog.com

Revision as of 03:48, 28 February 2017 by Yiding (talk | contribs) (继承)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

(以下内容译自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.

参考