Javascript開閉原則

開閉原則的核心是:對(duì)擴(kuò)展開放峦树,對(duì)修改關(guān)閉

白話意思就是我們改變一個(gè)軟件時(shí)(比如擴(kuò)展其他功能)额划,應(yīng)該通過擴(kuò)展的方式來達(dá)到軟件的改變工闺,而不應(yīng)愛修改原有代碼來實(shí)現(xiàn)變化

說白了度苔,就是這些需要執(zhí)行多樣行為的實(shí)體應(yīng)該設(shè)計(jì)成不需要修改就可以實(shí)現(xiàn)各種的變化,堅(jiān)持開閉原則有利于用最少的代碼進(jìn)行項(xiàng)目維護(hù)暂吉。

下面是使用開閉原則的一個(gè)簡(jiǎn)單示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<div id="test"></div>
</body>
<script type="text/javascript">

//一個(gè)面向?qū)ο蟮腏S例子胖秒,很好的支持了開閉原則

function HtmlControl(options) { //定義一個(gè)方法

    var el = options.element;  //el賦值為dom元素
    //下面是為el元素添加參數(shù)樣式
    el.style.width = options.width;

    el.style.height = options.height;

    el.style.top = options.top;

    el.style.background = options.background;

    el.innerHTML=options.text;
    console.log(el)

}



var option = { //為方法定義一個(gè)參數(shù)對(duì)象

    element: document.getElementById('test'),

    left: 50,

    top: 0,

    width: 100,

    height: 200,

    background: '#ccc',

    text:'什么是開閉原則'

}

option.background = 'red'; //對(duì)參數(shù)對(duì)象進(jìn)行擴(kuò)展



HtmlControl(option); //調(diào)用

</script>
</html>

這是一個(gè)簡(jiǎn)單的例子,意思就是說封裝一個(gè)功能慕的,這個(gè)功能有默認(rèn)參數(shù)阎肝,對(duì)外提供接口,接口參數(shù)可擴(kuò)展改變业稼,但是需要修改功能是不可以的盗痒,也就是說在功能接口內(nèi)擴(kuò)展修改該功能,就可以得到不同的效果低散。

為了直觀地描述,我們來舉個(gè)例子演示一下骡楼,下屬代碼是動(dòng)態(tài)展示question列表的代碼(沒有使用開閉原則)熔号。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<div id="questions"></div>
</body>
<script type="text/javascript">
// 問題類型
var AnswerType = {
    Choice: 0,
    Input: 1
};

// 問題實(shí)體
function question(label, answerType, choices) {
    return {
        label: label,
        answerType: answerType,
        choices: choices // 這里的choices是可選參數(shù)
    };
}

var view = (function() {
    // render一個(gè)問題
    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);//創(chuàng)建文本節(jié)點(diǎn)
        questionLabel.appendChild(label);//在指定節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn)列表之后添加一個(gè)新的子節(jié)點(diǎn)。

        var answer = document.createElement('div');
        answer.className = 'question-input';

        // 根據(jù)不同的類型展示不同的代碼:分別是下拉菜單和輸入框兩種
        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 {
        // 遍歷所有的問題列表進(jìn)行展示
        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);

</script>
</html>

實(shí)現(xiàn)效果

image.png

上面的代碼鸟整,view對(duì)象里包含一個(gè)render方法用來展示question列表引镊,展示的時(shí)候根據(jù)不同的question類型使用不同的展示方式,一個(gè)question包含一個(gè)label和一個(gè)問題類型以及choices的選項(xiàng)(如果是選擇類型的話)篮条。如果問題類型是Choice那就根據(jù)選項(xiàng)生產(chǎn)一個(gè)下拉菜單弟头,如果類型是Input,那就簡(jiǎn)單地展示input輸入框涉茧。

該代碼有一個(gè)限制赴恨,就是如果再增加一個(gè)question類型的話,那就需要再次修改renderQuestion里的條件語句伴栓,這明顯違反了開閉原則伦连。

重構(gòu)代碼

讓我們來重構(gòu)一下這個(gè)代碼雨饺,以便在出現(xiàn)新question類型的情況下允許擴(kuò)展view對(duì)象的render能力,而不需要修改view對(duì)象內(nèi)部的代碼惑淳。

先來創(chuàng)建一個(gè)通用的questionCreator函數(shù):

function questionCreator(spec, my) {
    var that = {};
 
    my = my || {};
    my.label = spec.label;
 
    my.renderInput = function () {
        throw not implemented; 
        // 這里renderInput沒有實(shí)現(xiàn)额港,主要目的是讓各自問題類型的實(shí)現(xiàn)代碼去覆蓋整個(gè)方法
    };
 
    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()
        // 因?yàn)椴煌膯栴}類型有不同的實(shí)現(xiàn)
 
        questionWrapper.appendChild(questionLabel);
        questionWrapper.appendChild(answer);
        return questionWrapper;
    };
 
    return that;
}

該代碼的作用組合要是render一個(gè)問題,同時(shí)提供一個(gè)未實(shí)現(xiàn)的renderInput方法以便其他function可以覆蓋歧焦,以使用不同的問題類型移斩,我們繼續(xù)看一下每個(gè)問題類型的實(shí)現(xiàn)代碼:

function choiceQuestionCreator(spec) {
 
    var my = {},
that = questionCreator(spec, my);
             
    // choice類型的renderInput實(shí)現(xiàn)
    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實(shí)現(xiàn)
    my.renderInput = function () {
        var input = document.createElement('input');
        input.type = 'text';
        return input;
    };
 
    return that;
}

choiceQuestionCreator函數(shù)和inputQuestionCreator函數(shù)分別對(duì)應(yīng)下拉菜單和input輸入框的renderInput實(shí)現(xiàn),通過內(nèi)部調(diào)用統(tǒng)一的questionCreator(spec, my)然后返回that對(duì)象(同一類型哦)绢馍。

view對(duì)象的代碼就很固定了向瓷。


var view = {
    render: function(target, questions) {
        for (var i = 0; i < questions.length; i++) {
            target.appendChild(questions[i].render());
        }
    }
};

所以我們聲明問題的時(shí)候只需要這樣做,就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);

完整代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JS開閉原則</title>
</head>
<body>
<div id="questions"></div>
</body>
<script type="text/javascript">
function questionCreator(spec, my) {

    var that = {};
    
    my = my || {};
    my.label = spec.label;

    my.renderInput = function() {

        // 1這里renderInput沒有實(shí)現(xiàn)风罩,主要目的是讓各自問題類型的實(shí)現(xiàn)代碼去覆蓋整個(gè)方法
    };
    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();
        console.log(answer)
        // 該render方法是同樣的粗合理代碼
        // 唯一的不同就是上面的一句my.renderInput()
        // 因?yàn)椴煌膯栴}類型有不同的實(shí)現(xiàn)

        questionWrapper.appendChild(questionLabel);
        questionWrapper.appendChild(answer);
        return questionWrapper;
    };

    return that;
}



function choiceQuestionCreator(spec) {
    var my = {};

    var that = questionCreator(spec, my);

    // choice類型的renderInput實(shí)現(xiàn)
    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;
    };
    //返回一個(gè)方法
    return that;
}

function inputQuestionCreator(spec) {

    var my = {},
        that = questionCreator(spec, my);

    // input類型的renderInput實(shí)現(xiàn)
    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({//運(yùn)行choiceQuestionCreator函數(shù)
        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);
</script>
</html>

重構(gòu)后的最終代碼

上面的代碼里應(yīng)用了一些技術(shù)點(diǎn),我們來逐一看一下:

首先舵稠,questionCreator方法的創(chuàng)建超升,可以讓我們使用模板方法模式將處理問題的功能delegat給針對(duì)每個(gè)問題類型的擴(kuò)展代碼renderInput上。

其次哺徊,我們用一個(gè)私有的spec屬性替換掉了前面question方法的構(gòu)造函數(shù)屬性室琢,因?yàn)槲覀兎庋b了render行為進(jìn)行操作,不再需要把這些屬性暴露給外部代碼了落追。

第三盈滴,我們?yōu)槊總€(gè)問題類型創(chuàng)建一個(gè)對(duì)象進(jìn)行各自的代碼實(shí)現(xiàn),但每個(gè)實(shí)現(xiàn)里都必須包含renderInput方法以便覆蓋questionCreator方法里的renderInput代碼轿钠,這就是我們常說的策略模式巢钓。

通過重構(gòu),我們可以去除不必要的問題類型的枚舉AnswerType疗垛,而且可以讓choices作為choiceQuestionCreator函數(shù)的必選參數(shù)(之前的版本是一個(gè)可選參數(shù))症汹。

總結(jié)

重構(gòu)以后的版本的view對(duì)象可以很清晰地進(jìn)行新的擴(kuò)展了,為不同的問題類型擴(kuò)展新的對(duì)象贷腕,然后聲明questions集合的時(shí)候再里面指定類型就行了背镇,view對(duì)象本身不再修改任何改變,從而達(dá)到了開閉原則的要求泽裳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瞒斩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子涮总,更是在濱河造成了極大的恐慌胸囱,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妹卿,死亡現(xiàn)場(chǎng)離奇詭異旺矾,居然都是意外死亡蔑鹦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門箕宙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嚎朽,“玉大人,你說我怎么就攤上這事柬帕∮慈蹋” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵陷寝,是天一觀的道長(zhǎng)锅很。 經(jīng)常有香客問我,道長(zhǎng)凤跑,這世上最難降的妖魔是什么爆安? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮仔引,結(jié)果婚禮上扔仓,老公的妹妹穿的比我還像新娘。我一直安慰自己咖耘,他們只是感情好翘簇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著儿倒,像睡著了一般版保。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夫否,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天彻犁,我揣著相機(jī)與錄音,去河邊找鬼凰慈。 笑死袖裕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的溉瓶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谤民,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼堰酿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起张足,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤触创,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后为牍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哼绑,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岩馍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抖韩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛀恩。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖茂浮,靈堂內(nèi)的尸體忽然破棺而出双谆,到底是詐尸還是另有隱情,我是刑警寧澤席揽,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布顽馋,位于F島的核電站,受9級(jí)特大地震影響幌羞,放射性物質(zhì)發(fā)生泄漏寸谜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一属桦、第九天 我趴在偏房一處隱蔽的房頂上張望熊痴。 院中可真熱鬧,春花似錦地啰、人聲如沸愁拭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岭埠。三九已至,卻和暖如春蔚鸥,著一層夾襖步出監(jiān)牢的瞬間惜论,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工止喷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留馆类,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓弹谁,卻偏偏與公主長(zhǎng)得像乾巧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子预愤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,072評(píng)論 25 707
  • 本文出自《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》中的第一章沟于。 1、優(yōu)化代碼的第一步——單一職責(zé)原則 單一職責(zé)原則的...
    MrSimp1e0閱讀 1,766評(píng)論 1 13
  • 設(shè)計(jì)模式之六大原則(轉(zhuǎn)載) 關(guān)于設(shè)計(jì)模式的六大設(shè)計(jì)原則的資料網(wǎng)上很多...
    霄霄霄霄閱讀 899評(píng)論 0 1
  • 一座小城植康, 藏著童年的記憶旷太, 那是我心靈的花苞出土的地方, 天然的,濃濃的純凈供璧, 現(xiàn)在存崖,小城愈加潔凈, 潔凈地很難...
    童淑閱讀 349評(píng)論 2 2
  • 一個(gè)人不能沒有夢(mèng)想睡毒,我的夢(mèng)想是成為天使守護(hù)你...... 一個(gè)人不能沒有理想来惧,我的理想是掌握天下所有技能為你服務(wù)....
    babybus_hentai閱讀 191評(píng)論 0 0