面向對象設計的SOLID原則

文章首發於公衆號:松花皮蛋的黑板報數據庫

做者就任於京東,在穩定性保障、敏捷開發、高級JAVA、微服務架構有深刻的理解bash

一、單一職責原則

考慮下面這個類架構

class Animal {
    constructor(name: string){ }
    getAnimalName() { }
    saveAnimal(a: Animal) { }
}
複製代碼

它實際上違背了單一職責原則SRP。上面的類其實有兩個職責,一爲動物實體的持久化管理,另一個爲動物的屬性管理。微服務

那咱們應該如何設計避免這種錯誤呢?咱們能夠新建另一個類,它負責將實體對象存儲到數據庫上。以下所示:post

class Animal {
    constructor(name: string){ }
    getAnimalName() { }
}
class AnimalDB {
    getAnimal(a: Animal) { }
    saveAnimal(a: Animal) { }
}
複製代碼

當咱們設計類時須要注意的一點是,若是功能因不一樣緣由而發生變化,咱們應該嘗試將功能分開。ui

二、開閉原則

這個原則強調現有接口規範能夠經過繼承重用,不要修改現有已完成的接口。this

咱們繼續以動物這個類說明url

class Animal {
    constructor(name: string){ }
    getAnimalName() { }
}
複製代碼

咱們的需求是讓列表中每一個動物發出不一樣的聲音,以下所示spa

//...
const animals: Array<Animal> = [
    new Animal('lion'),
    new Animal('mouse')
];
function AnimalSound(a: Array<Animal>) {
    for(int i = 0; i <= a.length; i++) {
        if(a[i].name == 'lion')
            log('roar');
        if(a[i].name == 'mouse')
            log('squeak');
    }
}
AnimalSound(animals);
複製代碼

上面的示例違背了開閉原則,當有新的類型須要處理時,將不得不在原有代碼上進行修改,致使大量的閱讀性差的IF條件語句。設計

那咱們應該怎麼設計避免這種錯誤呢?咱們能夠定義一個有makeSound方法的共同類好比說Animal類,而後每一個具體動物類繼承並重寫makeSound方法,完成個性化處理。另外真正的處理業務的AnimalSound類遍歷Animal列表而後調用makeSound方法便可。當新擴展一個具體動物類時,AnimalSound類無須作任何修改。代碼以下:

class Animal {
        makeSound();
        //...
}
class Lion extends Animal {
    makeSound() {
        return 'roar';
    }
}
class Squirrel extends Animal {
    makeSound() {
        return 'squeak';
    }
}
class Snake extends Animal {
    makeSound() {
        return 'hiss';
    }
}
//...
function AnimalSound(a: Array<Animal>) {
    for(int i = 0; i <= a.length; i++) {
        log(a[i].makeSound());
    }
}
AnimalSound(animals);
複製代碼

三、里氏替換原則

這個原則強調當一個類繼承另一個類時,除添加新的方法外,儘可能不要重寫或者重載父類的方法。不然引用基類完成的功能,換成子類後就會出現異常。

咱們繼續使用Animal類進行說明,先看一段代碼,以下:

//...
function AnimalLegCount(a: Array<Animal>) {
    for(int i = 0; i <= a.length; i++) {
        if(typeof a[i] == Lion)
            log(LionLegCount(a[i]));
        if(typeof a[i] == Mouse)
            log(MouseLegCount(a[i]));
        if(typeof a[i] == Snake)
            log(SnakeLegCount(a[i]));
    }
}
AnimalLegCount(animals);
複製代碼

上面的示例違背了這個原則,由於必須知道每一個類型才能決定使用哪一個方法。

那咱們應該怎麼設計才能避免這種錯誤呢?咱們應該保證子類的方法參數必須和超類的參數類型等同,或者爲其超類參數的子類;咱們應該保證子類的方法返回類型必須和超類的返回類型等同,或者爲其超類返回類型的子類。

接下來咱們進行代碼改造:

function AnimalLegCount(a: Array<Animal>) {
    for(let i = 0; i <= a.length; i++) {
        a[i].LegCount();
    }
}
AnimalLegCount(animals);
複製代碼

改造後的方法不對參數進行任何類型判斷,只關心它是否爲Animal類或者爲Animal的子類而後調用LegCount方法。

四、接口隔離原則

這個原則強調不該該強迫實現類實現它不須要的接口方法。

假設有以下這個接口類:

interface IShape {
    drawCircle();
    drawSquare();
    drawRectangle();
}
複製代碼

它有三個互不相關的方法,然而子類必須實現永遠使用不上的方法。當咱們在接口類中新增一個抽象方法時,必須修改全部子類而後實現可能更離譜的方法。

那咱們應該怎麼設計避免這種錯誤呢?答案是接口隔離,以下:

interface IShape {
    draw();
}
interface ICircle {
    drawCircle();
}
interface ISquare {
    drawSquare();
}
interface IRectangle {
    drawRectangle();
}
interface ITriangle {
    drawTriangle();
}
class Circle implements ICircle {
    drawCircle() {
        //...
    }
}
class Square implements ISquare {
    drawSquare() {
        //...
    }
}
class Rectangle implements IRectangle {
    drawRectangle() {
        //...
    }    
}
class Triangle implements ITriangle {
    drawTriangle() {
        //...
    }
}
class CustomShape implements IShape {
   draw(){
      //...
   }
}
複製代碼

五、依賴倒置原則

這個原則強調高層模塊不該該依賴具體細節實現模塊,二者都應該依賴上層的抽象模塊。固然抽象不該該依賴細節,細節才應該依賴上層的抽象。

咱們如下面的代碼進行說明:

class XMLHttpService extends XMLHttpRequestService {}
class Http {
    constructor(private xmlhttpService: XMLHttpService) { }
    get(url: string , options: any) {
        this.xmlhttpService.request(url,'GET');
    }
    post() {
        this.xmlhttpService.request(url,'POST');
    }
    //...
}
複製代碼

上面的示例違背了這個原則,高層模塊的Http類依賴了低層模塊的XMLHttpService類,當咱們改變請求的實現時,好比使用NodeJs、Mock服務,咱們須要細心地重構上述代碼,得不償失。

實際上高層模塊的Http類不該該關心具體的的實現細節,接起來,咱們進行改造。

定義一個請求抽象類Connection類

interface Connection {
    request(url: string, opts:any);
}
複製代碼

Http類的參數修改成抽象Connection類

class Http {
    constructor(private httpConnection: Connection) { }
    get(url: string , options: any) {
        this.httpConnection.request(url,'GET');
    }
    post() {
        this.httpConnection.request(url,'POST');
    }
    //...
}
複製代碼

這樣的好處是,Http類能夠輕鬆完成請求業務邏輯而無須關注具體的實現類型。

文章來源:www.liangsonghua.me

做者介紹:京東資深工程師-梁鬆華,在穩定性保障、敏捷開發、JAVA高級、微服務架構方面有深刻的理解

相關文章
相關標籤/搜索