摘要:搞論文準(zhǔn)備答辯的時(shí)候,仔細(xì)思考以及仔細(xì)閱讀很多設(shè)計(jì)模式的文章后,終于對(duì)開(kāi)閉原則有了一點(diǎn)認(rèn)識(shí)。其實(shí),我們遵循設(shè)計(jì)模式其他幾大原則,以及使用種設(shè)計(jì)模式的目的就是遵循開(kāi)閉原則。
之前簡(jiǎn)單介紹了常見(jiàn)設(shè)計(jì)模式遵循的設(shè)計(jì)原則--單一職責(zé)原則,這篇介紹一下另外一個(gè)相當(dāng)重要和具有指導(dǎo)性的一個(gè)原則,開(kāi)放關(guān)閉原則。但是,關(guān)于這一個(gè)原則的使用,經(jīng)驗(yàn)是相當(dāng)重要的一個(gè)因素。
但是個(gè)人感覺(jué)開(kāi)閉原則可能是設(shè)計(jì)模式幾大原則中定義最模糊的一個(gè)了,它只告訴我們對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉,可是到底如何才能做到對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉,并沒(méi)有明確的告訴我們。以前,如果有人說(shuō)“你進(jìn)行設(shè)計(jì)的時(shí)候一定要遵守開(kāi)閉原則”,會(huì)讓人覺(jué)得什么都沒(méi)說(shuō),但貌似又什么都說(shuō)了。因?yàn)殚_(kāi)閉原則真的太虛了。
開(kāi)閉原則Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
定義:一個(gè)軟件實(shí)體如類、模塊和函數(shù)應(yīng)該對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。
問(wèn)題由來(lái):在軟件的生命周期內(nèi),因?yàn)樽兓⑸?jí)和維護(hù)等原因需要對(duì)軟件原有代碼進(jìn)行修改時(shí),可能會(huì)給舊代碼中引入錯(cuò)誤,也可能會(huì)使我們不得不對(duì)整個(gè)功能進(jìn)行重構(gòu),并且需要原有代碼經(jīng)過(guò)重新測(cè)試。
解決方案:當(dāng)軟件需要變化時(shí),盡量通過(guò)擴(kuò)展軟件實(shí)體的行為來(lái)實(shí)現(xiàn)變化,而不是通過(guò)修改已有的代碼來(lái)實(shí)現(xiàn)變化。
借助下面這個(gè)例子,深入學(xué)習(xí)和理解所謂的開(kāi)閉原則的思想。代碼是動(dòng)態(tài)展示question列表的代碼(沒(méi)有使用開(kāi)閉原則)。
// 問(wèn)題類型 var AnswerType = { Choice: 0, Input: 1 }; // 問(wèn)題實(shí)體 function question(label, answerType, choices) { return { label: label, answerType: answerType, choices: choices // 這里的choices是可選參數(shù) }; } var view = (function () { // render一個(gè)問(wèn)題 function renderQuestion(target, question) { var questionWrapper = document.createElement("div"); questionWrapper.className = "question"; var questionLabel = document.createElement("div"); questionLabel.className = "question-label"; var label = document.createTextNode(question.label); questionLabel.appendChild(label); var answer = document.createElement("div"); answer.className = "question-input"; // 根據(jù)不同的類型展示不同的代碼:分別是下拉菜單和輸入框兩種 if (question.answerType === AnswerType.Choice) { var input = document.createElement("select"); var len = question.choices.length; for (var i = 0; i < len; i++) { var option = document.createElement("option"); option.text = question.choices[i]; option.value = question.choices[i]; input.appendChild(option); } } else if (question.answerType === AnswerType.Input) { var input = document.createElement("input"); input.type = "text"; } answer.appendChild(input); questionWrapper.appendChild(questionLabel); questionWrapper.appendChild(answer); target.appendChild(questionWrapper); } return { // 遍歷所有的問(wèn)題列表進(jìn)行展示 render: function (target, questions) { for (var i = 0; i < questions.length; i++) { renderQuestion(target, questions[i]); }; } }; })(); var questions = [ question("Have you used tobacco products within the last 30 days?", AnswerType.Choice, ["Yes", "No"]), question("What medications are you currently using?", AnswerType.Input) ]; var questionRegion = document.getElementById("questions"); view.render(questionRegion, questions);
上面的代碼,view對(duì)象里包含一個(gè)render方法用來(lái)展示question列表,展示的時(shí)候根據(jù)不同的question類型使用不同的展示方式,一個(gè)question包含一個(gè)label和一個(gè)問(wèn)題類型以及choices的選項(xiàng)(如果是選擇類型的話)。如果問(wèn)題類型是Choice那就根據(jù)選項(xiàng)生產(chǎn)一個(gè)下拉菜單,如果類型是Input,那就簡(jiǎn)單地展示input輸入框。
該代碼有一個(gè)限制,就是如果再增加一個(gè)question類型的話,那就需要再次修改renderQuestion里的條件語(yǔ)句,這明顯違反了開(kāi)閉原則。
完善讓我們來(lái)重構(gòu)一下這個(gè)代碼,以便在出現(xiàn)新question類型的情況下允許擴(kuò)展view對(duì)象的render能力,而不需要修改view對(duì)象內(nèi)部的代碼。
先來(lái)創(chuàng)建一個(gè)通用的questionCreator函數(shù):
function questionCreator(spec, my) { var that = {}; my = my || {}; my.label = spec.label; my.renderInput = function () { throw "not implemented"; // 這里renderInput沒(méi)有實(shí)現(xiàn),主要目的是讓各自問(wèn)題類型的實(shí)現(xiàn)代碼去覆蓋整個(gè)方法 }; that.render = function (target) { var questionWrapper = document.createElement("div"); questionWrapper.className = "question"; var questionLabel = document.createElement("div"); questionLabel.className = "question-label"; var label = document.createTextNode(spec.label); questionLabel.appendChild(label); var answer = my.renderInput(); // 該render方法是同樣的粗合理代碼 // 唯一的不同就是上面的一句my.renderInput() // 因?yàn)椴煌膯?wèn)題類型有不同的實(shí)現(xiàn) questionWrapper.appendChild(questionLabel); questionWrapper.appendChild(answer); return questionWrapper; }; return that; }
該代碼的作用組合要是render一個(gè)問(wèn)題,同時(shí)提供一個(gè)未實(shí)現(xiàn)的renderInput方法以便其他function可以覆蓋,以使用不同的問(wèn)題類型,我們繼續(xù)看一下每個(gè)問(wèn)題類型的實(shí)現(xiàn)代碼:
function choiceQuestionCreator(spec) { var my = {}, that = questionCreator(spec, my); // choice類型的renderInput實(shí)現(xiàn) my.renderInput = function () { var input = document.createElement("select"); var len = spec.choices.length; for (var i = 0; i < len; i++) { var option = document.createElement("option"); option.text = spec.choices[i]; option.value = spec.choices[i]; input.appendChild(option); } return input; }; return that; } function inputQuestionCreator(spec) { var my = {}, that = questionCreator(spec, my); // input類型的renderInput實(shí)現(xiàn) my.renderInput = function () { var input = document.createElement("input"); input.type = "text"; return input; }; return that; }
choiceQuestionCreator函數(shù)和inputQuestionCreator函數(shù)分別對(duì)應(yīng)下拉菜單和input輸入框的renderInput實(shí)現(xiàn),通過(guò)內(nèi)部調(diào)用統(tǒng)一的questionCreator(spec, my)然后返回that對(duì)象(同一類型)。
view對(duì)象的代碼就很固定了。
var view = { render: function(target, questions) { for (var i = 0; i < questions.length; i++) { target.appendChild(questions[i].render()); } } };
所以我們聲明問(wèn)題的時(shí)候只需要這樣做,就OK了:
var questions = [ choiceQuestionCreator({ label: "Have you used tobacco products within the last 30 days?", choices: ["Yes", "No"] }), inputQuestionCreator({ label: "What medications are you currently using?" }) ];
最終的使用代碼,我們可以這樣來(lái)用:
var questionRegion = document.getElementById("questions"); view.render(questionRegion, questions);總結(jié)
上面的代碼里應(yīng)用了一些技術(shù)點(diǎn),這里總結(jié)一下:
首先,questionCreator方法的創(chuàng)建,可以讓我們使用模板方法模式將處理問(wèn)題的功能delegat給針對(duì)每個(gè)問(wèn)題類型的擴(kuò)展代碼renderInput上。
其次,我們用一個(gè)私有的spec屬性替換掉了前面question方法的構(gòu)造函數(shù)屬性,因?yàn)槲覀兎庋b了render行為進(jìn)行操作,不再需要把這些屬性暴露給外部代碼了。
第三,我們?yōu)槊總€(gè)問(wèn)題類型創(chuàng)建一個(gè)對(duì)象進(jìn)行各自的代碼實(shí)現(xiàn),但每個(gè)實(shí)現(xiàn)里都必須包含renderInput方法以便覆蓋questionCreator方法里的renderInput代碼,這就是我們常說(shuō)的策略模式。通過(guò)完善之后,我們可以去除不必要的問(wèn)題類型的枚舉AnswerType,而且可以讓choices作為choiceQuestionCreator函數(shù)的必選參數(shù)(之前的版本是一個(gè)可選參數(shù))。
重構(gòu)以后的版本的view對(duì)象可以很清晰地進(jìn)行新的擴(kuò)展了,為不同的問(wèn)題類型擴(kuò)展新的對(duì)象,然后聲明questions集合的時(shí)候再里面指定類型就行了,view對(duì)象本身不再修改任何改變,從而達(dá)到了開(kāi)閉原則的要求。
搞論文準(zhǔn)備答辯的時(shí)候,仔細(xì)思考以及仔細(xì)閱讀很多設(shè)計(jì)模式的文章后,終于對(duì)開(kāi)閉原則有了一點(diǎn)認(rèn)識(shí)。其實(shí),我們遵循設(shè)計(jì)模式其他幾大原則,以及使用23種設(shè)計(jì)模式的目的就是遵循開(kāi)閉原則。也就是說(shuō),只要我們其他原則遵守的好了,設(shè)計(jì)出的軟件自然是符合開(kāi)閉原則的,這個(gè)開(kāi)閉原則更像是這些原則遵守程度的“平均得分”,這些原則遵守的好,平均分自然就高,說(shuō)明軟件設(shè)計(jì)開(kāi)閉原則遵守的好;這些原則遵守的不好,則說(shuō)明開(kāi)閉原則遵守的不好。
開(kāi)閉原則無(wú)非就是想表達(dá)這樣一層意思:用抽象構(gòu)建框架,用實(shí)現(xiàn)擴(kuò)展細(xì)節(jié)。因?yàn)槌橄箪`活性好,適應(yīng)性廣,只要抽象的合理,可以基本保持軟件架構(gòu)的穩(wěn)定。而軟件中易變的細(xì)節(jié),我們用從抽象派生的實(shí)現(xiàn)類來(lái)進(jìn)行擴(kuò)展,當(dāng)軟件需要發(fā)生變化時(shí),我們只需要根據(jù)需求重新派生一個(gè)實(shí)現(xiàn)類來(lái)擴(kuò)展就可以了。當(dāng)然前提是我們的抽象要合理,要對(duì)需求的變更有前瞻性和預(yù)見(jiàn)性才行。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/78395.html
摘要:?jiǎn)我宦氊?zé)原則開(kāi)閉原則里氏替換原則依賴倒置原則接口隔離原則迪米特法則組合聚合復(fù)用原則單一職責(zé)原則高內(nèi)聚低耦合定義不要存在多于一個(gè)導(dǎo)致類變更的原因。建議接口一定要做到單一職責(zé),類的設(shè)計(jì)盡量做到只有一個(gè)原因引起變化。使用繼承時(shí)遵循里氏替換原則。 單一職責(zé)原則 開(kāi)閉原則 里氏替換原則 依賴倒置原則 接口隔離原則 迪米特法則 組合/聚合復(fù)用原則 單一職責(zé)原則(Single Responsi...
摘要:眾多面向?qū)ο蟮木幊趟枷腚m不盡一致,但是無(wú)論哪種面向?qū)ο缶幊陶Z(yǔ)言都具有以下的共通功能。原型編程以類為中心的傳統(tǒng)面向?qū)ο缶幊蹋且灶悶榛A(chǔ)生成新對(duì)象。而原型模式的面向?qū)ο缶幊陶Z(yǔ)言沒(méi)有類這樣一個(gè)概念。 什么是面向?qū)ο螅窟@個(gè)問(wèn)題往往會(huì)問(wèn)到剛畢業(yè)的新手or實(shí)習(xí)生上,也是往往作為一個(gè)技術(shù)面試的開(kāi)頭題。在這里我們不去談如何答(fu)好(yan)問(wèn)(guo)題(qu),僅談?wù)勎宜斫獾拿嫦驅(qū)ο蟆?從歷...
摘要:在面向?qū)ο笤O(shè)計(jì)中,可維護(hù)性的復(fù)用是以設(shè)計(jì)原則為基礎(chǔ)的。面向?qū)ο笤O(shè)計(jì)原則為支持可維護(hù)性復(fù)用而誕生,這些原則蘊(yùn)含在很多設(shè)計(jì)模式中,它們是從許多設(shè)計(jì)方案中總結(jié)出的指導(dǎo)性原則。 面向?qū)ο笤O(shè)計(jì)原則 概述 對(duì)于面向?qū)ο筌浖到y(tǒng)的設(shè)計(jì)而言,在支持可維護(hù)性的同時(shí),提高系統(tǒng)的可復(fù)用性是一個(gè)至關(guān)重要的問(wèn)題,如何同時(shí)提高一個(gè)軟件系統(tǒng)的可維護(hù)性和可復(fù)用性是面向?qū)ο笤O(shè)計(jì)需要解決的核心問(wèn)題之一。在面向?qū)ο笤O(shè)計(jì)中,...
摘要:依賴倒置原則是個(gè)設(shè)計(jì)原則中最難以實(shí)現(xiàn)的原則,它是實(shí)現(xiàn)開(kāi)閉原則的重要途徑,依賴倒置原則沒(méi)有實(shí)現(xiàn),就別想實(shí)現(xiàn)對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。 1、單一職能原則(Single Responsibility Principle, SRP) 定義 There should never be more than one reason for a class to change.應(yīng)該有且僅有一個(gè)原因引起類的...
閱讀 1083·2021-09-22 15:19
閱讀 1697·2021-08-23 09:46
閱讀 2226·2021-08-09 13:47
閱讀 1405·2019-08-30 15:55
閱讀 1408·2019-08-30 15:55
閱讀 1974·2019-08-30 15:54
閱讀 2795·2019-08-30 15:53
閱讀 713·2019-08-30 11:03