zoukankan      html  css  js  c++  java
  • Qomolangma实现篇(七):Qomo的接口机制

    ================================================================================
    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)
    项目团队:../../Qomo_team.txt
    有贡献者:JingYu(zjy@cnpack.org)
    ================================================================================


    一、Qomolangma中的接口(Interface.js)
    ~~~~~~~~~~~~~~~~~~

    在做AOP(面向切面编程)系统之前,我一直在想:有什么必要在JavaScript中做“接口(Interface)”的
    机制。——当然,这也说明,你可能需要通过阅读迟些提供的、关于AOP框架的文档,才能理解如何使用
    Qomo中强大的接口机制。^.^

    接口是现代软件工程中的一种常用工具,它的出现使设计人员更多的关注于功能的“对外表现”,而非
    “内部实现”。在软件模型设计中,类图通常用于描述设计的细部,而接口则更常用于描述模块、层次
    间的交互关系。

    然而这仍然停留在“设计”层面。在C++中,你会看到“接口是抽象类”这样的描述。换个说法,C++认
    为接口只是“没有实现的类”。如果这样来描述的话,JavaScript中就没有必要实现一个“接口机制”。

    所以,我们看到altas中,接口的实现就简单得多。例如:
    ---------------
    // 1. 声明接口
    Web.IArray = function() {
      this.get_length = Function.abstractMethod;
      this.getItem = Function.abstractMethod;
    }

    // 2. 注册接口,以及为类注册接口
    Type.registerInterface("Web.IArray");
    Type.registerClass('Web.UI.Data.DataControl', Web.UI.Control, Web.IArray);

    // 3. 接口操作(方法)
    Function.prototype.implementsInterface = function(interfaceType) { ... }
    Function.prototype.isImplementedBy = function(instance) { ... }
    ---------------

    同样的原因,altas中的接口也仅在极少数的地方得以应用。例如在属性检测(类似反射)时,有这样的
    代码:
    ---------------
    Web.TypeDescriptor.getProperty = function(instance, propertyName, key) {
        if (Web.ICustomTypeDescriptor.isImplementedBy(instance)) {
            return instance.getProperty(propertyName, key);
        }
        // ...
    }
    ---------------

    我们看到atlas中的接口主要用于“检测一个类(或对象实例)”是否实现过某接口。这用于保障后续代
    码能安全的执行。本质上,altas是用registerInterface()的机制,来使开发人员不必使用'in'运算来
    检测属性。因而上面的代码事实上写成这样也没有关系:
    ---------------
    Web.TypeDescriptor.getProperty = function(instance, propertyName, key) {
        if ('getProperty' in instance) {
            return instance.getProperty(propertyName, key);
        }
        // ...
    }
    ---------------

    ——然而,这仅仅只是“接口机制”应用的一个方面而已。

    在Qomo中,接口不单单是描述,也是实现。实现的接口(的方法)可以被调用,这与Win32中的COM机制
    是类同的。Qomo在实现了更加完整的接口特性,并在这样的基础之上,完整地实现了AOP的种种特性。

    与JSEnhance.js一样,Qomo中的Interface.js可以脱离Qomo项目运行。


    二、概念:接口是描述,也是实现
    ~~~~~~~~~~~~~~~~~~

    接口最基础的定义就是“描述对象的行为”。所以根本上来讲,下面的声明:
    ---------------
    Web.IArray = function() {
      this.get_length = Function.abstractMethod;
      this.getItem = Function.abstractMethod;
    }
    Type.registerClass('Web.UI.Data.DataControl', Web.IArray);
    ---------------

    将表明下列的含义:
      - 类Web.UI.Data.DataControl实现了Web.IArray接口
      - 类Web.UI.Data.DataControl的实例(instance)将必然具有如下方法:get_length()和getItem()

    接口描述了对象实例的行为能力,只是接口机制的一个方面。在COM框架、以及一些其它高级语言的接
    口语义中,接口也表明了一种实现。这种“实现”体现在三个方面:
      - 用户可以通过接口,来持有一组对象方法的引用
      - 用户可以通过聚合、包含和委托等技术,来使对象包含多个接口
      - 用户代码可以通过一个接口,来查询“实现接口的对象”的更多接口(以及行为能力)

    例如(delphi source):
    ---------------
    type
      IMyObject = interface
        // [a guid for COM framework]
        function run(): boolean;
      end;

      TMyObject = Class(TObject, IMyObject)
        //...
      end;

    var
      obj : TMyObject;
      intf : IMyObject;


    // ...
    obj := TMyObject.Create;
    if obj.GetInterface(IMyObject, intf) then
      intf.run();
    ---------------

    在这个例子中,接口IMyObject可以用于变量的类型声明。而将intf声明为IMyObject类型,也表明
    intf接口指针“从对象实例中取得有效的接口实现”。GetInterface()是COM中的QueryInterface()
    的另一个实现,用于从obj中取接口引用。

    一旦intf变量成功的获取一个“已实现的接口(一组对象方法指针的引用)”,那么接下来就可以调
    用这些接口方法了,例如执行:intf.run()。


    三、Qomo中的接口之一:接口的基本语法及实现
    ~~~~~~~~~~~~~~~~~~

      1. 接口声明
      ----------
      Qomo中的接口声明比较简单,采用与altas(以及其它OOP框架)兼容的代码:
    ---------------
    IMyObject = function() {
      this.method = Abstract;
    }

    IMyObject2 = function() {
      this.method = Abstract;
      this.method2 = Abstract;
    }
    ---------------

    Abstract是一个在Qomo中全局提供的、类似关键字的函数。这在讲述OOP框架时已经提及过。不过它
    现在被从Object.js中移到了Interface.js中。Abstract()调用的结果是触发一个异常,这也意味着
    使用new()创建出的任何一个接口实例,其方法调用都将失败。

    Qomo中允许使用类似继承的方式来声明接口。但Qomo不推荐接口继承,因此如果你希望接口“继承
    自”其它接口,那么建议用下面的代码来声明:
    ---------------
    IMyObjectEx = function() {
      IMyObject.call(this);

      this.method_ex = Abstract;
    }
    ---------------

    使用显式的IMyObject.call()的原因,是使得开发人员在阅读代码时能清晰地了解这里有一个继
    承关系。——换而言之,父级接口的修改将影响到子级接口的声明。

    当然,这种方式也给一些接口声明带来了方便,例如在Interface.js中声明的:
    ---------------
    IAspectedClass = function() {
      IClass.call(this);
      IJoPoints.call(this);
    }
    ---------------

    最后补充一点:不要在接口声明中对构造器加入口参数。因为接口不需要、也不应当通过可变参
    数来“创建”。


      2. 接口注册
      ----------
      Qomo中接口注册有两种形式:
        - Interface.RegisterInterface(obj, intf1 [, intf2 ... intfn]);
        - Class(<BaseClass>, <ConstructorName> [, intf1 .. intfn]);

    第一种形式可以为任何的JavaScript对象,包括原生的对象、函数等注册接口。这种形式存在一
    个完全等义的全局函数RegisterInterface()。——事实上这指向同一个函数——在Qomo的内部代
    码中,只使用Interface.RegisterInterface()这种完整形式。

    第二种形式在Object.js中实现。Interface.js中用了少量的代码来使得“类的所有实例、和子类
    拥有相同的接口”。如果你使用Qomo的OOP系统,那么只需要在调用Class()进行类注册的时候加
    上一个实现的接口表即可。

    可以通过Aggregate()来注册“聚合的”接口,这一部分放在后面单独讲述。

    作为特例:对undefined和null对象注册接口返回一个-1值。


      3. 接口查询
      ----------
      Qomo中可用下面的形式来获取对象(含Qomo和原生的JavaScript对象)的注册接口:
        - Interface.QueryInterface(obj, aInterface);

    与注册过程相同,接口查询也有一个完全等义的全局函数QueryInterface(),并且Qomo内部只使
    用完整形式的调用。

    QueryInterface()将返回一个接口的实现,也就是一个可以调用的接口(对象/指针)。例:
    ---------------
    function MyObject() { }
    TMyObject = Class(TObject, 'MyObject');

    var obj = new MyObject();
    var intf = QueryInterface(obj, IObject);

    document.writeln(intf.hasAttribute('Name'));
    // output: false
    ---------------

    如果查询null或undefined的接口,或者传入了无效的、未注册的接口同,QueryInterface()将
    抛出异常。如果返回undefined,则表明不包含该接口。——此时在用户代码中,该接口引用应
    该不被继续处理,而不是盲处理。

    QueryInterface()能够查询通过Aggregate()来注册“聚合的”接口。

      4. 其它
      ----------
      接口系统提供一个函数来检测一个“声明”是否是接口。如果要在QueryInterface()之前检测一
    个接口是否被注册过,可以使用如下函数(返回true/false):
      - Interface.IsInterface(intf);

    如果一段声明代码没有(隐式地)被注册到Qomo系统中,那么使用该声明来QueryInterface()会导致
    一个异常。——将“接口声明”注册到Qomo的行为隐式地包含在RegisterInterface()和Class()等
    调用的过程中。

    如果你真的需要在一个接口被实现之前就注册它,那么你可以将它注册给IInterface。尽管这看起
    来不怎么合理,但在Qomo中,这是唯一能被理解的方式:
    ---------------
    IMyCustomIntf = function() { ... };
    Interface.RegisterInterface(IInterface, IMyCustomIntf);
    ---------------

    在Qomo内核中是具有“将声明直接注册(封装)成接口”的能力的,这就是Interface.js中实现过的
    warpInterface()函数。但它不提供给用户代码使用。系统中通过这种方法注册的接口包括:
      - IInterface
      - IJoPoint
      - IMuEvent
      - IJoPoints

    如果需要,你可以将自己需要(预先)注册的接口加入到Interface.js的代码中。当然,这并不是一
    种推荐的形式。

    Qomo提供一些对接口的演示和示例。在Qomo代码包中:
      /Framework/DOCUMENTs/T_InterfaceQuery.html     : 演示接口机制的基本原理
      /Framework/DOCUMENTs/T_InterfaceAggregate.html : 演示脱离Qomo使用接口、聚合的基本使用
      /Framework/DOCUMENTs/BaseObjectDemo4.html      : Qomo中的接口系统的使用示例和基本特性


    四、Qomo中的接口之二:(内部)聚合接口的语法
    ~~~~~~~~~~~~~~~~~~

    在COM中,分离接口的方法包括“聚合”和“包含”;在Delphi中,在编译器级别提出了“委托”来
    实现接口分离的技术。三者之间只是实现技术和时间上的差异,其效果是基本一致的。

    基本上来说,接口隔离原则(Interface Segregation Principle)基于“使用多个专门的接口比使用
    单一的总接口总要好”。因此,这事实上是使一个对象同时具有多个接口的技术。在Qomo中,下面两
    种语法都可以实现这个目标:
        - Interface.RegisterInterface(obj, intf1 [, intf2 ... intfn]);
        - Class(<BaseClass>, <ConstructorName> [, intf1 .. intfn]);

    然而,这两种接口注册的方式要求对象/类显示的具有接口指定的方法。也就是说:如果对象obj注册
    了接口intf,则 intf.XXXXX 方法将直接指向obj.XXXXX方法。

    然而,我们举个例子来说: TMyObject作为类,总是有对象构造过程的,这表明TMyObject应该具备这
    样接口:
    ---------------
    IObjectConstructor = function() {
      this.OnNewInstance = Abstract;
    }
    ---------------

    但我们也知道,我们不能把一个OnNewInstnace事件放在TMyObject类上,或者放在obj实例上。——因
    为它并不是实例的使用者所关注的。而且,更重要的是,“对象构造过程”在Qomo中被隐含在Class()
    的实现代码内部,我们需要一种机制将它的这个能力public出来,这样才能做到“接口是实现”。

    在Delphi中,接口的声明方法可以“在对象的私有域中实现”。这时可以通过接口“在对象/单元外”
    访问该方法。这体现了“接口表现对象的能力,而不关注具体实现(是私有、公有或者委托)”这一接口
    的基本原则。

    在Qomo中,综合上述的这些问题及其解决方案,实现了“(内部的)聚合接口”这一特殊的机制。

      1. 声明聚合(在函数内部)
      ----------
      Qomo的聚合首先是“为了表现目标的内部具备的行为能力”而提供的。而JavaScript中,提供行为能
    力的,只有对象(构造器)和函数。因此,“声明聚合”的行为通常应当发生在一个函数的内部。例如:
    ---------------
    function MyFunc() {
      // Aggregate()与RegisterInterface()有相同的入口参数
      var intfs = Aggregate(MyFunc, intf1 [, intf2 .. intfn]);
    }
    ---------------

    由于这会导致每次执行MyFunc都会调用Aggregate(),因此Qomo推荐如下的语法来声明一个带有聚合接口
    的函数:
    ---------------
    MyFunc = function () {
      function _MyFunc() {
        // (函数自身的实现代码...)
      }

      var intfs = Aggregate(_MyFunc, intf1 [, intf2 .. intfn]);
      // (实现接口 ...)

      return _MyFunc;
    }();
    ---------------

    由于这样做仅是为了避免重复地调用Aggregate(),因此事实上用户也可以这样来声明(尽管同样麻烦):
    ---------------
    function MyFunc () {
      if (a_aggregated_tag == false) {
        var intfs = Aggregate(_MyFunc, intf1 [, intf2 .. intfn]);
        // (实现接口 ...)
        a_aggregated_tag = true;
      }

      // (函数自身的实现代码...)
    }
    ---------------


      2. 实现接口
      ----------
      Aggregate()只是建立对象与接口之间的关系并返回一个“Interfaces对象”,这个接口集合中所有的
    接口都是未被实现过的。这仍然需要由用户代码来完成,例如:
    ---------------
    IMyFunc = function() {
      this.getLength = Abstract;
    }

    MyFunc = function () {
      var arr = new Array(5);

      function _MyFunc() {
        // (函数自身的实现代码...)
      }

      // 0). 声明聚合
      var intfs = Aggregate(_MyFunc, IMyFunc);
      // 1). 取指定接口的一个引用
      var intf = intfs.GetInterface(IMyFunc);
      // 2). 实现接口方法
      intf.getLength = function() {
        return arr.length;
      }

      return _MyFunc;
    }();
    ---------------

    上面的代码中,“Interfaces对象”intfs的GetInterface()方法用于得到一个接口的引用,而接下来的
    代码则描述该接口如何被实现。——在这里,可以使用“聚合”、“包含”或者“委托”这些技术之一。
    (目前,)Qomo没有直接提供这三种技术的实现方案,只是“老老实实地”逐一实现了该接口的各个方法。


      3. 使用接口
      ----------
      前面已经提到过,Aggregate()内部聚合的接口的使用,与普通注册的接口是一样的。如上例:
    ---------------
    var intf = Interface.QueryInterface(MyFunc, IMyFunc);
    document.writeln('the length is: ', intf.getLength());
    ---------------


    五、Qomo中的接口之三:接口的实现
    ~~~~~~~~~~~~~~~~~~

      接口的使用示例请参见/Framework/DOCUMENTs/BaseObjectDemo4.html。内部聚合技术的使用示例可
    参见/Framework/DOCUMENTs/T_InterfaceAggregate.html。下面讲述Qomo中实现这些技术的一些细节。

      1. 基本接口功能的实现
      ----------
    Qomo中的接口采用这样的结构来实现Interface关键字及相关的语法:
    ---------------
    Interface = function() {
      //...

      function _Interface(obj) { /* Intf1 .. Intfn */
        //...
      }
      _Interface.QueryInterface = function(obj, intf) { ... }
      _Interface.RegisterInterface =_Interface;
      _Interface.IsInterface = isInterface;

      return _Interface;
    }();
    ---------------

    其中_Interface()用于实现RegisterInterface()与Interface(),它们是等义的。_Interface()首先
    调用warpInterface()将除第一个参数之外的其它参数转变成接口,这其实是在该接口声明(函数)上附
    加一个'_INTFHANDLE_'属性。——这是目前为止Qomo中唯一尝试改变对象对外的属性的地方。

    由于接口本身只是“声明”而不能直接引用,因此加上该属性并不会导致什么麻烦。在warpInterface()
    对该属性做了检测,以避免用户通过修改这个值来伪造、套取接口。——尽管这对用户来说也没有什么
    意义。^.^

    warpInterface()是真正实现RegisterInterface()的关键代码。_Interface()中的另一部分代码用于处
    理与"聚合接口(Aggregate)"相关的一些逻辑。——这在后面进一步叙述。

    接口中另一个重要的函数是_Interface.QueryInterface。它的实现相对要复杂些,因为它要处理:
      - 对象的接口可以在类注册中通过Class()注册到指定的类,而不必为每一个对象实例注册;
      - Qomo对象或者其它JavaScript原生对象都可以具有自己的接口注册;
      - 函数可以注册内部的聚合接口。
    QueryInterface()必要有能力从所有这些可能注册过接口的“对象”中查询到接口。而且还要返回该
    接口的一个“有效的、能调用接口方法的”实现。

    “实现接口”的基本原理可以参见/Framework/DOCUMENTs/T_InterfaceQuery.html。其基本步骤是:
      - 创建指定接口的实例:intf = new IYour_Intf();
      - 为接口的每一个“虚方法”重新封装一个调用方法,该方法将实现该接口的对象的同名方法;
      - 特殊处理:任何对象都可以有IInterface接口,将持有QueryInterface()的一个引用。


      2. 聚合接口功能的实现
      ----------
    在对_Interface()的实现过程中特殊处理了Aggregate():
    ---------------
        // register aggregated interfacds. is special!
        if ((typeof obj == 'function') || (obj instanceof Function)) {
          if (_Interface.caller===Aggregate && this!==Aggregate) {
            // ...
          }
        }
    ---------------

    也就是说如果:
      - "obj"是一个函数对象,且
      - 通过调用Aggregate()函数来注册它的接口,且
      - this对象不是Aggregate()自身

    则_Interface()将尝试把目标对象"obj"与一个"Interfaces"对象(this)分别添加到两个数组:
    ---------------
       $Aggrs.push(obj);   // 拥有接口的对象(总是一个函数)
       $Aggri.push(this);  // 所拥有的接口集合对象
    ---------------

    而在Aggregate()工具函数中,将两次调用Interface.RegisterInterface(),也就是前面提到的
    _Interface()函数。这两次调用的作用并不一样:

    第一次采用标准格式调用RegisterInterface(),这时传入的this将是_Aggregate()函数的引用:
    ---------------
          Interface.RegisterInterface.apply(_Aggregate, arguments);
    ---------------

    代码中,由于Aggregate()与RegisterInterface()的入口参数声明是一样的,因此apply()的第
    二个参数直接使用了arguments。这样就首先将接口注册到了指定的对象上。

    第二次采用特殊格式调用RegisterInterface(),这时传入的this将是“接口集合”Interfaces
    的一个实例,而唯一的一个入口参数foo,将指向被注册接口的函数对象引用:
    ---------------
          return Interface.RegisterInterface.call(new Interfaces(arguments), foo);
    ---------------

    这次调用,RegisterInterface()将返回传入的this,也就是“接口集合”对象。这样做只是为了
    省掉在_Aggregate()中一个临时变量。

    上面的代码流程表明,一个“(内部的)聚合接口”至少具有拥有如下信息:
      - 一个Interfaces对象。用于存放所有实现过的接口列表;
      - 在_Interface()中的Interfaces与foo的对照表“$Aggrs与$Aggri”中存放一对信息。用于实
        现getAggrInterfaces()函数,并用于支持QueryInterface()对该函数foo的查询;
      - 在实现接口的函数内部,持有Interfaces对象的一个引用。它用于各接口的实现。


    五、其它
    ~~~~~~~~~~~~~~~~~~

      1. 缺省注册的接口
      ----------
    Qomo为的一些全局对象、函数和基类注册了缺省的接口。这些接口定义在Interface.js中。缺
    省注册的对象包括:
      注册到        接口             说明
      --------------------------------------------------------------------
      *             IInterface       包括undefined、null在内的所有JS原生对象和Qomo对象等
      $import()     IImport          (beta版暂未实现)
      MuEvent()     IMuEvent         所有多投事件句柄: 由MuEvent()构造的实例(beta版暂未实现)
      JoPoint()     IJoPoint         所有的联接点: 由JoPoint()构造的实例
      TObject()     IObject          Qomo OOP系统的基类
      TClass        IClass           所有通过Class()注册的类
      Class()       IClassRegister   类注册函数
      Class()等     IJoPoints        Qomo内部为一些能提供AOP能力的函数注册了IJoPoints接口
      Aspect()      IAspect          Qomo AOP系统的切面基类


      2. 如何为特定的对象(如JavaScript原生对象)注册接口
      ----------
      在Qomo的(目前的)发布版本中,对一些JavaScript原生对象未提供接口支持。例如对Array()对象。
    这只是因为Qomo没有觉察到这样做的必要,因而没有加入相关的代码。这在技术实现上其实非常简
    单。下面参考对MuEvent()的实现,介绍一下Array()的接口实现方法:
    ---------------
    // 以下代码填写在interface.js中

    // 1. 接口声明
    IArray = function() {
      this.push = Abstract;
      this.pop = Abstract;
      // ...
    }

    // 2. Interface()的实现代码中,warpInterface()函数声明之后加入代码
    warpInterface(IArray);

    // 3. 在isImplemented()的实现代码中,修改(添加)如下代码
    switch (intf) {
      // ...
      case IArray:   return this instanceof Array;
    }
    ---------------

    这样就完成了。


      3. 将来的版本
      ----------
      可能在更晚些的版本中,Qomo会提供“聚合”、“包含”和“委托”这些技术的完整、具体的实
    现(例如提供工具函数)。但目前的版本中,只提供了对“(内部的)聚合接口”这种需求的解决方法。

    在Qomo V1正式发布的版本中,将包含对接口中的get/setter的实现。目前用户仍然只能使用类似于
    intf.getLength()这样的方法去访问特性(attribute)。除此之外,接口中的“索引指示器”仍在考
    虑之中。

    与C#等高级语言一样,Qomo的接口对“方法、属性、索引指示器和事件”做出了思考或者实现,但
    不支持对“常量、域、操作符、构造函数或析构函数”这些进行接口声明。这是“接口”本身的语
    义所决定的。

  • 相关阅读:
    PHP运行及语句及逻辑
    数据库基础,表及SQL语句
    php后台修改人员表信息
    php后台增加删除修改跳转页面
    用PHP访问数据库
    php登录注册页面及加载
    php做登录注册页面及加载
    实现基于物理的渲染
    Tile-Based Deferred Rendering
    矩阵基础 2
  • 原文地址:https://www.cnblogs.com/encounter/p/2188703.html
Copyright © 2011-2022 走看看