JavaScript中常見的十五種設計模式

来源:https://www.cnblogs.com/imwtr/archive/2018/08/10/9451129.html
-Advertisement-
Play Games

在程式設計中有很多實用的設計模式,而其中大部分語言的實現都是基於“類”。 在JavaScript中並沒有類這種概念,JS中的函數屬於一等對象,在JS中定義一個對象非常簡單(var obj = {}),而基於JS中閉包與弱類型等特性,在實現一些設計模式的方式上與眾不同。 本文基於《JavaScript ...


在程式設計中有很多實用的設計模式,而其中大部分語言的實現都是基於“類”。

在JavaScript中並沒有類這種概念,JS中的函數屬於一等對象,在JS中定義一個對象非常簡單(var obj = {}),而基於JS中閉包與弱類型等特性,在實現一些設計模式的方式上與眾不同。

本文基於《JavaScript設計模式與開發實踐》一書,用一些例子總結一下JS常見的設計模式與實現方法。文章略長,自備瓜子板凳~

 

設計原則

單一職責原則(SRP)

一個對象或方法只做一件事情。如果一個方法承擔了過多的職責,那麼在需求的變遷過程中,需要改寫這個方法的可能性就越大。

應該把對象或方法劃分成較小的粒度

最少知識原則(LKP)

一個軟體實體應當 儘可能少地與其他實體發生相互作用 

應當儘量減少對象之間的交互。如果兩個對象之間不必彼此直接通信,那麼這兩個對象就不要發生直接的 相互聯繫,可以轉交給第三方進行處理

開放-封閉原則(OCP)

軟體實體(類、模塊、函數)等應該是可以 擴展的,但是不可修改

當需要改變一個程式的功能或者給這個程式增加新功能的時候,可以使用增加代碼的方式,儘量避免改動程式的源代碼,防止影響原系統的穩定

 

什麼是設計模式

作者的這個說明解釋得挺好

假設有一個空房間,我們要日復一日地往裡 面放一些東西。最簡單的辦法當然是把這些東西 直接扔進去,但是時間久了,就會發現很難從這 個房子里找到自己想要的東西,要調整某幾樣東 西的位置也不容易。所以在房間里做一些柜子也 許是個更好的選擇,雖然柜子會增加我們的成 本,但它可以在維護階段為我們帶來好處。使用 這些柜子存放東西的規則,或許就是一種模式

學習設計模式,有助於寫出可復用和可維護性高的程式

設計模式的原則是“找出 程式中變化的地方,並將變化封裝起來”,它的關鍵是意圖,而不是結構。

不過要註意,使用不當的話,可能會事倍功半。

 

一、單例模式

二、策略模式

三、代理模式

四、迭代器模式

五、發佈—訂閱模式

六、命令模式

七、組合模式

八、模板方法模式

九、享元模式

十、職責鏈模式

十一、中介者模式

十二、裝飾者模式

十三、狀態模式

十四、適配器模式

十五、外觀模式

 

一、單例模式

1. 定義

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點

2. 核心

確保只有一個實例,並提供全局訪問

3. 實現

假設要設置一個管理員,多次調用也僅設置一次,我們可以使用閉包緩存一個內部變數來實現這個單例

function SetManager(name) {
    this.manager = name;
}

SetManager.prototype.getName = function() {
    console.log(this.manager);
};

var SingletonSetManager = (function() {
    var manager = null;

    return function(name) {
        if (!manager) {
            manager = new SetManager(name);
        }

        return manager;
    } 
})();

SingletonSetManager('a').getName(); // a
SingletonSetManager('b').getName(); // a
SingletonSetManager('c').getName(); // a

這是比較簡單的做法,但是假如我們還要設置一個HR呢?就得複製一遍代碼了

所以,可以改寫單例內部,實現地更通用一些

// 提取出通用的單例
function getSingleton(fn) {
    var instance = null;

    return function() {
        if (!instance) {
            instance = fn.apply(this, arguments);
        }

        return instance;
    }
}

再進行調用,結果還是一樣

// 獲取單例
var managerSingleton = getSingleton(function(name) {
    var manager = new SetManager(name);
    return manager;
});

managerSingleton('a').getName(); // a
managerSingleton('b').getName(); // a
managerSingleton('c').getName(); // a

這時,我們添加HR時,就不需要更改獲取單例內部的實現了,僅需要實現添加HR所需要做的,再調用即可

function SetHr(name) {
    this.hr = name;
}

SetHr.prototype.getName = function() {
    console.log(this.hr);
};

var hrSingleton = getSingleton(function(name) {
    var hr = new SetHr(name);
    return hr;
});

hrSingleton('aa').getName(); // aa
hrSingleton('bb').getName(); // aa
hrSingleton('cc').getName(); // aa

或者,僅想要創建一個div層,不需要將對象實例化,直接調用函數

結果為頁面中僅有第一個創建的div

function createPopup(html) {
    var div = document.createElement('div');
    div.innerHTML = html;
    document.body.append(div);

    return div;
}

var popupSingleton = getSingleton(function() {
    var div = createPopup.apply(this, arguments);
    return div;
});

console.log(
    popupSingleton('aaa').innerHTML,
    popupSingleton('bbb').innerHTML,
    popupSingleton('bbb').innerHTML
); // aaa  aaa  aaa

 

二、策略模式

1. 定義

定義一系列的演算法,把它們一個個封裝起來,並且使它們可以相互替換。

2. 核心

將演算法的使用和演算法的實現分離開來。

一個基於策略模式的程式至少由兩部分組成:

第一個部分是一組策略類,策略類封裝了具體的演算法,並負責具體的計算過程。

第二個部分是環境類Context,Context接受客戶的請求,隨後把請求委托給某一個策略類。要做到這點,說明Context 中要維持對某個策略對象的引用

3. 實現

策略模式可以用於組合一系列演算法,也可用於組合一系列業務規則

假設需要通過成績等級來計算學生的最終得分,每個成績等級有對應的加權值。我們可以利用對象字面量的形式直接定義這個組策略

// 加權映射關係
var levelMap = {
    S: 10,
    A: 8,
    B: 6,
    C: 4
};

// 組策略
var scoreLevel = {
    basicScore: 80,

    S: function() {
        return this.basicScore + levelMap['S']; 
    },

    A: function() {
        return this.basicScore + levelMap['A']; 
    },

    B: function() {
        return this.basicScore + levelMap['B']; 
    },

    C: function() {
        return this.basicScore + levelMap['C']; 
    }
}

// 調用
function getScore(level) {
    return scoreLevel[level] ? scoreLevel[level]() : 0;
}

console.log(
    getScore('S'),
    getScore('A'),
    getScore('B'),
    getScore('C'),
    getScore('D')
); // 90 88 86 84 0

在組合業務規則方面,比較經典的是表單的驗證方法。這裡列出比較關鍵的部分

// 錯誤提示
var errorMsgs = {
    default: '輸入數據格式不正確',
    minLength: '輸入數據長度不足',
    isNumber: '請輸入數字',
    required: '內容不為空'
};

// 規則集
var rules = {
    minLength: function(value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg || errorMsgs['minLength']
        }
    },
    isNumber: function(value, errorMsg) {
        if (!/\d+/.test(value)) {
            return errorMsg || errorMsgs['isNumber'];
        }
    },
    required: function(value, errorMsg) {
        if (value === '') {
            return errorMsg || errorMsgs['required'];
        }
    }
};

// 校驗器
function Validator() {
    this.items = [];
};

Validator.prototype = {
    constructor: Validator,
    
    // 添加校驗規則
    add: function(value, rule, errorMsg) {
        var arg = [value];

        if (rule.indexOf('minLength') !== -1) {
            var temp = rule.split(':');
            arg.push(temp[1]);
            rule = temp[0];
        }

        arg.push(errorMsg);

        this.items.push(function() {
            // 進行校驗
            return rules[rule].apply(this, arg);
        });
    },
    
    // 開始校驗
    start: function() {
        for (var i = 0; i < this.items.length; ++i) {
            var ret = this.items[i]();
            
            if (ret) {
                console.log(ret);
                // return ret;
            }
        }
    }
};

// 測試數據
function testTel(val) {
    return val;
}

var validate = new Validator();

validate.add(testTel('ccc'), 'isNumber', '只能為數字'); // 只能為數字
validate.add(testTel(''), 'required'); // 內容不為空
validate.add(testTel('123'), 'minLength:5', '最少5位'); // 最少5位
validate.add(testTel('12345'), 'minLength:5', '最少5位');

var ret = validate.start();

console.log(ret);

4. 優缺點

優點

可以有效地避免多重條件語句,將一系列方法封裝起來也更直觀,利於維護

缺點

往往策略集會比較多,我們需要事先就瞭解定義好所有的情況

 

三、代理模式

1. 定義

為一個對象提供一個代用品或占位符,以便控制對它的訪問

2. 核心

當客戶不方便直接訪問一個 對象或者不滿足需要的時候,提供一個替身對象 來控制對這個對象的訪問,客戶實際上訪問的是 替身對象。

替身對象對請求做出一些處理之後, 再把請求轉交給本體對象

代理和本體的介面具有一致性,本體定義了關鍵功能,而代理是提供或拒絕對它的訪問,或者在訪問本體之前做一 些額外的事情

3. 實現

代理模式主要有三種:保護代理、虛擬代理、緩存代理

保護代理主要實現了訪問主體的限制行為,以過濾字元作為簡單的例子

// 主體,發送消息
function sendMsg(msg) {
    console.log(msg);
}

// 代理,對消息進行過濾
function proxySendMsg(msg) {
    // 無消息則直接返回
    if (typeof msg === 'undefined') {
        console.log('deny');
        return;
    }
    
    // 有消息則進行過濾
    msg = ('' + msg).replace(/泥\s*煤/g, '');

    sendMsg(msg);
}


sendMsg('泥煤呀泥 煤呀'); // 泥煤呀泥 煤呀
proxySendMsg('泥煤呀泥 煤'); //
proxySendMsg(); // deny

它的意圖很明顯,在訪問主體之前進行控制,沒有消息的時候直接在代理中返回了,拒絕訪問主體,這數據保護代理的形式

有消息的時候對敏感字元進行了處理,這屬於虛擬代理的模式

 

虛擬代理在控制對主體的訪問時,加入了一些額外的操作

在滾動事件觸發的時候,也許不需要頻繁觸發,我們可以引入函數節流,這是一種虛擬代理的實現

// 函數防抖,頻繁操作中不處理,直到操作完成之後(再過 delay 的時間)才一次性處理
function debounce(fn, delay) {
    delay = delay || 200;
    
    var timer = null;

    return function() {
        var arg = arguments;
          
        // 每次操作時,清除上次的定時器
        clearTimeout(timer);
        timer = null;
        
        // 定義新的定時器,一段時間後進行操作
        timer = setTimeout(function() {
            fn.apply(this, arg);
        }, delay);
    }
};

var count = 0;

// 主體
function scrollHandle(e) {
    console.log(e.type, ++count); // scroll
}

// 代理
var proxyScrollHandle = (function() {
    return debounce(scrollHandle, 500);
})();

window.onscroll = proxyScrollHandle;

 

緩存代理可以為一些開銷大的運算結果提供暫時的緩存,提升效率

來個慄子,緩存加法操作

// 主體
function add() {
    var arg = [].slice.call(arguments);

    return arg.reduce(function(a, b) {
        return a + b;
    });
}

// 代理
var proxyAdd = (function() {
    var cache = [];

    return function() {
        var arg = [].slice.call(arguments).join(',');
        
        // 如果有,則直接從緩存返回
        if (cache[arg]) {
            return cache[arg];
        } else {
            var ret = add.apply(this, arguments);
            return ret;
        }
    };
})();

console.log(
    add(1, 2, 3, 4),
    add(1, 2, 3, 4),

    proxyAdd(10, 20, 30, 40),
    proxyAdd(10, 20, 30, 40)
); // 10 10 100 100

 

四、迭代器模式

1. 定義

迭代器模式是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不需要暴露該對象的內部表示。

2. 核心

在使用迭代器模式之後,即使不關心對象的內部構造,也可以按順序訪問其中的每個元素

3. 實現

JS中數組的map forEach 已經內置了迭代器

[1, 2, 3].forEach(function(item, index, arr) {
    console.log(item, index, arr);
});

不過對於對象的遍歷,往往不能與數組一樣使用同一的遍歷代碼

我們可以封裝一下

function each(obj, cb) {
    var value;

    if (Array.isArray(obj)) {
        for (var i = 0; i < obj.length; ++i) {
            value = cb.call(obj[i], i, obj[i]);

            if (value === false) {
                break;
            }
        }
    } else {
        for (var i in obj) {
            value = cb.call(obj[i], i, obj[i]);

            if (value === false) {
                break;
            }
        }
    }
}

each([1, 2, 3], function(index, value) {
    console.log(index, value);
});

each({a: 1, b: 2}, function(index, value) {
    console.log(index, value);
});

// 0 1
// 1 2
// 2 3

// a 1
// b 2

再來看一個例子,強行地使用迭代器,來瞭解一下迭代器也可以替換頻繁的條件語句

雖然例子不太好,但在其他負責的分支判斷情況下,也是值得考慮的

function getManager() {
    var year = new Date().getFullYear();

    if (year <= 2000) {
        console.log('A');
    } else if (year >= 2100) {
        console.log('C');
    } else {
        console.log('B');
    }
}

getManager(); // B

將每個條件語句拆分出邏輯函數,放入迭代器中迭代

function year2000() {
    var year = new Date().getFullYear();

    if (year <= 2000) {
        console.log('A');
    }

    return false;
}

function year2100() {
    var year = new Date().getFullYear();

    if (year >= 2100) {
        console.log('C');
    }

    return false;
}

function year() {
    var year = new Date().getFullYear();

    if (year > 2000 && year < 2100) {
        console.log('B');
    }

    return false;
}

function iteratorYear() {
    for (var i = 0; i < arguments.length; ++i) {
        var ret = arguments[i]();

        if (ret !== false) {
            return ret;
        }
    }
}

var manager = iteratorYear(year2000, year2100, year); // B

 

五、發佈-訂閱模式

1. 定義

也稱作觀察者模式,定義了對象間的一種一對多的依賴關係,當一個對象的狀態發 生改變時,所有依賴於它的對象都將得到通知

2. 核心

取代對象之間硬編碼的通知機制,一個對象不用再顯式地調用另外一個對象的某個介面。

與傳統的發佈-訂閱模式實現方式(將訂閱者自身當成引用傳入發佈者)不同,在JS中通常使用註冊回調函數的形式來訂閱

3. 實現

JS中的事件就是經典的發佈-訂閱模式的實現

// 訂閱
document.body.addEventListener('click', function() {
    console.log('click1');
}, false);

document.body.addEventListener('click', function() {
    console.log('click2');
}, false);

// 發佈
document.body.click(); // click1  click2

自己實現一下

小A在公司C完成了筆試及面試,小B也在公司C完成了筆試。他們焦急地等待結果,每隔半天就電話詢問公司C,導致公司C很不耐煩。

一種解決辦法是 AB直接把聯繫方式留給C,有結果的話C自然會通知AB

這裡的“詢問”屬於顯示調用,“留給”屬於訂閱,“通知”屬於發佈

// 觀察者
var observer = {
    // 訂閱集合
    subscribes: [],

    // 訂閱
    subscribe: function(type, fn) {
        if (!this.subscribes[type]) {
            this.subscribes[type] = [];
        }
        
        // 收集訂閱者的處理
        typeof fn === 'function' && this.subscribes[type].push(fn);
    },

    // 發佈  可能會攜帶一些信息發佈出去
    publish: function() {
        var type = [].shift.call(arguments),
            fns = this.subscribes[type];
        
        // 不存在的訂閱類型,以及訂閱時未傳入處理回調的
        if (!fns || !fns.length) {
            return;
        }
        
        // 挨個處理調用
        for (var i = 0; i < fns.length; ++i) {
            fns[i].apply(this, arguments);
        }
    },
    
    // 刪除訂閱
    remove: function(type, fn) {
        // 刪除全部
        if (typeof type === 'undefined') {
            this.subscribes = [];
            return;
        }

        var fns = this.subscribes[type];

        // 不存在的訂閱類型,以及訂閱時未傳入處理回調的
        if (!fns || !fns.length) {
            return;
        }

        if (typeof fn === 'undefined') {
            fns.length = 0;
            return;
        }

        // 挨個處理刪除
        for (var i = 0; i < fns.length; ++i) {
            if (fns[i] === fn) {
                fns.splice(i, 1);
            }
        }
    }
};

// 訂閱崗位列表
function jobListForA(jobs) {
    console.log('A', jobs);
}

function jobListForB(jobs) {
    console.log('B', jobs);
}

// A訂閱了筆試成績
observer.subscribe('job', jobListForA);
// B訂閱了筆試成績
observer.subscribe('job', jobListForB);


// A訂閱了筆試成績
observer.subscribe('examinationA', function(score) {
    console.log(score);
});

// B訂閱了筆試成績
observer.subscribe('examinationB', function(score) {
    console.log(score);
});

// A訂閱了面試結果
observer.subscribe('interviewA', function(result) {
    console.log(result);
});

observer.publish('examinationA', 100); // 100
observer.publish('examinationB', 80); // 80
observer.publish('interviewA', '備用'); // 備用

observer.publish('job', ['前端', '後端', '測試']); // 輸出A和B的崗位


// B取消訂閱了筆試成績
observer.remove('examinationB');
// A都取消訂閱了崗位
observer.remove('job', jobListForA);

observer.publish('examinationB', 80); // 沒有可匹配的訂閱,無輸出
observer.publish('job', ['前端', '後端', '測試']); // 輸出B的崗位

4. 優缺點

優點

一為時間上的解耦,二為對象之間的解耦。可以用在非同步編程中與MV*框架中

缺點

創建訂閱者本身要消耗一定的時間和記憶體,訂閱的處理函數不一定會被執行,駐留記憶體有性能開銷

弱化了對象之間的聯繫,複雜的情況下可能會導致程式難以跟蹤維護和理解

 

六、命令模式

1. 定義

用一種松耦合的方式來設計程式,使得請求發送者和請求接收者能夠消除彼此之間的耦合關係

命令(command)指的是一個執行某些特定事情的指令

2. 核心

命令中帶有execute執行、undo撤銷、redo重做等相關命令方法,建議顯示地指示這些方法名

3. 實現

簡單的命令模式實現可以直接使用對象字面量的形式定義一個命令

var incrementCommand = {
    execute: function() {
        // something
    }
};

不過接下來的例子是一個自增命令,提供執行、撤銷、重做功能

採用對象創建處理的方式,定義這個自增

// 自增
function IncrementCommand() {
    // 當前值
    this.val = 0;
    // 命令棧
    this.stack = [];
    // 棧指針位置
    this.stackPosition = -1;
};

IncrementCommand.prototype = {
    constructor: IncrementCommand,

    // 執行
    execute: function() {
        this._clearRedo();
        
        // 定義執行的處理
        var command = function() {
            this.val += 2;
        }.bind(this);
        
        // 執行並緩存起來
        command();
        
        this.stack.push(command);

        this.stackPosition++;

        this.getValue();
    },
    
    canUndo: function() {
        return this.stackPosition >= 0;
    },
    
    canRedo: function() {
        return this.stackPosition < this.stack.length - 1;
    },

    // 撤銷
    undo: function() {
        if (!this.canUndo()) {
            return;
        }
        
        this.stackPosition--;

        // 命令的撤銷,與執行的處理相反
        var command = function() {
            this.val -= 2;
        }.bind(this);
        
        // 撤銷後不需要緩存
        command();

        this.getValue();
    },
    
    // 重做
    redo: function() {
        if (!this.canRedo()) {
            return;
        }
        
        // 執行棧頂的命令
        this.stack[++this.stackPosition]();

        this.getValue();
    },
    
    // 在執行時,已經撤銷的部分不能再重做
    _clearRedo: function() {
        this.stack = this.stack.slice(0, this.stackPosition + 1);
    },
    
    // 獲取當前值
    getValue: function() {
        console.log(this.val);
    }
};

再實例化進行測試,模擬執行、撤銷、重做的操作

var incrementCommand = new IncrementCommand();

// 模擬事件觸發,執行命令
var eventTrigger = {
    // 某個事件的處理中,直接調用命令的處理方法
    increment: function() {
        incrementCommand.execute();
    },

    incrementUndo: function() {
        incrementCommand.undo();
    },

    incrementRedo: function() {
        incrementCommand.redo();
    }
};


eventTrigger['increment'](); // 2
eventTrigger['increment'](); // 4

eventTrigger['incrementUndo'](); // 2

eventTrigger['increment'](); // 4

eventTrigger['incrementUndo'](); // 2
eventTrigger['incrementUndo'](); // 0
eventTrigger['incrementUndo'](); // 無輸出

eventTrigger['incrementRedo'](); // 2
eventTrigger['incrementRedo'](); // 4
eventTrigger['incrementRedo'](); // 無輸出

eventTrigger['increment'](); // 6

 

此外,還可以實現簡單的巨集命令(一系列命令的集合)

var MacroCommand = {
    commands: [],

    add: function(command) {
        this.commands.push(command);

        return this;
    },

    remove: function(command) {
        if (!command) {
            this.commands = [];
            return;
        }

        for (var i = 0; i < this.commands.length; ++i) {
            if (this.commands[i] === command) {
                this.commands.splice(i, 1);
            }
        }
    },

    execute: function() {
        for (var i = 0; i < this.commands.length; ++i) {
            this.commands[i].execute();
        }
    }
};

var showTime = {
    execute: function() {
        console.log('time');
    }
};

var showName = {
    execute: function() {
        console.log('name');
    }
};

var showAge = {
    execute: function() {
        console.log('age');
    }
};

MacroCommand.add(showTime).add(showName).add(showAge);

MacroCommand.remove(showName);

MacroCommand.execute(); // time age

 

七、組合模式

1. 定義

是用小的子對象來構建更大的 對象,而這些小的子對象本身也許是由更小 的“孫對象”構成的。

2. 核心

可以用樹形結構來表示這種“部分- 整體”的層次結構。

調用組合對象 的execute方法,程式會遞歸調用組合對象 下麵的葉對象的execute方法

但要註意的是,組合模式不是父子關係,它是一種HAS-A(聚合)的關係,將請求委托給 它所包含的所有葉對象。基於這種委托,就需要保證組合對象和葉對象擁有相同的 介面

此外,也要保證用一致的方式對待 列表中的每個葉對象,即葉對象屬於同一類,不需要過多特殊的額外操作

3. 實現

使用組合模式來實現掃描文件夾中的文件

// 文件夾 組合對象
function Folder(name) {
    this.name = name;
    this.parent = null;
    this.files = [];
}

Folder.prototype = {
    constructor: Folder,

    add: function(file) {
        file.parent = this;
        this.files.push(file);

        return this;
    },

    scan: function() {
        // 委托給葉對象處理
        for (var i = 0; i < this.files.length; ++i) {
            this.files[i].scan();
        }
    },

    remove: function(file) {
        if (typeof file === 'undefined') {
            this.files = [];
            return;
        }

        for (var i = 0; i < this.files.length; ++i) {
            if (this.files[i] === file) {
                this.files.splice(i, 1);
            }
        }
    }
};

// 文件 葉對象
function File(name) {
    this.name = name;
    this.parent = null;
}

File.prototype = {
    constructor: File,

    add: function() {
        console.log('文件裡面不能添加文件');
    },

    scan: function() {
        var name = [this.name];
        var parent = this.parent;

        while (parent) {
            name.unshift(parent.name);
            parent = parent.parent;
        }

        console.log(name.join(' / '));
    }
};

構造好組合對象與葉對象的關係後,實例化,在組合對象中插入組合或葉對象

var web = new Folder('Web');
var fe = new Folder('前端');
var css = new Folder('CSS');
var js = new Folder('js');
var rd = new Folder('後端');

web.add(fe).add(rd);

var file1 = new File('HTML權威指南.pdf');
var file2 = new File('CSS權威指南.pdf');
var file3 = new File('JavaScript權威指南.pdf');
var file4 = new File('MySQL基礎.pdf');
var file5 = new File('Web安全.pdf');
var file6 = new File('Linux菜鳥.pdf');

css.add(file2);
fe.add(file1).add(file3).add(css).add(js);
rd.add(file4).add(file5);
web.add(file6);

rd.remove(file4);

// 掃描
web.scan();

掃描結果為

 

 4. 優缺點

優點

可 以方便地構造一棵樹來表示對象的部分-整體 結構。在樹的構造最終 完成之後,只需要通過請求樹的最頂層對 象,便能對整棵樹做統一一致的操作。

缺點

創建出來的對象長得都差不多,可能會使代碼不好理解,創建太多的對象對性能也會有一些影響

 

八、模板方法模式

1. 定義

模板方法模式由兩部分結構組成,第一部分是抽象父類,第二部分是具體的實現子類。

2. 核心

在抽象父類中封裝子類的演算法框架,它的 init方法可作為一個演算法的模板,指導子類以何種順序去執行哪些方法。

由父類分離出公共部分,要求子類重寫某些父類的(易變化的)抽象方法

3. 實現

模板方法模式一般的實現方式為繼承

以運動作為例子,運動有比較通用的一些處理,這部分可以抽離開來,在父類中實現。具體某項運動的特殊性則有自類來重寫實現。

最終子類直接調用父類的模板函數來執行

//
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 大數據,是未來必然的發展趨勢,當下我國的大數據建設已經如火如荼的進行著,無論是企業,還是個人,都逐步意識到大數據學習的重要性,大數據在未來的應用前進還會更加廣泛,不少初學者開始自學大數據。 基本上剛開始學習,大家都是從看書開始的,當然如果你覺得自己看書效率太慢,就可以從網上搜集一些課程,課程質量參差 ...
  • 直接執行:sudo yum install ntp或者sudo -y install ntp ...
  • Fence Repair Description Farmer John wants to repair a small length of the fence around the pasture. He measures the fence and finds that he needs N ( ...
  • 簡單總結一下對於數據的分組和分組函數。 本文所舉實例,數據來源oracle用戶scott下的emp,dept ,salgrade 3表:數據如下: 一、分組函數 1、sum()求和函數、max()求最大值函數、min()求最小值函數、avg()求平均值函數、count()求總行數函數 Express ...
  • List<Integer> integerList = new ArrayList<>(); 當我們要移除某個Item的時候 remove(int position):移除某個位置的Item remove(object object):移除某個對象 那麼remove(12)到底是移除第12的item ...
  • 1.java.lang.NoSuchMethodError: android.content.res.Resources.getDrawable/getColor或者 java.lang.NoSuchMethodError:android.content.Context.getDrawable/ge ...
  • 最近在做一個Xamarin for android的項目,有個需求是一次可以從相冊中選擇多張圖片,但是 android API<19 的版本還不支持一次選擇多張圖片,在網找了一下,發現原生的組件有很多並且都非常好用,也找到了很多原生的通過Binding 技術生成的 Xamarin for andro ...
  • 最近做了一個關於vue.js的小demo: 當用戶登錄時,使用狀態保存vuex將用戶的頭像信息存儲到store.state當中,這樣不同用戶登錄就會顯示相應的頭像。 具體實現方法: 在組件的計算屬性當中通過 this.$store.getters.userImg 獲取當前用戶的頭像,然後用requi ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...