摘要:搞論文準備答辯的時候,仔細思考以及仔細閱讀很多設計模式的文章后,終于對開閉原則有了一點認識。其實,我們遵循設計模式其他幾大原則,以及使用種設計模式的目的就是遵循開閉原則。
之前簡單介紹了常見設計模式遵循的設計原則--單一職責原則,這篇介紹一下另外一個相當重要和具有指導性的一個原則,開放關閉原則。但是,關于這一個原則的使用,經驗是相當重要的一個因素。
但是個人感覺開閉原則可能是設計模式幾大原則中定義最模糊的一個了,它只告訴我們對擴展開放,對修改關閉,可是到底如何才能做到對擴展開放,對修改關閉,并沒有明確的告訴我們。以前,如果有人說“你進行設計的時候一定要遵守開閉原則”,會讓人覺得什么都沒說,但貌似又什么都說了。因為開閉原則真的太虛了。
開閉原則Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
定義:一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。
問題由來:在軟件的生命周期內,因為變化、升級和維護等原因需要對軟件原有代碼進行修改時,可能會給舊代碼中引入錯誤,也可能會使我們不得不對整個功能進行重構,并且需要原有代碼經過重新測試。
解決方案:當軟件需要變化時,盡量通過擴展軟件實體的行為來實現變化,而不是通過修改已有的代碼來實現變化。
借助下面這個例子,深入學習和理解所謂的開閉原則的思想。代碼是動態展示question列表的代碼(沒有使用開閉原則)。
// 問題類型 var AnswerType = { Choice: 0, Input: 1 }; // 問題實體 function question(label, answerType, choices) { return { label: label, answerType: answerType, choices: choices // 這里的choices是可選參數 }; } var view = (function () { // render一個問題 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"; // 根據不同的類型展示不同的代碼:分別是下拉菜單和輸入框兩種 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 { // 遍歷所有的問題列表進行展示 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對象里包含一個render方法用來展示question列表,展示的時候根據不同的question類型使用不同的展示方式,一個question包含一個label和一個問題類型以及choices的選項(如果是選擇類型的話)。如果問題類型是Choice那就根據選項生產一個下拉菜單,如果類型是Input,那就簡單地展示input輸入框。
該代碼有一個限制,就是如果再增加一個question類型的話,那就需要再次修改renderQuestion里的條件語句,這明顯違反了開閉原則。
完善讓我們來重構一下這個代碼,以便在出現新question類型的情況下允許擴展view對象的render能力,而不需要修改view對象內部的代碼。
先來創建一個通用的questionCreator函數:
function questionCreator(spec, my) { var that = {}; my = my || {}; my.label = spec.label; my.renderInput = function () { throw "not implemented"; // 這里renderInput沒有實現,主要目的是讓各自問題類型的實現代碼去覆蓋整個方法 }; 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() // 因為不同的問題類型有不同的實現 questionWrapper.appendChild(questionLabel); questionWrapper.appendChild(answer); return questionWrapper; }; return that; }
該代碼的作用組合要是render一個問題,同時提供一個未實現的renderInput方法以便其他function可以覆蓋,以使用不同的問題類型,我們繼續看一下每個問題類型的實現代碼:
function choiceQuestionCreator(spec) { var my = {}, that = questionCreator(spec, my); // choice類型的renderInput實現 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實現 my.renderInput = function () { var input = document.createElement("input"); input.type = "text"; return input; }; return that; }
choiceQuestionCreator函數和inputQuestionCreator函數分別對應下拉菜單和input輸入框的renderInput實現,通過內部調用統一的questionCreator(spec, my)然后返回that對象(同一類型)。
view對象的代碼就很固定了。
var view = { render: function(target, questions) { for (var i = 0; i < questions.length; i++) { target.appendChild(questions[i].render()); } } };
所以我們聲明問題的時候只需要這樣做,就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?" }) ];
最終的使用代碼,我們可以這樣來用:
var questionRegion = document.getElementById("questions"); view.render(questionRegion, questions);總結
上面的代碼里應用了一些技術點,這里總結一下:
首先,questionCreator方法的創建,可以讓我們使用模板方法模式將處理問題的功能delegat給針對每個問題類型的擴展代碼renderInput上。
其次,我們用一個私有的spec屬性替換掉了前面question方法的構造函數屬性,因為我們封裝了render行為進行操作,不再需要把這些屬性暴露給外部代碼了。
第三,我們為每個問題類型創建一個對象進行各自的代碼實現,但每個實現里都必須包含renderInput方法以便覆蓋questionCreator方法里的renderInput代碼,這就是我們常說的策略模式。通過完善之后,我們可以去除不必要的問題類型的枚舉AnswerType,而且可以讓choices作為choiceQuestionCreator函數的必選參數(之前的版本是一個可選參數)。
重構以后的版本的view對象可以很清晰地進行新的擴展了,為不同的問題類型擴展新的對象,然后聲明questions集合的時候再里面指定類型就行了,view對象本身不再修改任何改變,從而達到了開閉原則的要求。
搞論文準備答辯的時候,仔細思考以及仔細閱讀很多設計模式的文章后,終于對開閉原則有了一點認識。其實,我們遵循設計模式其他幾大原則,以及使用23種設計模式的目的就是遵循開閉原則。也就是說,只要我們其他原則遵守的好了,設計出的軟件自然是符合開閉原則的,這個開閉原則更像是這些原則遵守程度的“平均得分”,這些原則遵守的好,平均分自然就高,說明軟件設計開閉原則遵守的好;這些原則遵守的不好,則說明開閉原則遵守的不好。
開閉原則無非就是想表達這樣一層意思:用抽象構建框架,用實現擴展細節。因為抽象靈活性好,適應性廣,只要抽象的合理,可以基本保持軟件架構的穩定。而軟件中易變的細節,我們用從抽象派生的實現類來進行擴展,當軟件需要發生變化時,我們只需要根據需求重新派生一個實現類來擴展就可以了。當然前提是我們的抽象要合理,要對需求的變更有前瞻性和預見性才行。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/78395.html
摘要:單一職責原則開閉原則里氏替換原則依賴倒置原則接口隔離原則迪米特法則組合聚合復用原則單一職責原則高內聚低耦合定義不要存在多于一個導致類變更的原因。建議接口一定要做到單一職責,類的設計盡量做到只有一個原因引起變化。使用繼承時遵循里氏替換原則。 單一職責原則 開閉原則 里氏替換原則 依賴倒置原則 接口隔離原則 迪米特法則 組合/聚合復用原則 單一職責原則(Single Responsi...
摘要:眾多面向對象的編程思想雖不盡一致,但是無論哪種面向對象編程語言都具有以下的共通功能。原型編程以類為中心的傳統面向對象編程,是以類為基礎生成新對象。而原型模式的面向對象編程語言沒有類這樣一個概念。 什么是面向對象?這個問題往往會問到剛畢業的新手or實習生上,也是往往作為一個技術面試的開頭題。在這里我們不去談如何答(fu)好(yan)問(guo)題(qu),僅談談我所理解的面向對象。 從歷...
摘要:依賴倒置原則是個設計原則中最難以實現的原則,它是實現開閉原則的重要途徑,依賴倒置原則沒有實現,就別想實現對擴展開放,對修改關閉。 1、單一職能原則(Single Responsibility Principle, SRP) 定義 There should never be more than one reason for a class to change.應該有且僅有一個原因引起類的...
閱讀 1094·2021-09-22 15:19
閱讀 1711·2021-08-23 09:46
閱讀 2233·2021-08-09 13:47
閱讀 1412·2019-08-30 15:55
閱讀 1420·2019-08-30 15:55
閱讀 1980·2019-08-30 15:54
閱讀 2804·2019-08-30 15:53
閱讀 718·2019-08-30 11:03