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ù)。
讓我們使用盡可能用簡單的示例來模擬錯(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)