每天敲點犀牛書:子類(2)

每天敲點犀牛書:子類中,已經介紹了定義子類構造函數和方法鏈這兩部分的內容,今天開始敲關于子類的剩下兩個部分的內容。

組合 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的非抽象類。


子類的部分已經敲完了,如果其中有任何錯誤,請給我留言,不勝感激!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,482評論 2 379

推薦閱讀更多精彩內容