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)