摘要:前言本章我們要講解的是五大原則語言實現的第篇,開閉原則。該代碼有一個限制,就是如果再增加一個類型的話,那就需要再次修改里的條件語句,這明顯違反了開閉原則。關于本文本文轉自大叔的深入理解系列。
前言
本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第2篇,開閉原則OCP(The Open/Closed Principle )。
開閉原則的描述是:
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
軟件實體(類,模塊,方法等等)應當對擴展開放,對修改關閉,即軟件實體應當在不修改的前提下擴展。
open for extension(對擴展開放)的意思是說當新需求出現的時候,可以通過擴展現有模型達到目的。而Close for modification(對修改關閉)的意思是說不允許對該實體做任何修改,說白了,就是這些需要執行多樣行為的實體應該設計成不需要修改就可以實現各種的變化,堅持開閉原則有利于用最少的代碼進行項目維護。
英文原文:http://freshbrewedcode.com/derekgreer/2011/12/19/solid-javascript-the-openclosed-principle/
問題代碼為了直觀地描述,我們來舉個例子演示一下,下屬代碼是動態展示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對象本身不再修改任何改變,從而達到了開閉原則的要求。
另:懂C#的話,不知道看了上面的代碼后是否和多態的實現有些類似?其實上述的代碼用原型也是可以實現的,大家可以自行研究一下。
關于本文本文轉自TOM大叔的深入理解JavaScript系列。關于S.O.L.I.D系列的五篇文章我糾結了很久,本來不想去整理的,但最終發現其實中間說的很多都是關于OOP(面向對象)編碼原則的東西,十分值得研讀,所以最后還是決定整理出來。
【深入理解JavaScript系列】文章,包括了原創,翻譯,轉載,整理等各類型文章,原文是TOM大叔的一個非常不錯的專題,現將其重新整理發布。謝謝大叔。如果你覺得本文不錯,請幫忙點個推薦,支持一把,感激不盡。
更多優秀文章歡迎關注我的專欄
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78465.html
摘要:前言本章我們要講解的是五大原則語言實現的第篇,接口隔離原則。接口隔離原則和單一職責有點類似,都是用于聚集功能職責的,實際上可以被理解才具有單一職責的程序轉化到一個具有公共接口的對象。與我們下面討論的一些小節是里關于違反接口隔離原則的影響。 前言 本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第4篇,接口隔離原則ISP(The Interface Segreg...
摘要:,開始我們的第一篇單一職責。通過解耦可以讓每個職責工更加有彈性地變化。關于本文本文轉自大叔的深入理解系列。深入理解系列文章,包括了原創,翻譯,轉載,整理等各類型文章,原文是大叔的一個非常不錯的專題,現將其重新整理發布。 前言 Bob大叔提出并發揚了S.O.L.I.D五大原則,用來更好地進行面向對象編程,五大原則分別是: The Single Responsibility Princi...
摘要:前言本章我們要講解的是五大原則語言實現的第篇,里氏替換原則。因此,違反了里氏替換原則。與行為有關,而不是繼承到現在,我們討論了和繼承上下文在內的里氏替換原則,指示出的面向對象。 前言 本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第3篇,里氏替換原則LSP(The Liskov Substitution Principle )。英文原文:http://fre...
摘要:前言本章我們要講解的是五大原則語言實現的第篇,依賴倒置原則。當應用依賴倒置原則的時候,關系就反過來了。在當靜態類型語言的上下文里討論依賴倒置原則的時候,耦合的概念包括語義和物理兩種。依賴倒置原則和依賴注入都是關注依賴,并且都是用于反轉。 前言 本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第5篇,依賴倒置原則LSP(The Dependency Invers...
摘要:是首個個面向對象設計準則的首字母縮寫,這些準則是由提出的他更為人所熟知的名字是。單一功能原則開閉原則里氏替換原則接口隔離原則依賴反轉原則接下來讓我們看看每個原則,來了解為什么可以幫助我們成為更好的開發人員。 showImg(https://segmentfault.com/img/remote/1460000019313380?w=1680&h=656); S.O.L.I.D?是?首個...
閱讀 3563·2023-04-25 16:35
閱讀 699·2021-10-11 11:09
閱讀 6162·2021-09-22 15:11
閱讀 3355·2019-08-30 14:03
閱讀 2597·2019-08-29 16:54
閱讀 3349·2019-08-29 16:34
閱讀 3053·2019-08-29 12:18
閱讀 2123·2019-08-28 18:31