JavaScript: The Definitive Guide, Sixth Editio javaScript权威指南(第6版) pdf 文字版-文字版, javascript电子书, 和javascript Class有关的电子书:

9.7.4 Class Hierarchies and Abstract Classes

9.7.4 Class Hierarchies and Abstract Classes

In the previous section you were urged to “favor composition over inheritance.” But to illustrate this principle, we created a subclass of Set. We did this so that the resulting class would be instanceof Set, and so that it could inherit the useful auxiliary Set methods like toString() and equals(). These are valid pragmatic reasons, but it still would have been nice to be able to do set composition without subclassing a concrete implementation like the Set class. A similar point can be made about our SingletonSet class from Example 9-12 —that class subclassed Set, so that it could inherit the auxiliary methods, but its implementation was completely different than its superclass. SingletonSet is not a specialized version of the Set class, but a completely different kind of Set. SingletonSet should be a sibling of Set in the class hierarchy, not a descendant of it.

The solution in classical OO languages and also in JavaScript is to separate interface from implementation. Suppose we define an AbstractSet class which implements the auxiliary methods like toString() but does not implement the core methods like foreach(). Then, our set implementations, Set, SingletonSet, and FilteredSet, can all be subclasses of AbstractSet. FilteredSet and SingletonSet no longer subclass an unrelated implementation.

Example 9-16 takes this approach further and defines a hierarchy of abstract set classes. AbstractSet defines only a single abstract method, contains(). Any class that purports to be a set must define at least this one method. Next, we subclass AbstractSet to define AbstractEnumerableSet. That class adds abstract size() and foreach() methods, and defines useful concrete methods (toString(), toArray(), equals(), and so on) on top of them. AbstractEnumerableSet does not define add() or remove() methods and represents read-only sets. SingletonSet can be implemented as a concrete subclass. Finally, we define AbstractWritableSet as a subclass of AbstractEnumerableSet. This final abstract set defines the abstract methods add() and remove(), and implements concrete methods like union() and intersection() that use them. AbstractWritableSet is the appropriate superclass for our Set and FilteredSet classes. They are omitted from this example, however, and a new concrete implementation named ArraySet is included instead.

Example 9-16 is a long example, but worth reading through in its entirety. Note that it uses Function.prototype.extend() as a shortcut for creating subclasses.

Example 9-16. A hierarchy of abstract and concrete Set classes

// A convenient function that can be used for any abstract method function abstractmethod() { throw new Error("abstract method"); }


* The AbstractSet class defines a single abstract method, contains().

*/ function AbstractSet() { throw new Error("Can't instantiate abstract classes");} AbstractSet.prototype.contains = abstractmethod;


*NotSet is a concrete subclass of AbstractSet.*The members of this set are all values that are not members of some * other set. Because it is defined in terms of another set it is not*writable, and because it has infinite members, it is not enumerable.*All we can do with it is test for membership.*Note that we're using the Function.prototype.extend() method we defined*earlier to define this subclass. */

var NotSet = AbstractSet.extend( function NotSet(set) { this.set = set; }, {

contains: function(x) { return !this.set.contains(x); }, toString: function(x) { return "~" + this.set.toString(); }, equals: function(that) {

return that instanceof NotSet && this.set.equals(that.set); } } );


*AbstractEnumerableSet is an abstract subclass of AbstractSet.*It defines the abstract methods size() and foreach(), and then implements*concrete isEmpty(), toArray(), to[Locale]String(), and equals() methods * on top of those. Subclasses that implement contains(), size(), and foreach()*get these five concrete methods for free. */

var AbstractEnumerableSet = AbstractSet.extend( function() { throw new Error("Can't instantiate abstract classes"); }, {

size: abstractmethod, foreach: abstractmethod, isEmpty: function() { return this.size() == 0; }, toString: function() {

var s = "{", i = 0;

9.7 Subclasses | 235

this.foreach(function(v) { if (i++ > 0) s += ", "; s += v;


return s + "}"; }, toLocaleString : function() {

var s = "{", i = 0;

this.foreach(function(v) { if (i++ > 0) s += ", "; if (v == null) s += v; // null & undefined else s += v.toLocaleString(); // all others


return s + "}"; }, toArray: function() {

var a = []; this.foreach(function(v) { a.push(v); }); return a;


equals: function(that) { if (!(that instanceof AbstractEnumerableSet)) return false; // If they don't have the same size, they're not equal if (this.size() != that.size()) return false; // Now check whether every element in this is also in that. try {

this.foreach(function(v) {if (!that.contains(v)) throw false;}); return true; // All elements matched: sets are equal.

} catch (x) { if (x === false) return false; // Sets are not equal throw x; // Some other exception occurred: rethrow it.

} } });


*SingletonSet is a concrete subclass of AbstractEnumerableSet.*A singleton set is a read-only set with a single member. */

var SingletonSet = AbstractEnumerableSet.extend( function SingletonSet(member) { this.member = member; }, {

contains: function(x) { return x === this.member; }, size: function() { return 1; }, foreach: function(f,ctx) {, this.member); }

} );


*AbstractWritableSet is an abstract subclass of AbstractEnumerableSet.*It defines the abstract methods add() and remove(), and then implements*concrete union(), intersection(), and difference() methods on top of them. */

var AbstractWritableSet = AbstractEnumerableSet.extend( function() { throw new Error("Can't instantiate abstract classes"); },

{ add: abstractmethod, remove: abstractmethod, union: function(that) {

var self = this; that.foreach(function(v) { self.add(v); }); return this;


intersection: function(that) { var self = this; this.foreach(function(v) { if (!that.contains(v)) self.remove(v);}); return this;


difference: function(that) { var self = this; that.foreach(function(v) { self.remove(v); }); return this;

} });


*An ArraySet is a concrete subclass of AbstractWritableSet.*It represents the set elements as an array of values, and uses a linear*search of the array for its contains() method. Because the contains()*method is O(n) rather than O(1), it should only be used for relatively*small sets. Note that this implementation relies on the ES5 Array methods*indexOf() and forEach(). */ var ArraySet = AbstractWritableSet.extend(

function ArraySet() { this.values = []; this.add.apply(this, arguments);


{ contains: function(v) { return this.values.indexOf(v) != -1; }, size: function() { return this.values.length; }, foreach: function(f,c) { this.values.forEach(f, c); }, add: function() {

for(var i = 0; i < arguments.length; i++) { var arg = arguments[i]; if (!this.contains(arg)) this.values.push(arg);


return this; }, remove: function() {

for(var i = 0; i < arguments.length; i++) { var p = this.values.indexOf(arguments[i]); if (p == -1) continue; this.values.splice(p, 1);

} return this; } } );

9.7 Subclasses | 237

友情链接It题库(| 版权归yishouce.com所有| 友链等可联系|粤ICP备16001685号-1