zoukankan      html  css  js  c++  java
  • Qomolangma实现篇(五):Qomo的OOP的语法和类继承体系

    ================================================================================
    Qomolangma OpenProject v1.0


    类别    :Rich Web Client
    关键词  :JS OOP,JS Framwork, Rich Web Client,RIA,Web Component,
              DOM,DTHML,CSS,JavaScript,JScript

    项目发起:aimingoo (aim@263.net)
    项目团队:aimingoo, leon(pfzhou@gmail.com)
    有贡献者:JingYu(zjy@cnpack.org)
    ================================================================================


    一、Qomolangma中完整的OOP支持:Object.js
    ~~~~~~~~~~~~~~~~~~

    Qomo自Field Test4开始提供Object.js,这个单元用于在Qomo中支持完整的OOP特性。通过
    Object.js,Qomo中的JavaScript由原来的“原型继承”转变成了“类继承”。而且,这个
    转变对于开发人员来说,几乎是完全透明的。

    通过Object.js,Qomo支持了如下的面向对象特性:
      - 特性(Attribute)及读写器(getter/setter)
      - 类注册与类继承(Class),以及类在命名空间上的注册
      - (继承)父类的方法调用(Inherited)

    (TODO: 在safari中,由于不支持function.caller属性,因此无法支持Qomo中的Object.js)


    二、Qomolangma中的OOP语法
    ~~~~~~~~~~~~~~~~~~

    Qomo的确扩展了一些JS语法。但与一些其它的JS OOP实现方案不同:Qomo是基于JS语法自
    身扩展,而不是加上一些替换用的标识/关键字。后者的实现方案,可能要求单独的一个
    语法翻译系统/解释引擎。但Qomo不需要,而且基本上来说,由于不存在syntax parse,
    所以Qomo运行的速度要较快,而且开发人员书写的代码与调试器中的代码完全一致。

    Qomo在全局提供了三个以关键字形式描述的函数:Class(), Attribute()和Abstract()。
    其中Class和Abstract事实上使用了js保留的关键字class与abstract。这意味着在今后的
    (例如Qomo for JS 2.0中)Qomo有可能删除掉它们。

    这些函数中,Class()用于将一个构造器函数注册到一个类; Attribute()用于快速地声明
    和初始化特性; Abstract()用于声明一个抽象方法。

    Qomo在全局提供了三个仅能在类注册和初始化阶段使用的函数:_set(),_get()和_cls()。
    它们用"_"开始,表明调用它们的上下文环境是受限的:只能在类声明过程中被调用。

    在Object.js中,还重写了Object()类,重写的Object()与JavaScript原有的Object()具有
    完全相同的特性。事实上,它的原型就是原来JavaScript Object()的一个实例。

    重写Object()而不是直接使用原有的,是因为在Qomo中有一项重要约定:
      - Qomo中应该允许自由地使用"{}"语法来声明直接量的对象,而且这个对象一定是没有任
        何“可见的”属性的。

    由于Qomo是使用“类继承”体系的,因此Qomo注册了一个全局的基类:TObject。


      1. 类声明与类注册(Class)
      ----------
      Qomo中声明一个类的方法,与标准JS中声明一个“对象构造器(函数)”的方法完全一致。
    但是,在声明类之后,Qomo需要调用Class()来注册它。如下例:
    ----------
    // 类声明
    function MyObject() {
      this.value = 'Hello, Qomoer!!!';
    }
    // 类注册
    TMyObject = Class(TObject, 'MyObject');

    // 创建实例和调用
    var obj = new MyObject();
    alert(obj.value);
    ----------

    我们也注意到,除了多一行类注册之外,Qomo其它的(基本)语法与标准的JS没有任何的不同。

    但是,通过Class()注册,得到的对象实例obj将会具有以下三个“多余的”方法调用:
      - obj.get() : 取特性值
      - obj.set() : 置特性值
      - obj.inherited() : 继承(调用)父类方法

    关于Qomo的类与对象实例的一些特征,请参见示例:TestCase/BaseObjectDemo.html

    接下来我们讲述与这三个方法相关的一些语法。


      2. 特性(Attribute)的使用
      ----------
      在Delphi中属性是可以有读写器的,在C#等其它高级语言中也可以具备这些。包括在JS2.0
    中,也提供了属性读写器的声明语法。但这些都是基于属性(properties)的。

    Qomo没有办法提供一种“扩展的语法”来使得JS 1.3支持“属性的读写器”。因此Qomo借用了
    C#中"特性(Attribute)"一词,实现了可定制读写器的Attribute。事实上,也如同C#所推荐的
    一样:Attribute是“最具创新的一种构造”。

    Qomo中可以在“类声明”中直接声明特性(注意这些特性声明/方法不会出现在对象实例的属性
    中,即使是使用for..in运算来列举),而无需事先描述它:
    ----------
    function MyObject() {
      this.getValue = function() {
        return 100;
      }
    }
    TMyObject = Class(TObject, 'MyObject');

    var obj = new MyObject();
    alert(obj.get('Value'));
    ----------

    简单地说,Qomo认为所有使用'get/set'开始的方法,都是一个指定特性的“读写器方法”。
    因此一行简单的“this.getValue = function(){...}”,就具有了以下语义:
      - (在对象实例内部, )声明一个名为"Value"的特性
      - 它的读方法(getter)为getValue()
      - 它的写方法(setter)将直接操作"Value"特性本身

    在上面这个例子中,我们看到getValue()将直接返回100。那么如何能返回“对象实例内部的
    Value特性”的值呢?——因为setter没有被声明,会直接操作这个内部值。参见这个示例:
    ----------
    function MyObject() {
      this.getValue = function() {
        return this.get();
      }
    }
    TMyObject = Class(TObject, 'MyObject');

    var obj = new MyObject();
    obj.set('Value', 1000);
    alert(obj.get('Value'));
    ----------

    这个例子表明,“(仅仅)在读写器函数内部”,可以不带"name"参数地调用:
      - this.get() : 用于取内部特性的数据
      - this.set() : 用于置内部特性数据的值

    注意这两种调用总是发生在读写器方法的内部,这时this总是指向当前实例。此外,对于“写
    方法”来说,要多一个参数。例如:
    ----------
    function MyObject() {
      this.setValue = function(v) {
        return this.set(v*2);
      }
    }
    TMyObject = Class(TObject, 'MyObject');

    var obj = new MyObject();
    obj.set('Value', 1000)
    alert(obj.get('Value'));
    ----------

    在内部实现上,Qomo的特性采用了“写复制”的技术,因此一个类的所有实例的特性初值
    是一样的。但如果某一实例重写了它,则该实例具有一个不同的值。此外,子类对象实例
    也共享父类特性的一份引用(而不是拷贝)。但当实例试图写特性值时,它将写在自己的数
    据区写,而不会影响类或者其它实例。

    特性初值由Attribute()函数来声明,它仅能在类声明阶段被调用。例如:
    ----------
    function MyObject() {
      Attribute(this, 'Value', 100, 'rw');
    }
    TMyObject = Class(TObject, 'MyObject');

    var obj = new MyObject();
    alert(obj.get('Value'));
    ----------

    Attribute()的参数说明如下:
      - 1. base : 必须是this
      - 2. name : 指定特性名. 习惯上以大写开头.
      - 3. value: 初值
      - 4. tag  : 标志字符. 目前仅用于描述读写特性. 包括字符:r, w

    Attribute()的tag参数(目前)用于描述读写性。如果尝试读一个只写的特性时,将触发
    一个异常,反之亦然。但是,在读写器方法的内部就没有这项限制。例如:
    ----------
    function MyObject() {
      Attribute(this, 'Value', 100, 'r');
     
      this.getValue = function() {
        this.set(100);
        return this.get();
      }
    }
    TMyObject = Class(TObject, 'MyObject');

    var obj = new MyObject();
    alert(obj.get('Value'));

    // 以下代码将导致一个异常
    // obj.set('Value', 'hi, error!');
    ----------

    不过,Attribute()所声明的tag是非强制性的。也就是说,即使声明了只读/写特性,
    仍然可以通过一个指定名字的读写器方法来使“只读/写”失效。例如:
    ----------
    function MyObject() {
      Attribute(this, 'Value', 100, 'r');
     
      this.setValue = function(v) {
        this.set(v);
      }
    }
    TMyObject = Class(TObject, 'MyObject');

    var obj = new MyObject();
    obj.set('Value', 'hi, success!');
    alert(obj.get('Value'));
    ----------

    这样设计的原因,是使得子类有机会重写父类的特性的读写器方法。——他们只是名字
    上的相同,但可读写性不一定要一致。例如:
    ----------
    function MyObject() {
      Attribute(this, 'Value', 100, 'r');
    }
    TMyObject = Class(TObject, 'MyObject');

    function MyObjectEx() {
      this.setValue = function(v) {
        this.set('value is: ' + v);
      }
    }
    TMyObjectEx = Class(TMyObject, 'MyObjectEx');

    var obj = new MyObjectEx();
    alert(obj.get('Value'));           // 显示自MyObject()继承来的特性值
    obj.set('Value', 'hi, success!');  // 为实例特性置新值
    alert(obj.get('Value'));           // 显示新值

    var obj2 = new MyObject();
    alert(obj2.get('Value'));          // 检测:其它的实例没有受到影响

    // 对于MyObject()来说,Value特性是不可写的,因此下面的代码将出错
    // (注: 在Field Test 4中,这项限制被暂时地取消了.)
    // var obj3 = new MyObject();
    // obj3.set('Value', 1000);
    ----------

    关于Attribute()的更多示例,请参见:TestCase/BaseObjectDemo2.html


      3. 继承(Inherited)方法的使用
      ----------
      对于一个实例来说,obj.inherited()将调用父类方法,这种调用关系可以在父类
    的父类、乃至祖先类中发生。当调用到根类TObject()时,由于不存在同名的方法,
    因此将会返回一个异常。

    在Qomo中,inherited具有与delphi中的inherited完全一致的特性。当然,它只能在
    方法中被调用。这包括类(声明)方法与对象方法。——这两者的区别随后再介绍。

    inherited有三种调用方法(由于它总是在方法内被调用,因此this总是指向当前实例):
      - this.inherited() :
      - this.inherited(this.mehtod [, param]);
      - this.inherited('<methodName>' [, param]);

    第一种方法没有任何参数,这种情况下,Qomo将查找当前方法的父类方法,并使用相
    同的入口参数调用;对于第二、三两种方法来说,则可以传入一个新的参数表。如果
    参数忽略,也将使用当前方法的参数表。

    第二、三两种方法本意上是相同的,只是一个是用方法名,另一个是用方法引用来作
    为第一个参数。二者不存在效率上的差异,只是用于应对不同的需求。

    关于如何对象的继承性,需要读者去阅读有关OOP的相关书籍了,这里不细述。下面
    的例子描述如何在Qomo中使用这一特性:
    ----------
    function Animal() {
      this.leg = function() {
        alert('跑');
      }

      this.run = function() {
        this.leg();
      }
    }

    function Cat() {
      this.jump = function() {
        alert('跳');
      }

      this.run = function() {
        this.jump();
        this.jump();
        this.inherited();
      }
    }

    TAnimal = Class(TObject, 'Animal');
    TCat = Class(TAnimal, 'Cat');

    obj = new Cat();
    obj.run();
    ----------

    这个例子说明:
     - “动物”有一种run的行为,这个行为的内容是(不停地)“跑(leg)”
     - “猫”是一种“动物”,并从“动物”那里继承了run的行为
     - “猫”通常是“跳两下”,然后再继续run的行为

    所以,猫的run的行为描述是:
    ----------
      this.run = function() {
        this.jump();
        this.jump();
        this.inherited();
      }
    ----------

    关于inherited()的更多示例,请参见:TestCase/BaseObjectDemo3.html


      3. 类声明周期与对象构造周期
      ----------
      在此前的所有例子中,我都没有把Qomo中一项最重要的特性展现在用户面前。这就是
    “类声明周期”。——即使在绝大多数的例子前面,留有这样注释的。

    Qomo中用户代码编写的“构造器”,其实是只应当具有“类声明语法”的。所谓类声明
    语法,就是指“仅于一个对象的表现有关”的语法。而不应该加入过多的逻辑代码。基
    本上来说,类声明可以包括如下代码:
    ----------
    function MyObject() {
      Attribute(this, 'Value', 0);// 快速特性声明
      var v = _get('Data');       // 取(当前类的)父类的特性值
      _set('Data', v);            // 置特性值, 但不覆盖父类

      var cls = _cls();           // 取类引用
      var count = 10;             // 声明类(私有)静态成员
      var foo = function() {};    // 声明类私有函数
      function foo2() {};         // 同上

      this.data = 123;            // 公开属性
      this.getValue = function(){ // 特性读方法
        this.set();               // 内部的读方法
        this.get();               // 内部的写方法
      }                         
      this.setValue = function(){ // 特性写方法
        // ...
      }                         

      this.method1 = function() { // 类方法
        this.inherited();         // 继承(调用)父类方法
      }

      // (other code...)
    }
    TMyObject = Class(TObject, 'MyObject');
    ----------

    上面的示例包括了“类声明周期”的绝大多数代码及其语法。当然,在"other code"中
    用户也可以加入自己的代码。例如初始化类的一些特性,或者将类加入全局的monitor等
    等。但用户需要注意的是,在“类声明周期”:
      - this指向所有对象实例的原型
      - 可以用_cls()来取得类自身的一个引用
      - 不能直接操作当前类声明的特性值(可能会存在一些限制)
      - 不能给(类声明时的)构造器加入口参数
      - 这个构造器函数只被执行一次

    对于用户代码来说,构造器不能加入口参数很大程度上的限制了使用。但事实上,在“类
    继承体系”中,这是很合理的。——你不能用同一个类声明去描述两个不同的类及实例。

    在Qomo中,用户声明的“构造器”实际上被用作“类声明”。因此就独立出来一个“对象
    构造周期”。这个周期是用户代码完全可控的:
    ----------
    function MyObject() {
      // (略: 类声明)

      this.Create = function(v1, v2, v3) {
        // 对象构造周期
      }
    }
    TMyObject = Class(TObject, 'MyObject');
    ----------

    “对象构造周期”是由this.Create()来描述的一个函数。它也很象标准JS中的构造器:
      - 每次对象构造都将为实例调用this.Create()方法
      - 该方法可以有任意的入口参数
      - 该方法中的this,是指向对象实例的
      - 在该方法中,可以访问类的私有成员和方法

    因此,用户可以按自己的习惯在这里书写任意的JavaScript代码。但是与“类声明相
    关的”一些语法不能在这里使用:
      - Attribute(), _set(), _get(), _cls()不能使用
      - this.getValue()等不能声明成特性


    但有一个唯一的例外:this.inherited()可以在“对象构造周期”使用:
    ----------
    function MyObject() {
      this.method1 = function() {
        // ...
      }

      this.Create = function(v1, v2, v3) {
        // 对象构造周期
        // ...
        if (!v1) return;

        this.method1 = function() {
          this.inherited();
        }
      }
    }
    TMyObject = Class(TObject, 'MyObject');
    ----------
    这个例子中,“对象构造周期”中的method1()事实上覆盖了“类.method1”方法。但
    它不会改变其它该类构造的其它实例(如果v1值是有效的)。因此“对象方法”与“类
    声明的方法”是被区分开来的。

    因为这种区分,所以你应该知道“对象构造周期”如果覆盖了“类声明的方法(A)”,
    那么这个inherited将会调用“方法A”。——inhreited()的语义是“父类方法”。

    这个inherited()的特性,请参见:TestCase/BaseObjectDemo3.html


    三、Qomolangma中OOP语法的一些注意事项
    ~~~~~~~~~~~~~~~~~~

    首先,最重要的一点是:Qomo的多投事件系统对任何框架来说,是“完全透明”的!因
    此,它可以在其它任何框架中,象一个普通的事件函数(响应句柄)一样地加入被植入。
    事实上,Qomo的多投事件与Qomo OOP框架完全地脱离开,不利用任何的OOP特性、框架特
    性。——这种设计思路完整地体现了Qomo的目标与宗旨,以及,我们对OOP的认知。


      1. 类声明与构造的一些特例与语法
      ----------
      Qomo中Class()可以只有一个参数,即<Constructor Name>。这有两种情况:
    ----------
    // 语法一:值为'Object',仅仅保留给TObject的声明
    function Object(){
    }
    TObject = Class('Object');


    // 语法二:值不为'Object',相当于调用Class(TObject, '<Constructor Name>');
    function MyObject(){
    }
    TMyObject = Class('MyObject');
    ----------

    此外,Class()还有两种可能的用法:
    ----------
    // 语法三:Class()在声明前调用
    TMyObject = Class(TObject, 'MyObject');
    function MyObject(){
    }

    // 语法四:只注册但不声明类
    function MyObject(){
    }
    Class(TObject, 'MyObject');
    ----------

    语法三使用了JavaScript编译期的特性。由于直接声明的function将在编译期被接受,
    因此TMyObject对'MyObject'的使用可以出现在它的声明之前。但是下面这样的用法就
    不行(关于这一点,在以前的《Qomolangma内核篇(四)》中有讲过):
    ----------
    // 不正确的用法
    TMyObject = Class('MyObject');
    MyObject = function(){
    }
    ----------

    语法四利用了Qomo中不强制类声明的特性。这种用法只将MyObject()注册成为类,但
    不提供一个类类型TMyObject。这种情况下,仍然可以通过obj = new MyObject()来得
    到类实例。而与类类型TMyObject完全等义的引用,可以在以下两个地方找到:
    ----------
    obj = new MyObject();

    // 1. 在obj.ClassInfo中存在类引用
    alert(obj.ClassInfo.ClassName);

    // 2. 在命名空间上存在该类引用
    var cls = eval(obj.ClassInfo.SpaceName + '.TMyObject');
    alert(cls.ClassName);
    ----------

      2. Qomo对象仍然是基于原型构造的
      ----------
      与其它的一些OOP框架不同的是:Qomo对象仍然是基于原型构造的。这表明开发人员
    仍然可以使用prototype来修改实例和构造器的属性。重要的是,这种修改对Qomo的类
    构造系统不会造成任何负面的影响。例如:
    ----------
    function MyObject() {
      Attribute(this, 'Value', 200, 'r');
    }
    TMyObject = Class(TObject, 'MyObject');

    MyObject.prototype.Value = 100;

    var obj = new MyObject();
    alert(obj.Value);
    alert(obj.get('Value'));
    ----------

    Qomo 框架采用这种“对原型继承透明”的设计,使得它可以更容易地嵌入到其它的框
    或者系统中。大多数情况下,第三方的框架感觉不到Qomo的存在,也不会受到Qomo语法
    的任何影响。

      3. Qomo对象构造的第二种语法
      ----------
      首先是作为一种习惯,我在Qomo中实现了一个“与Delphi完全一致”的语法:
    ----------
    function MyObject() {
    }
    TMyObject = Class(TObject, 'MyObject');

    var obj = TMyObject.Create();
    ----------

    我们可以看到,其实“对象=类.Create()”在语义上更符合“类继承体系”。但是Qomo
    不希望将任何与JavaScript无关的语法或者语义“强加”给开发者。所以Qomo中仍然支持
    JavaScript中标准的构造器语法“对象=new 构造器()”。需要说明的是,这两种方法得
    到的对象实例没有任何的区别。

    Qomo实现“类.Create()”语法的另一个原因,是因为在命名空间中,我们存储的是类的引
    用,而非构造器的引用。也就是说,上面的TMyObject能在命名空间中找到:
    ----------
    cls = <a_name_space>.TMyObject;
    ----------

    这种情况下,我们只能使用“类.Create()”这种语法。当然,这种情况下,仍然可以有两
    种选择:
    ----------
    cls = <a_name_space>.TMyObject;

    // 方法一:
    var obj1 = cls.Creae();

    // 方法二:
    var obj2 = new cls.Create();

    // 错误的方法:
    // var obj3 = new cls();
    ----------

    不要试图对一个类使用new()关键字,在Qomo中,"new cls()"这样的语义是不可理解的。

  • 相关阅读:
    Iterator与 Enumeration
    多态性理解
    django---路由层
    django常用模块汇总
    django初识
    python常见模块
    python PEP8规范
    logging模块
    mysql一些了解的名词
    python 链接 mysql数据库
  • 原文地址:https://www.cnblogs.com/encounter/p/2188707.html
Copyright © 2011-2022 走看看