前言

本章我们要讲解的是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: ,
Input:
}; // 问题实体
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 = ; 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 = ; 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 = ; 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 = ; 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); 重构后的最终代码
function questionCreator(spec, my) {
var that = {}; my = my || {};
my.label = spec.label; my.renderInput = function() {
throw "not implemented";
}; 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(); questionWrapper.appendChild(questionLabel);
questionWrapper.appendChild(answer);
return questionWrapper;
}; return that;
} function choiceQuestionCreator(spec) { var my = {},
that = questionCreator(spec, my); my.renderInput = function() {
var input = document.createElement('select');
var len = spec.choices.length;
for (var i = ; 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); my.renderInput = function() {
var input = document.createElement('input');
input.type = 'text';
return input;
}; return that;
} var view = {
render: function(target, questions) {
for (var i = ; i < questions.length; i++) {
target.appendChild(questions[i].render());
}
}
}; 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#的话,不知道看了上面的代码后是否和多态的实现有些类似?其实上述的代码用原型也是可以实现的,大家可以自行研究一下。 同步与推荐 本文已同步至目录索引:深入理解JavaScript系列 深入理解JavaScript系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。

深入理解JavaScript系列(7):S.O.L.I.D五大原则之开闭原则OCP的更多相关文章

  1. 最简单直接地理解Java软件设计原则之开闭原则

    写在前面 本文属于Java软件设计原则系列文章的其中一篇,后续会继续分享其他的原则.想以最简单的方式,最直观的demo去彻底理解设计原则.文章属于个人整理.也欢迎大家提出不同的想法. 首先是一些理论性 ...

  2. 深入理解JavaScript系列

    转自http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html 深入理解JavaScript系列(1):编写高质量JavaScript代码 ...

  3. 深入理解JavaScript系列(转自汤姆大叔)

    深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaScript ...

  4. [转]深入理解JavaScript系列

    文章转自:汤姆大叔-深入理解JavaScript系列文章 深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解Ja ...

  5. [转载]深入理解JavaScript系列 --汤姆大叔

    深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaScript ...

  6. 深入理解JavaScript系列(转载)

    深入理解JavaScript系列 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 深入理解JavaScript系列(2):揭秘命名函数表达式 深入理解JavaSc ...

  7. 深入理解JavaScript系列(22):S.O.L.I.D五大原则之依赖倒置原则DIP

    前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第5篇,依赖倒置原则LSP(The Dependency Inversion Principle ). 英文原文:htt ...

  8. 深入理解JavaScript系列(21):S.O.L.I.D五大原则之接口隔离原则ISP

    前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第4篇,接口隔离原则ISP(The Interface Segregation Principle). 英文原文:htt ...

  9. 深入理解JavaScript系列(8):S.O.L.I.D五大原则之里氏替换原则LSP

    前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第3篇,里氏替换原则LSP(The Liskov Substitution Principle ). 英文原文:http ...

随机推荐

  1. C# 接口(3)

    这么半天说了如何使用,实现接口.相信也都发现了接口和抽象类很多相似的地方. 但是! 这两个根本就是不一样的. 抽象类 :                                         ...

  2. 结对作业-WordCount进阶版

    1.在文章开头给出博客作业要求地址. 博客园地址:https://www.cnblogs.com/happyzm/p/9559372.html 2.给出结对小伙伴的学号.博客地址,结对项目的码云地址. ...

  3. 【数据结构】单链表&&静态链表详解和代码实例

    喜欢的话可以扫码关注我们的公众号哦,更多精彩尽在微信公众号[程序猿声] 01 单链表(Singly Linked List ) 1.1 什么是单链表? 单链表是一种链式存储的结构.它动态的为节点分配存 ...

  4. sed的基本用法

    了解sed的基本参数 sed匹配的方法: '//'p, 此符号与grep的引号类似,但sed是一定加此符号的,且还要加上-n的参数,匹配起来相当麻烦.sed匹配的方法: '//'pI 加上I的参数是指 ...

  5. Xilinx FPGA使用——ROM初始化文件

    在调用ROM的IP Core时,需要对其进行初始化,利用MATLAB生成其初始化数据文件. 工具:ISE 14.7.MATLAB.notepad++ 废话不多说,直接上MATLAB代码,生成了一个10 ...

  6. 011 Android TabLayout+ViewPager实现顶部滑动效果(多个页面)

    1.TabLayout介绍 TabLayout提供了一个水平的布局用来展示Tabs,很多应用都有这样的设计,典型的有网易新闻,简书,知乎等.TabLayout就可以很好的完成这一职责,首先TabLay ...

  7. ZOJ Monthly, January 2019 I Little Sub and Isomorphism Sequences(set 妙用) ZOJ4089

    写这篇博客来证明自己的愚蠢 ...Orz  飞机 题意:给定你个数组,以及一些单点修改,以及询问,每次询问需要求得,最长的字串长度,它在其他位置存在同构 题解:经过一些奇思妙想后 ,你可以发现问题是传 ...

  8. BZOJ - 2844 线性基

    题意:求给定的数在原数组中的异或组合中的排名(非去重) 因为线性基中\(b[j]=1\)表示该位肯定存在,所以给定的数如果含有该位,由严格递增和集合枚举可得,排名必然加上\(2^j\)(不是完全对角就 ...

  9. Tomcat SSL证书安装配置

    [From Internet] 首先找到安装Tomcat 目录下该文件“Server.xml”,一般默认路径都是在Conf 文件夹中.然后用文本编辑器打开该文件,接着找到 <Connector ...

  10. [转] Spring Boot配置多个DataSource

    [From]  https://www.liaoxuefeng.com/article/001484212576147b1f07dc0ab9147a1a97662a0bd270c20000 Sprin ...