1 概述
开闭原则的含义就是,软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
简单地说,执行不同行为时,实体应该被设计成不需要修改就可以保证应用的可变性。开闭原则有助于减少代码,并提高可维护性。
2 举个例子
var AnswerType = {
Choice: 0,
Input: 1
};
function question(label, answerType, choices) {
return {
label: label,
answerType: answerType,
choices: choices
};
}
var view = (function() {
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);
在这个例子中,视图对象包含了呈现方法,该方法根据接收到的类型呈现问题。问题由标签、答案类型组成。
按照上面的设计,如果要添加新的类型将需要在呈现方法中添加新的条件。这样势必违反开闭原则。
为了遵循开闭原则,我们将代码修改为:
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 = 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);
my.renderInput = function() {
var input = document.createElement('input');
input.type = 'text';
return input;
};
return that;
}
var view = {
render: function(target, questions) {
for (var i = 0; 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
的构造函数。该构造函数利用模板方法将每个答案的创建委托给扩展类型。
其次,我们用一个私有的 spec
属性来充当 questionsCreator
构造函数的接口。由于我们将呈现行为与它所操作的数据封装在一起,因此我们不需要将这些属性公开。
第三,我们把创建每个答案类型的代码作为一个算法家族,并将每个算法分解成一个单独的对象(一种称为策略模式的技术) ,该技术使用差异继承扩展了questionCreator
对象。
代码重构后的一个附加好处是,让我们能够避免使用AnswerType
,只使用数组就可添加choiceQuestionCreator
接口。