3. 設計模式之原則

在最早接觸設計模式的時候,我就看到了對「開閉原則」的介紹,後續又陸陸續續接觸了「單一職責原則」、「迪米特法則」等等,今天在這裏對設計模式的各原則進行一個統一的記錄。javascript

  • 單一職責原則
  • 開放-封閉原則
  • 裏式代換原則
  • 依賴倒轉原則
  • 迪米特法則
  • 接口隔離原則

單一職責原則

單一職責原則(SRP),就一個類而言,應該僅有一個引發它變化的緣由。

這個原則說的就是一個類只關注一件事情,咱們常常會看到這個原則,好比咱們開始學習編程的時候,會說到怎麼優化代碼,其中一項就是一個函數只作一件事情這樣的,那時候「只作一件事情」就在我腦海中留下了比較深入的印象。前端

如今隨着前端組件化的發展,咱們將頁面也細分爲大大小小的組件來進行拼接。在這個時候,「單一職責原則」就和前端的工做息息相關了,咱們在拆分組件的時候,也應該保證每個組件只作一件事情,只關注一個重點。這樣咱們的頁面在後期的擴展維護上就會節省不少的功夫,這也是優秀的組件應該作到的。java

「單一職責原則」的有點也是顯而易見的:git

  • 代碼複雜度變低
  • 可維護性提升
  • 需求變動引發的影響減少

開放-封閉原則

開放-封閉原則,是說軟件實體(類、模塊、函數等等)應該能夠擴展,可是不可修改。[ASD]

咱們在開發系統的時候,常常會有這樣的感覺,在產品最開始設計的時候,咱們就儘量把準備工做多作一些,但願最開始的框架可以知足將來的一些需求。可是隨着開發工做的逐漸深刻,咱們最頭疼的狀況就發生了:無論咱們開始的時候想的有多麼完善,都沒法知足將來的需求,老是須要去擴展咱們的框架。這種時候,「開放-封閉原則」就是咱們的指導。github

其實咱們須要面對的問題就是:但原框架沒法適應新需求的時候,咱們是修改原來的代碼,仍是擴展原來的代碼?咱們常常會發現一個需求只要修改一小段代碼就能夠完成,而後咱們就用了很快的時間完成了任務,以後卻發現引發了許多的問題。因此咱們在處理新需求的時候,應該更多地在源代碼的基礎之上去加東西,而不要去改動原有的代碼,以防止影響原來的功能。編程

「開放封閉原則」的特徵就是:設計模式

  • 對於擴展是開放的
  • 對於更改是封閉的

它的優勢也顯而易見:前端工程師

  • 提升了系統的穩定性
開放-封閉原則是面向對象設計的核心所在。遵循這個原則能夠帶來面向對象技術所聲稱的巨大好處,也就是可維護、可擴展、複用性好、靈活性好。開發人員應該僅對程序中呈現出頻繁變化的那些部分作出抽象,然而,對於應用程序中的每一個部分都刻意地進行抽象一樣不是一個好主意。拒毫不成熟的抽象和抽象自己同樣重要。

裏式代換原則

里氏代換原則:子類型必須可以替換掉他們的父類型。

個人理解是「裏式代換原則」保留了父類的可擴展性,即咱們設計的時候在父類的基礎上添加特性,在具體實現的時候利用子類來實現。好處就是:echarts

  • 能夠隨時隨地地擴展父類,由於父類的擴展不影響具體功能
  • 全部具體功能的實現都由子類去作,系統是感知不到父類的存在的
  • 父類能夠真正的被複用

依賴倒轉原則

依賴倒轉原則

A. 高層模塊不該該依賴低層模塊。兩個都應該依賴抽象。框架

B. 抽象不該該依賴細節。細節應該依賴抽象。

說的清楚一些應該就是咱們要「針對接口編程,而不是針對實現編程」。

假如當下咱們要作一個關於考試分數的柱狀圖標,首先咱們就會想到用echarts來作這個圖表。

// echarts類
class Echarts {
    constructor() {}

    // 繪製柱狀圖
    renderBar(options) {}

    // 繪製折線圖圖
    renderLine(options) {}

    // 繪製餅圖
    renderPie(options) {}
}

// 繪製圖表類
class Render {
    tool = new Echarts();
    constructor() {}

    // 繪製柱狀圖
    renderBar(options) {
        this.tool.renderBar(options);
    }

    // 繪製折線圖圖
    renderLine(options) {
        this.tool.renderLine(options);
    }

    // 繪製餅圖
    renderPie(options) {
        this.tool.renderLine(options);
    }
}

const render = new Render();
render.renderBar();
render.renderLine();
render.renderLine();

可是這時客戶以爲更喜歡阿里的G2圖表,這時咱們修改代碼就發現很難去修改,由於咱們的業務繪製代碼直接依賴了Echarts,若是咱們須要改圖表依賴,咱們就要改不少東西,那怎麼作纔好一些呢?

// 依賴倒置原則
// echarts類
class Echarts {
    constructor() {}

    // 繪製柱狀圖
    renderBar(options) {}

    // 繪製折線圖圖
    renderLine(options) {}

    // 繪製餅圖
    renderPie(options) {}
}

// g2
class G2 {
    constructor() {}

    // 繪製柱狀圖
    renderBar(options) {}

    // 繪製折線圖圖
    renderLine(options) {}

    // 繪製餅圖
    renderPie(options) {}
}

function getTool(type) {
    switch (type) {
        case 'echarts':
            return new Echarts();
            break;
        case 'g2':
            return new G2();
            break;
    }
}

// 繪製圖表類
class Render {
    constructor(tool) {
        this.tool = tool;
    }

    // 繪製柱狀圖
    renderBar(options) {
        this.tool.renderBar(options);
    }

    // 繪製折線圖圖
    renderLine(options) {
        this.tool.renderLine(options);
    }

    // 繪製餅圖
    renderPie(options) {
        this.tool.renderLine(options);
    }
}

const render = new Render(getTool('g2'));
render.renderBar();
render.renderLine();
render.renderLine();

由上可見,咱們新增了一個getTool的方法,若是咱們須要調整或者擴展圖表類,咱們只須要擴展底層方法便可,代價和難度都會大大下降,也減小了高層代碼(繪製圖表類)對底層代碼(圖表類)的依賴。

迪米特法則

迪米特法則(LoD):若是兩個類沒必要彼此直接通訊,那麼這兩個類就不該當發生直接的相互做用。若是其中一個類須要調用另外一個類的某一個方法的話,能夠經過第三者轉發這個調用。

舉個例子,如今咱們打車都用上了高德地圖,可是仍是有些地方地圖定位的不是特別準確,這些時候就須要咱們口頭告訴司機應該怎麼走。這種場景下咱們就有了3個類:乘客司機汽車,其實咱們最終的目的是但願指揮車子的運動(向左轉、向右轉等等),可是其實咱們和車子的控制沒有關係,咱們就須要經過第三者(司機)來達成目的。

// 迪米特法則
class Car {
    constructor() {}

    // 左轉
    turnLeft() {}

    // 右轉
    turnRight() {}

    // 掉頭
    turnRound() {}

    // 直行
    goStraight() {}
}

class Driver {
    constructor(car) {
        this.car = car;
    }

    // 左轉
    turnLeft() {
        this.car.turnLeft();
    }

    // 右轉
    turnRight() {
        this.car.turnRight();
    }

    // 掉頭
    turnRound() {
        this.car.turnRound();
    }

    // 直行
    goStraight() {
        this.car.goStraight();
    }
}

class Customer {
    constructor() {}

    inform(driver, direction) {
        switch (direction) {
            case 'left':
                driver.turnLeft();
                break;
            case 'right':
                driver.turnRight();
                break;
            case 'round':
                driver.turnRound();
                break;
            case 'straight':
                driver.goStraight();
                break;
        }
    }
}

const car = new Car();
const driver = new Driver(car);
const customer = new Customer();

// 乘客告訴司機下個路口左轉
customer.inform(driver, 'left');
// 乘客告訴司機下個路口右轉
customer.inform(driver, 'right');
  1. 在類的結構設計上,每個類都應當儘可能下降成員的訪問權限;
  2. 迪米特法則其根本思想,是強調了類之間的鬆耦合;
  3. 類之間的耦合越弱,越有利於複用,一個處在弱耦合的類被修改,不會對有關係的類形成波及。

接口隔離原則

一個類對另外一個類的依賴應該創建在最小的接口上。

對於這個原則,個人理解是這樣的:在當下咱們寫頁面的時候,經常使用的UI框架有ElementUI和Ant Design UI,在這兩個UI框架中,有三個組件:InputAutoCompleteInputNumber,咱們會發現他們有不少共同的地方:

  • 都是輸入框
  • 支持相同的事件(Focus、Blur等等)
  • 支持相同的屬性(readOnly,autofocus等等)

除此以外他們也都有本身的特性:

  • AutoComplete支持自動搜索
  • InputNumber對數字有特殊的處理

其實咱們能不能把這些特性所有集中在Input上呢?其實應該也是能夠的,那樣的話Input組件就會很臃腫,也提供了不少咱們平時用不到的特性。

那最好的方式就是將「自動搜索」和「數字特殊處理」當作獨立接口處理,以造成咱們今天的3個組件,讓咱們在須要的地方用最適合的組件。

我想這是前端工程師對「接口隔離原則」的一個很棒的應用。


參考

大話設計模式 -- 程傑
Element-UI
Ant Design

我的博客

北落師門的博客

相關文章
相關標籤/搜索