S.O.L.I.D五大原则之开闭原则OCP

开闭原则的描述是:

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);

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);

上面的代码里应用了一些技术点,我们来逐一看一下:

  1. 首先,questionCreator方法的创建,可以让我们使用模板方法模式将处理问题的功能delegat给针对每个问题类型的扩展代码renderInput上。
  2. 其次,我们用一个私有的spec属性替换掉了前面question方法的构造函数属性,因为我们封装了render行为进行操作,不再需要把这些属性暴露给外部代码了。
  3. 第三,我们为每个问题类型创建一个对象进行各自的代码实现,但每个实现里都必须包含renderInput方法以便覆盖questionCreator方法里的renderInput代码,这就是我们常说的策略模式

通过重构,我们可以去除不必要的问题类型的枚举AnswerType,而且可以让choices作为choiceQuestionCreator函数的必选参数(之前的版本是一个可选参数)。

总结

重构以后的版本的view对象可以很清晰地进行新的扩展了,为不同的问题类型扩展新的对象,然后声明questions集合的时候再里面指定类型就行了,view对象本身不再修改任何改变,从而达到了开闭原则的要求。

时间: 2024-07-29 19:17:28

S.O.L.I.D五大原则之开闭原则OCP的相关文章

设计模式之6大原则(6)开闭原则

1. more第一版 实现基础功能,显示每一页固定24行文本,"q Enter"退出, "Enter" 下一行, "space Enter"下一页. /************************************************************************* > File Name: more01.c > Author: qianlv > Mail: [email protected] &

面向对象原则之一 开放封闭原则(开闭原则)

原文:面向对象原则之一 开放封闭原则(开闭原则) 前言 面向对象有人分为五大原则,分别为单一职责原则.开放封闭原则.依赖倒置原则.接口隔离原则.里氏替换原则. 也有人分为六大原则,分别为单一职责原则.开放封闭原则.依赖倒置原则.接口隔离原则.里氏替换原则.迪米特法则. 现在我们来介绍开放封闭原则,也叫开闭原则 开闭原则 1)概念 官方说法是 软件实体(模块.类.函数等)应该可以扩展,但是不可以修改.也就是说软件对扩展开放,对修改关闭. 需要说明的是,对修改关闭不是说软件设计不能做修改,只是尽量不

面向对象设计原则之开闭原则

http://blog.csdn.net/lovelion/article/details/7537584 为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键.在Java.C#等编程语言中,可以为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成.在很多面向对象编程语言中都提供了接口.抽象类等机制,可以通过它们定义系统的抽象层,再通过具体类来进行扩展.如果需要修改系统的行为,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有

设计原则之开闭原则Open Close Principle

翻译自http://www.oodesign.com 设计原则之开闭原则 动机:一个聪明的应用设计和代码编写应该考虑到开发过程中的频繁修改代码.通常情况下,一个新功能的增加会带来很多的修改.这些修改已存在的代码应该要最小化, 总结:软件应该对扩展开发,对修改关闭.装饰器模式,观察者模式,工厂模式可以帮助我们队代码做最小的修改. Bad Example: 缺点: 1.当新的shape被添加,开发者要花大量时间去理解GraphicEditor源码.. 2.添加新shape也许会影响已经存在的功能 /

设计模式七大原则之开闭原则学习

这是在我大学学习过程中,老师给我讲的第一个设计原则:开闭原则,至今为止,我只记住了一句话:程序对修改关闭,对扩展开放.接下来得好好理解一下这句话 一.开闭原则 开闭原则是编程中最基础.最重要的设计原则 基本介绍: (1)一个软件实体如类,模块和函数应该对扩展开放(对于提供方来说),对修改关闭(对于使用方来说).用抽象构建框架,用实现扩展细节. (2)当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化. (3)编程中遵循其它原则,以及使用设计模式的目的就是遵

深入理解JavaScript系列(7):S.O.L.I.D五大原则之开闭原则OCP

前言 本章我们要讲解的是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. 软件实体(类,模块,方法等等)应当对扩展开放,对修改关闭,即软件实体应当在不修改的前提下扩展.

设计模式原则之开闭原则

开闭原则(OCP)是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段. 遵循开闭原则设计出的模块具有两个主要特征: (1)对于扩展是开放的(Open for extension).这意味着模块的行为是可以扩展的.当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为.也就是说,我们可以改变模块的功能. (2)对于修改是关闭的(Closed for modification).对模块行为进行扩展时,不必改动模块的源

架构中的设计原则之开闭原则(OCP) - 《java开发技术-在架构中体验设计模式和算法之美》

开闭原则OCP(Open for Extension,Closed for Modification).开闭原则的核心思想是:一个对象对扩张开放,对修改关闭. 其实开闭原则的意思就是:对类的改动是通过增加代码进行的,而不是改动现有的代码.也就是说,软件开发人员一旦写出了可以运行的代码,就不应该去改变它,而是要保证它能一直运行下去,如何才能做到这一点呢?这就需要借助于抽象和多态,即把可能变化的内容抽象出来,从而使抽象的部分是相对稳定,而具体的实现层是可以改变和扩展的. 根据开闭原则,我们改变一个软

设计模式之禅--六大原则之开闭原则(精神领袖)

"对修改关闭,对拓展开放". 一个书店卖书的例子,见代码 public interface IBook { public String getName(); public int getPrice(); public String getAuthor(); } ---------- package com.sdkd.hms; public class NovelBook implements IBook { private String name; private int price;