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()"这样的语义是不可理解的。

  • 相关阅读:
    剑指offer-二维数组中的查找
    TF-IDF(term frequency–inverse document frequency)
    Java实现中文字符串的排序功能
    当前课程
    【R】资源整理
    CentOS相关
    【转】Setting up SDL Extension Libraries on MinGW
    【转】Setting up SDL Extension Libraries on Visual Studio 2010 Ultimate
    【转】Setting up SDL Extension Libraries on Code::Blocks 12.11
    【转】Setting up SDL Extension Libraries on Visual Studio 2019 Community
  • 原文地址:https://www.cnblogs.com/encounter/p/2188707.html
Copyright © 2011-2022 走看看