Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 7004

Show
Ignore:
Timestamp:
06/11/07 19:27:12 (1 year ago)
Author:
andrew
Message:

* Added Mislav's unit tests for inheritance. The code passes current unit tests -- feel free to change base.html if you want to add more requirements.
* Added Class.add (for extending classes while preserving inheritance).
* Added the "superclass" and "subclasses" properties on classes. (i.e., Cat's superclass is Animal; Animal's subclasses are Cat and Mouse.)
* Fixed "constructor" property so that it always points back to the proper class.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • spinoffs/prototype/branches/inheritance/src/base.js

    r6813 r7004  
    22var Class = { 
    33  extend: function(parent, methods) { 
    4     if (arguments.length == 1) { 
    5       methods = parent; parent = null; 
    6     } 
    74    var method = function() { 
    85      if (!Class.extending) this.initialize.apply(this, arguments); 
    96    }; 
    107     
     8    method.superclass = parent; 
     9    method.subclasses = []; 
     10     
    1111    if (typeof parent === 'function') { 
    1212      Class.extending = true; 
    1313      method.prototype = new parent(); 
     14      method.prototype.constructor = method; 
     15 
     16      parent.subclasses.push(method); 
     17 
    1418      delete Class.extending; 
    1519    } 
     20 
     21    if (methods) Class.add(method, methods); 
     22 
     23    return method;     
     24  }, 
     25   
     26  add: function(destination, source) { 
     27    for (var name in source) Class.inherit(destination, source, name); 
     28    return destination; 
     29  }, 
    1630     
    17     if (methods) Class.inherit(method.prototype, methods); 
    18     return method;     
    19   }, 
    20    
    2131  inherit: function(destination, source, name) { 
    22     if (arguments.length === 3) { 
    23       var ancestor = destination[name], descendant = source[name], 
    24        method = descendant; 
    25         
     32    var prototype = destination.prototype; 
     33    if (prototype[name] && typeof source[name] === 'function') { 
     34      var ancestor = prototype[name], descendant = source[name], method = descendant; 
    2635      descendant = function() { 
    2736        var ref = this.parent; this.parent = ancestor; 
     
    3039        return result; 
    3140      }; 
    32      
     41       
    3342      Object.extend(descendant, { 
    3443        valueOf:  function() { return method; }, 
    3544        toString: function() { return method.toString(); } 
    3645      }); 
    37       destination[name] = descendant; 
    38     } else { 
    39       for (var property in source) { 
    40         if (destination[property] && typeof source[property] === 'function') 
    41           Class.inherit(destination, source, property); 
    42         else destination[property] = source[property]; 
     46      prototype[name] = descendant; 
     47    } else prototype[name] = source[name]; 
     48 
     49    if (destination.subclasses && destination.subclasses.length > 0) { 
     50      for (var i = 0, subclass; subclass = destination.subclasses[i]; i++) { 
     51        Class.extending = true; 
     52        Object.extend(subclass.prototype, new destination()); 
     53        subclass.prototype.constructor = subclass; 
     54        delete Class.extending; 
     55        Class.inherit(subclass, destination.prototype, name); 
    4356      } 
    4457    } 
    45     return destination; 
    4658  }, 
    4759   
    4860  mixin: function(destination, source) { 
    49     return Object.extend(destination.prototype, source); 
    50   } 
    51 }; 
    52  
    53 Class.create = function() { 
    54   return Class.extend.apply(this, arguments); 
     61    return Object.extend(destination, source); 
     62  }, 
     63   
     64  create: function(methods) { 
     65    return Class.extend(null, methods); 
     66  } 
    5567}; 
    5668 
  • spinoffs/prototype/branches/inheritance/test/unit/base.html

    r6723 r7004  
    5656     
    5757  var globalBindTest = null; 
     58   
     59   
     60  // base class 
     61  var Animal = Class.create({ 
     62    initialize: function(name) { 
     63      this.name = name; 
     64    }, 
     65    name: "", 
     66    eat: function() { 
     67      return this.say("Yum!"); 
     68    }, 
     69    say: function(message) { 
     70      return this.name + ": " + message; 
     71    } 
     72  }); 
     73 
     74  // subclass that augments a method 
     75  var Cat = Class.extend(Animal, { 
     76    eat: function(food) { 
     77      if (food instanceof Mouse) return this.parent(); 
     78      else return this.say("Yuk! I only eat mice."); 
     79    } 
     80  }); 
     81 
     82  // empty subclass 
     83  var Mouse = Class.extend(Animal); 
    5884 
    5985  new Test.Unit.Runner({ 
     
    285311        assert(Prototype.Browser.Gecko); 
    286312      }  
     313    }}, 
     314     
     315    testInstantiation: function() { with(this) {  
     316      var pet = new Animal("Nibbles"); 
     317      assertEqual("Nibbles", pet.name, "property not initialized"); 
     318      assertEqual('Nibbles: Hi!', pet.say('Hi!')); 
     319      assertEqual(Animal, pet.constructor, "bad constructor reference"); 
     320      assertUndefined(pet.superclass); 
     321    }}, 
     322 
     323    testInheritance: function() { with(this) { 
     324      var tom = new Cat('Tom'); 
     325      assertEqual(Cat, tom.constructor, "bad constructor reference"); 
     326      assertEqual(Animal, tom.constructor.superclass, 'bad superclass reference'); 
     327      assertEqual('Tom', tom.name); 
     328      assertEqual('Tom: meow', tom.say('meow')); 
     329      assertEqual('Tom: Yuk! I only eat mice.', tom.eat(new Animal)); 
     330    }}, 
     331 
     332    testSupercall: function() { with(this) { 
     333      var tom = new Cat('Tom'); 
     334      assertEqual('Tom: Yum!', tom.eat(new Mouse)); 
     335    }}, 
     336 
     337    testAddingInstanceMethod: function() { with(this) { 
     338      var tom   = new Cat('Tom'); 
     339      var jerry = new Mouse('Jerry'); 
     340       
     341      Class.add(Animal, { 
     342        sleep: function() { 
     343          return this.say('ZZZ'); 
     344        } 
     345      }); 
     346       
     347      Class.add(Mouse, { 
     348        sleep: function() { 
     349          return this.parent() + " ... no, can't sleep! Gotta steal cheese!"; 
     350        } 
     351      }); 
     352       
     353      assertEqual('Tom: ZZZ', tom.sleep(), "added instance method not available to subclass"); 
     354      assertEqual("Jerry: ZZZ ... no, can't sleep! Gotta steal cheese!", jerry.sleep()); 
    287355    }} 
    288356