Using xbObjects to build three-level inheritance : Inheritance « Object Oriented « JavaScript Tutorial






<html>
<head>
<title>Example</title>
<script type="text/javascript">
/*
 * xbObjects.js
 * $Revision: 1.2 $ $Date: 2003/02/07 16:04:20 $
 */

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bob Clary code.
 *
 * The Initial Developer of the Original Code is
 * Bob Clary.
 * Portions created by the Initial Developer are Copyright (C) 2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s): Bob Clary <bc@bclary.com>
 *
 * ***** END LICENSE BLOCK ***** */

/*
ChangeLog: 2001-12-19 - bclary - changed xbException init method to
           remove possible exception due to permission denied issues
           in gecko 0.9.5+
*/

function _Classes()
{
  if (typeof(_classes) != 'undefined')
    throw('Only one instance of _Classes() can be created');

  function registerClass(className, parentClassName)
  {
    if (!className)
      throw('xbObjects.js:_Classes::registerClass: className missing');

    if (className in _classes)
      return;

    if (className != 'xbObject' && !parentClassName)
      parentClassName = 'xbObject';

    if (!parentClassName)
      parentClassName = null;
    else if ( !(parentClassName in _classes))
      throw('xbObjects.js:_Classes::registerClass: parentClassName ' + parentClassName + ' not defined');

    // evaluating and caching the prototype object in registerClass
    // works so long as we are dealing with 'normal' source files
    // where functions are created in the global context and then
    // statements executed. when evaling code blocks as in xbCOM,
    // this no longer works and we need to defer the prototype caching
    // to the defineClass method

    _classes[className] = { 'classConstructor': null, 'parentClassName': parentClassName };
  }
  _Classes.prototype.registerClass = registerClass;

  function defineClass(className, prototype_func)
  {
    var p;

    if (!className)
      throw('xbObjects.js:_Classes::defineClass: className not given');

    var classRef = _classes[className];
    if (!classRef)
      throw('xbObjects.js:_Classes::defineClass: className ' + className + ' not registered');

    if (classRef.classConstructor)
      return;

    classRef.classConstructor = eval( className );
    var childPrototype  = classRef.classConstructor.prototype;
    var parentClassName = classRef.parentClassName;

    if (parentClassName)
    {
      var parentClassRef = _classes[parentClassName];
      if (!parentClassRef)
        throw('xbObjects.js:_Classes::defineClass: parentClassName ' + parentClassName + ' not registered');

      if (!parentClassRef.classConstructor)
      {
        // force parent's prototype to be created by creating a dummy instance
        // note constructor must handle 'default' constructor case
        var dummy;
        eval('dummy = new ' + parentClassName + '();');
      }

      var parentPrototype = parentClassRef.classConstructor.prototype;

      for (p in parentPrototype)
      {
        switch (p)
        {
        case 'isa':
        case 'classRef':
        case 'parentPrototype':
        case 'parentConstructor':
        case 'inheritedFrom':
          break;
        default:
          childPrototype[p] = parentPrototype[p];
          break;
        }
      }
    }

    prototype_func();

    childPrototype.isa        = className;
    childPrototype.classRef   = classRef;

    // cache method implementor info
    childPrototype.inheritedFrom = new Object();
    if (parentClassName)
    {
      for (p in parentPrototype)
      {
        switch (p)
        {
        case 'isa':
        case 'classRef':
        case 'parentPrototype':
        case 'parentConstructor':
        case 'inheritedFrom':
          break;
        default:
          if (childPrototype[p] == parentPrototype[p] && parentPrototype.inheritedFrom[p])
          {
            childPrototype.inheritedFrom[p] = parentPrototype.inheritedFrom[p];
          }
          else
          {
            childPrototype.inheritedFrom[p] = parentClassName;
          }
          break;
        }
      }
    }
  }
  _Classes.prototype.defineClass = defineClass;
}

// create global instance
var _classes = new _Classes();

// register root class xbObject
_classes.registerClass('xbObject');

function xbObject()
{
  _classes.defineClass('xbObject', _prototype_func);

  this.init();

  function _prototype_func()
  {
    // isa is set by defineClass() to the className
    // Note that this can change dynamically as the class is cast
    // into it's ancestors...
    xbObject.prototype.isa        = null;

    // classref is set by defineClass() to point to the
    // _classes entry for this class. This allows access
    // the original _class's entry no matter how it has
    // been recast.
    // *** This will never change!!!! ***
    xbObject.prototype.classRef      = null;

    xbObject.prototype.inheritedFrom = new Object();

    function init() { }
    xbObject.prototype.init        = init;

    function destroy() {}
    xbObject.prototype.destroy      = destroy;

    function parentMethod(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
    {
      // find who implemented this method
      var className       = this.isa;
      var parentClassName = _classes[className].classConstructor.prototype.inheritedFrom[method];
      var tempMethod      = _classes[parentClassName].classConstructor.prototype[method];
      // 'cast' this into the implementor of the method
      // so that if parentMethod is called by the parent's method,
      // the search for it's implementor will start there and not
      // cause infinite recursion
      this.isa   = parentClassName;
      var retVal = tempMethod.call(this, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
      this.isa   = className;

      return retVal;
    }
    xbObject.prototype.parentMethod    = parentMethod;

    function isInstanceOf(otherClassConstructor)
    {
      var className = this.isa;
      var otherClassName = otherClassConstructor.prototype.isa;

      while (className)
      {
        if (className == otherClassName)
          return true;

        className = _classes[className].parentClassName;
      }

      return false;
    }
    xbObject.prototype.isInstanceOf    = isInstanceOf;
  }
}

// eof: xbObjects.js


</script>
</head>
<body>
<script type="text/javascript">
_classes.registerClass("Shape");

function Shape(iSides) {
    _classes.defineClass("Shape", prototypeFunction);
    this.init(iSides);
    function prototypeFunction() {
        Shape.prototype.init = function(iSides) {
            this.parentMethod("init");
            this.sides = iSides;
        };
        Shape.prototype.getArea = function () {
            return 0;
        };
    }
}

_classes.registerClass("Triangle", "Shape");

function Triangle(iBase, iHeight) {
    _classes.defineClass("Triangle", prototypeFunction);
    this.init(iBase,iHeight);
    function prototypeFunction() {
        Triangle.prototype.init = function(iBase, iHeight) {
            this.parentMethod("init", 3);
            this.base = iBase;
            this.height = iHeight;
        };
        Triangle.prototype.getArea = function () {
            return 0.5 * this.base * this.height;
        };
    }
}

_classes.registerClass("Rectangle", "Shape");

function Rectangle(iLength, iWidth) {
    _classes.defineClass("Rectangle", prototypeFunction);
    this.init(iLength, iWidth);
    function prototypeFunction() {
        Rectangle.prototype.init = function(iLength, iWidth) {
            this.parentMethod("init", 4);
            this.length = iLength;
            this.width = iWidth;
        }
        Rectangle.prototype.getArea = function () {
            return this.length * this.width;
        };
    }
}

var triangle = new Triangle(10, 40);
var rectangle = new Rectangle(20, 50);

alert(triangle.sides);
alert(triangle.getArea());

alert(rectangle.sides);
alert(rectangle.getArea());

</script>

</body>
</html>








25.9.Inheritance
25.9.1.Inheritance
25.9.2.Adding new properties and methods to the child class
25.9.3.The Call() Method
25.9.4.Using 'call method' to call the constructor of base class
25.9.5.The Apply() Method
25.9.6.Using 'apply method' to call the constructor of the base class
25.9.7.Prototype Chaining
25.9.8.All new properties and methods of the subclass must come after the assignment of the prototype property
25.9.9.Using apply function to call base constructor
25.9.10.Using call function to call the constructor from base class
25.9.11.Three-level inheritance
25.9.12.Using xbObjects to call the function from base class
25.9.13.Using xbObjects to build three-level inheritance
25.9.14.Hybrid Method for class inheritance
25.9.15.Three level inheritance by using the Hybrid Method
25.9.16.Class Inheritance