JavaScript 中的 SOLID 原則(二):“O”代表什么

JavaScript 中的 SOLID 原則:“S”代表什么

你可能已經(jīng)了解過一些設(shè)計(jì)原則或者設(shè)計(jì)模式,本文主要漸進(jìn)的講解了SOLID原則:

  • 不使用SOLID是怎么編寫代碼的,存在什么問題?

  • 應(yīng)該使用SOLID中的哪個(gè)原則?

  • 使用SOLID我們應(yīng)該如何對(duì)代碼進(jìn)行修改?

相信對(duì)比和沉浸式的示例會(huì)讓你更容易理解SOLID原則,以及如何應(yīng)用到代碼實(shí)踐中。

這是SOLID的第二篇翻譯文章(原文一共五篇),作者是serhiirubets,歡迎持續(xù)關(guān)注。

在本文中,我們將討論什么是 SOLID 原則,為什么我們應(yīng)該使用他們和如何在JavaScript中使用他們。

什么是SOLID

SOLID 是 Robert C. Martin 的前五個(gè)面向?qū)ο笤O(shè)計(jì)原則的首字母縮寫詞。 這些原則的目的是:讓你的代碼、架構(gòu)更具可讀性、可維護(hù)性、靈活性。

開閉原則(Open-Closed Principle)

O - 開閉原則。實(shí)體(類、模塊、方法、文件等)應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。從定義上很難理解,來看幾個(gè)例子:

假設(shè):我們有幾個(gè)不同的形狀,圓形、方向、三角形,需要計(jì)算他們的面積總和。如何解決呢?

沒什么難的,讓我們?yōu)槊總€(gè)形狀創(chuàng)建一個(gè)類,每個(gè)類有不同的字段:大小、高度、寬度、半徑和類型字段。當(dāng)計(jì)算每個(gè)形狀的面積時(shí),我們使用類型字段來區(qū)分。

class Square{
    constructor(size){
        this.size = size;
        this.type ='square' ;
    }
}

class Circle{
    constructor(radius) {
        this.radius = radius;
        this.type = 'circle' ;
    }
}

class Rect{
    constructor(width, height) {
        this.width = width
        this.height = height;
        this.type = 'rect' ;
    }
}

我們?cè)賱?chuàng)建一個(gè)函數(shù),來計(jì)算面積。

function getTotalAreas (shapes){
    return shapes.reduce((total, shape) =>{
        if (shape.type =='square') {
            total += shape.size * shape.size;
        }else if (shape.type = 'circle') {
            total += Math.PI * shape.radius;
        }else if (shape. type == ' rect') {
            total += shape.width * shape.height;
        }
        return total;
    }, 0);
}
getTotalAreas([
    new Square(5),
    new Circle(4),
    new Rect(7,14)
]);

似乎看起來并沒有什么問題,但是想象一下,如果我們想添加另一個(gè)形狀(原型、橢圓、菱形),我們應(yīng)該怎么做?我們需要為他們中的每一個(gè)創(chuàng)建一個(gè)新的類,定義類型并在getTotalAreas中添加新的if/else。

注意:

O - 開閉原則。讓我們?cè)僦貜?fù)一遍:這個(gè)原則是指:實(shí)體(類、模塊、方法等)應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。

在getTotalAreas中,每次添加新的形狀都需要進(jìn)行修改。這不符合開閉原則,我們需要做什么調(diào)整?

我們需要在每個(gè)類中創(chuàng)建getArea方法(類型字段已經(jīng)不再需要,已被刪除)。

class Square {
    constructor(size) {
        this.size = size;
    }
    getArea() {
        return this.size * this.size;
    }
}
class Circle {
    constructor(radius) {
        this.radius = radius;
    }
    getArea() {
        return Math.PI * (this.radius * this.radius);
    }
}
class Rect {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }
    getArea() {
        return this.width * this.height;
    }
}
function getTotalAreas (shapes) {
    return shapes. reduce((total, shape) => {
        return total + shape. getArea();
    },0)
}
getTotalAreas([
    new Square(5),
    new Circle(4),
    new Rect(7,14)
]);

現(xiàn)在我們已經(jīng)遵循了開閉原則,當(dāng)我們要添加另一個(gè)形狀,比如三角形,我們會(huì)創(chuàng)建一個(gè)Triangle類(對(duì)擴(kuò)展開放),定義一個(gè)getArea方法,僅此而已。我們不需要修改getTotalAreas方法(對(duì)修改關(guān)閉),只需要在調(diào)用getTotalAreas時(shí)向其數(shù)組增加一個(gè)參數(shù)。

我們?cè)賮砜匆粋€(gè)更實(shí)際的例子, 假設(shè)客戶端接收一個(gè)指定格式的錯(cuò)誤驗(yàn)證消息:

const response = {
    errors: {
        name: ['The name field should be more than 2 letters', 'The name field should not contains numbers'] ,
        email: ['The email field is required'],
        phone: ['User with provided phone exist']
    }
}

想象一下,服務(wù)端使用了不同的服務(wù)來驗(yàn)證,可能是我們自己的服務(wù),也可能是返回不同格式錯(cuò)誤的外部服務(wù)。

讓我們使用盡可能用簡(jiǎn)單的示例來模擬錯(cuò)誤:

const errorFromFacebook ='Bad credentials' ;
const errorFromTwitter = ['Bad credentials'];
const errorFromGoogle = { error: 'Bad credentials' }
function requestToFacebook() {
    return {
        type: 'facebook',
        error: errorFromFacebook
    }
}
function requestToTwitter() {
    return {
        type: 'twitter',
        error: errorFromTwitter
    }
}
function requestToGoogle() {
    return {
        type: 'google',
        error: errorFromGoogle
    }
}

我們來把錯(cuò)誤轉(zhuǎn)換成客戶端所需要的格式:

function getErrors() {
    const errorsList = [requestToFacebook(), requestToTwitter(), requestToGoogle()]; 
    const errors = errorsList.reduce((res, error) => {
        if (error.type == ' facebook') {
            res.facebookUser = [error.error]
        }
        if (error.type == 'twitter') {
            res.twitterUser = error.error;
        }
        if (error.type == 'google') {
            res.googleUser = [error.error];
        }
        return res;
    },[]);
    return { errors };   
}
console.log(getErrors());

我們就得到了客戶端所期望的結(jié)果:

{
    errors: {
        facebookUser:['Bad credentials'],
        twitterUser:['Bad credentials'],
        googleUser:['Bad credentials']
    }
}

但是,還是同樣的問題,我們沒有遵循開閉原則,當(dāng)我們需要從外部服務(wù)添加一個(gè)新的驗(yàn)證時(shí),我們就需要修改getErrors方法,添加新的if/else邏輯。

怎么解決這個(gè)問題呢?一個(gè)可行的解決方案是:我們可以創(chuàng)建一些通用的錯(cuò)誤驗(yàn)證類,并在其中定義一些通用的邏輯。我們就可以為每個(gè)錯(cuò)誤創(chuàng)建一個(gè)我們自己的類(FaceBookValidationError,GoogleValidationError)。

在每個(gè)類中,我們可以指定方法,像getErrors或TransformErrors,每個(gè)validationError類都應(yīng)該遵循這個(gè)規(guī)則。

const errorFromFacebook =' Bad credentials ' ;
const errorFromTwitter = ['Bad credentials'];
const errorFromGoogle = {error: ' Bad credentials'} 
class ValidationError {
    constructor(error) {
        this.error = error;
    }
    getErrors() {}
}
class FacebookValidationError extends ValidationError {
    getErrors() {
        return { key: ' facebookUser', text:[this.error] };
    }
}
class TwitterValidationError extends ValidationError {
    getErrors() {
        return {
            key: ' twitterUser',
            text: this.error
        }
    }
}
class GoogleValidationError extends ValidationError {
    getErrors() {
        return { key: ' googleUser', text: [this.error.error] }
    }
}

我們來在Mock的函數(shù)中使用這個(gè)錯(cuò)誤驗(yàn)證類,修改getErrors函數(shù):

function requestToFacebook() {
    return new FacebookValidationError(errorFromFacebook)
}

function requestToTwitter() {
    return new TwitterValidationError(errorFromTwitter)
}
function requestToGoogle() {
    return new GoogleValidationError(errorFromGoogle)
}

function getErrors (errorsList) {
    const errors = errorsList.reduce((res, item) => {
        const error = item.getErrors();
        res[error.key] = error.text
        return res ;
    }, {});
    return {errors}
}

console.log(getErrors([requestToFacebook(), requestToTwitter(), requestToGoogle()]));

可以看到,在getErrors函數(shù)接收errorList作為參數(shù),而不是在函數(shù)中進(jìn)行硬編碼。運(yùn)行結(jié)果是一樣的,但是我們遵循了開閉原則,當(dāng)新增一個(gè)錯(cuò)誤時(shí):我們可以為這個(gè)錯(cuò)誤創(chuàng)建一個(gè)新的驗(yàn)證類并且指定getErrors方法(對(duì)擴(kuò)展開放),getErrors可以幫我們把外部服務(wù)返回的信息轉(zhuǎn)換成我們需要的格式。我們?cè)谕ㄓ玫膅etErrors方法中來調(diào)用錯(cuò)誤類的getErrors,無需進(jìn)行其他修改(對(duì)修改關(guān)閉)。

歡迎關(guān)注微信公眾號(hào)”混沌前端“

推薦閱讀:

基于TypeScript理解程序設(shè)計(jì)的SOLID原則

clean-code-javascript: SOLIDhttps://github.com/ryanmcdermott/clean-code-javascript#solid)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容