罕见设想形式遵照的设想准绳 - 开放封闭准绳

之前简朴引见了罕见设想形式遵照的设想准绳–单一职责准绳,这篇引见一下别的一个相称重要和具有指导性的一个准绳,开放封闭准绳。然则,关于这一个准绳的运用,履历是相称重要的一个要素。

然则个人感觉开闭准绳多是设想形式几大准绳中定义最隐约的一个了,它只通知我们对扩大开放,对修正封闭,但是究竟怎样才做到对扩大开放,对修正封闭,并没有明白的通知我们。之前,假如有人说“你举行设想的时刻一定要恪守开闭准绳”,会让人以为什么都没说,但貌似又什么都说了。由于开闭准绳真的太虚了。

开闭准绳

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种设想形式的目标就是遵照开闭准绳。也就是说,只需我们其他准绳恪守的好了,设想出的软件天然是相符开闭准绳的,这个开闭准绳更像是这些准绳恪守水平的“均匀得分”,这些准绳恪守的好,均匀分天然就高,申明软件设想开闭准绳恪守的好;这些准绳恪守的不好,则申明开闭准绳恪守的不好。

开闭准绳不过就是想表达如许一层意义:用笼统构建框架,用完成扩大细节。由于笼统灵活性好,适应性广,只需笼统的合理,能够基础坚持软件架构的稳固。而软件中易变的细节,我们用从笼统派生的完成类来举行扩大,当软件须要发生变化时,我们只须要依据需求从新派生一个完成类来扩大就能够了。固然条件是我们的笼统要合理,要对需求的变动有前瞻性和预见性才行。

    原文作者:xuehen
    原文地址: https://segmentfault.com/a/1190000004240768
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞