在每天敲點犀牛書:子類中,已經介紹了定義子類、構造函數和方法鏈這兩部分的內容,今天開始敲關于子類的剩下兩個部分的內容。
組合 vs 子類
面向對象編程中有一條廣為人知的設計原則:組合優于繼承。下面利用組合的原理定義一個新的集合實現。
/*
* 實現一個FilteredSet,它包裝某個指定的“集合”對象,
* 并對傳入add()方法的值應用了某種指定的過濾器
* “范圍”類中的其他所有的核心方法延續到包裝后的實例中
*/
var FilteredSet = Set.extend(
function FilteredSet(set, filter) { // 構造函數
this.set = set;
this.filter = filter;
},
{ //實例方法
add: function() {
//如果已有過濾器,直接使用它
if(this.filter) {
for(var i = 0; i < arguments.length; i++) {
var v = arguments[i];
if(!this.filter(v))
throw new Error("FilteredSet: value " + v + " rejected by filter");
}
//調用set中的add()方法
this.set.add.apply(this.set, arguments);
return this;
}
},
//剩下的方法保持不變
remove: function() {
this.set.remove.apply(this.set, arguments);
return this;
},
contains: function(v) {return this.set.contains(v);},
size: function() {return this.set.size();},
foreach: function(f, c) {this.set.foreach(f, c);}
});
如上,使用組合的一個好處是,只需創建一個單獨的FilteredSet子類即可。可以利用這個類的實例來創建任何帶有成員限制的集合實例,比如:
//上一節提到的NonNullSet
var s = new FilteredSet(new Set(), function(x) {return x !== null;});
//甚至可以對已經過濾的集合進行過濾
var t = new FilteredSet(s, function(x) { return !(x instanceof Set); });
類的層次結構和抽象類
下面定義了一個層次結構的抽象的集合類,代碼很長,但還是應該閱讀一遍的。注意這里用到了Function.prototype.extend()作為創建子類的快捷方式。
// 這個函數可以用做任何抽象方法,非常方便
function abstractmethod() {throw new Error("abstract method");}
//AbstractSet類定義了一個抽象方法:contains()
function AbstractSet() { throw new Error("Can't instantiate abstract classes");}
AbstractSet.prototype.contains = abstractmethod;
/*
* NotSet是AbstractSet的一個非抽象子類
* 所有不在其他集合中的成員都在這個集合中
* 因為它是在其他集合是不可寫的條件下定義的
* 同時由于它的成員是無限個,因此它是不可枚舉的
* 我們只能用它來檢測元素成員的歸屬情況
* 注意,我們使用了Function.prototypr.extend()方法來定義這個子類
*/
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是AbstractSet的一個抽象子類
* 它定義了抽象方法size()和foreach()
* 然后實現了非抽象方法isEmpty()、toArray()、to[locale]String()和equals()方法
* 子類實現了contains()、size()和foreach(),這三個方法可以很輕易地調用這5個非抽象方法
*/
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;
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();//其他情況
});
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(this.size() != that.size()) return false;
//檢查每一個元素是否也在that中
try {
this.foreach(function(v) {if(!that.contains(v)) throw false;});
return true; //所有元素都匹配:集合相等
} catch(x) {
if(x === false) return false; //集合不相等
throw x; //發生了其他的異常:重新拋出異常
}
}
}
);
/*
* SingletonSet是AbstractEnumerableSet的非抽象子類
* singleton集合是只讀的,它只包含一個成員
*/
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) {f.call(ctx, this.member);}
}
);
/*
* AbstractWritableSet是AbstractEnumerableSet的抽象子類
* 它定義了抽象方法add()和remove()
* 然后實現了非抽象方法union()、intersection()和difference()
*/
var AbstractWritableASet = 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;
}
}
);
/*
* ArraySet是AbstractWritableSet的非抽象子類
* 它以數組的形式表示集合中的元素
* 對于它的contains()方法使用了數組的線性查找
* 因為contains()方法的算法復雜度為O(n)而不是O(1)
* 它非常適用于小型的集合,注意,這里的實現用到了ES5的數組方法indexOf()和forEach()
*/
var ArraySet = AbstractWritableASet.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.valuse.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;
}
}
);
在這個抽象類和非抽象Set類的層次結構中,AbstractSet只定義了一個抽象方法:contains()。然而定義了AbstractSet的子類AbstractEnumerableSet,這個類增加了抽象的size()和foreach()方法,而且定義了一些有用的非抽象方法toString()等,AbstractEnumerableSet并沒有定義add()和remove()方法,它代表只讀集合。SingletonSet可以實現為非抽象類。最后定義了AbstractEnumerableSet的子類AbstractWritableSet,這個final抽象集合定義了抽象方法add()和remove(),并實現了union()等具體方法。AbstractWritableSet是Set和FilteredSet類相應的父類。但這個例子中并沒有實現它,而是實現了一個新的ArraySet的非抽象類。
子類的部分已經敲完了,如果其中有任何錯誤,請給我留言,不勝感激!