如何寫出優雅的 JS 代碼?使用 SOLID 原則

做者:ryanmcdermott
譯者:前端小智
來源:github
點贊再看,養成習慣

本文 GitHub https://github.com/qq44924588... 上已經收錄,更多往期高贊文章的分類,也整理了不少個人文檔,和教程資料。歡迎Star和完善,你們面試能夠參照考點複習,但願咱們一塊兒有點東西。javascript

設計模式的六大原則有:前端

  • Single Responsibility Principle:單一職責原則
  • Open Closed Principle:開閉原則
  • Liskov Substitution Principle:里氏替換原則
  • Law of Demeter:迪米特法則
  • Interface Segregation Principle:接口隔離原則
  • Dependence Inversion Principle:依賴倒置原則

把這六個原則的首字母聯合起來(兩個 L 算作一個)就是 SOLID (solid,穩定的),其表明的含義就是這六個原則結合使用的好處:創建穩定、靈活、健壯的設計。下面咱們來分別看一下這六大設計原則。java

單一職責原則(SRP)

單一功能原則 :單一功能原則 認爲對象應該僅具備一種單一功能的概念。node

換句話說就是讓一個類只作一種類型責任,當這個類須要承擔其餘類型的責任的時候,就須要分解這個類。在全部的SOLID原則中,這是大多數開發人員感到最能徹底理解的一條。嚴格來講,這也多是違反最頻繁的一條原則了。單一責任原則能夠看做是低耦合、高內聚在面向對象原則上的引伸,將責任定義爲引發變化的緣由,以提升內聚性來減小引發變化的緣由。責任過多,可能引發它變化的緣由就越多,這將致使責任依賴,相互之間就產生影響,從而極大的損傷其內聚性和耦合度。單一責任,一般意味着單一的功能,所以不要爲一個模塊實 現過多的功能點,以保證明體只有一個引發它變化的緣由。git

很差的寫法github

class UserSettings {
  constructor(user) {
    this.user = user;
  }

  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }

  verifyCredentials() {
    // ...
  }
}

好的寫法面試

class UserAuth {
  constructor(user) {
    this.user = user;
  }

  verifyCredentials() {
    // ...
  }
}

class UserSettings {
  constructor(user) {
    this.user = user;
    this.auth = new UserAuth(user);
  }

  changeSettings(settings) {
    if (this.auth.verifyCredentials()) {
      // ...
    }
  }
}

開放閉合原則 (OCP)

軟件實體應該是可擴展,而不可修改的。也就是說,對擴展是開放的,而對修改是封閉的。這個原則是諸多面向對象編程原則中最抽象、最難理解的一個。ajax

  1. 經過增長代碼來擴展功能,而不是修改已經存在的代碼。
  2. 若客戶模塊和服務模塊遵循同一個接口來設計,則客戶模塊能夠不關心服務模塊的類型,服務模塊能夠方便擴展服務(代碼)。
  3. OCP支持替換的服務,而不用修改客戶模塊。

說大白話就是:你不是要變化嗎?,那麼我就讓你繼承實現一個對象,用一個接口來抽象你的職責,你變化越多,繼承實現的子類就越多。編程

很差的寫法設計模式

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    if (this.adapter.name === "ajaxAdapter") {
      return makeAjaxCall(url).then(response => {
        // transform response and return
      });
    } else if (this.adapter.name === "nodeAdapter") {
      return makeHttpCall(url).then(response => {
        // transform response and return
      });
    }
  }
}

function makeAjaxCall(url) {
  // request and return promise
}

function makeHttpCall(url) {
  // request and return promise
}

好的寫法

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }

  request(url) {
    // request and return promise
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }

  request(url) {
    // request and return promise
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this.adapter.request(url).then(response => {
      // transform response and return
    });
  }
}

里氏替換原則(LSP)

里氏替換原則 :里氏替換原則 認爲「程序中的對象應該是能夠在不改變程序正確性的前提下被它的子類所替換的」的概念。

LSP則給了咱們一個判斷和設計類之間關係的基準:需不需 要繼承,以及怎樣設計繼承關係。

當一個子類的實例應該可以替換任何其超類的實例時,它們之間才具備is-A關係。繼承對於OCP,就至關於多態性對於里氏替換原則。子類能夠代替基類,客戶使用基類,他們不須要知道派生類所作的事情。這是一個針對行爲職責可替代的原則,若是ST的子類型,那麼S對象就應該在不改變任何抽象屬性狀況下替換全部T對象。

客戶模塊不該關心服務模塊的是如何工做的;一樣的接口模塊之間,能夠在不知道服務模塊代碼的狀況下,進行替換。即接口或父類出現的地方,實現接口的類或子類能夠代入。

很差的寫法

class Rectangle {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  setWidth(width) {
    this.width = width;
    this.height = width;
  }

  setHeight(height) {
    this.width = height;
    this.height = height;
  }
}

function renderLargeRectangles(rectangles) {
  rectangles.forEach(rectangle => {
    rectangle.setWidth(4);
    rectangle.setHeight(5);
    const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20.
    rectangle.render(area);
  });
}

const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);

好的寫法

class Shape {
  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Shape {
  constructor(length) {
    super();
    this.length = length;
  }

  getArea() {
    return this.length * this.length;
  }
}

function renderLargeShapes(shapes) {
  shapes.forEach(shape => {
    const area = shape.getArea();
    shape.render(area);
  });
}

const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);

接口隔離原則(ISP)

接口隔離原則 :接口隔離原則 認爲「多個特定客戶端接口要好於一個寬泛用途的接口」的概念。

不能強迫用戶去依賴那些他們不使用的接口。換句話說,使用多個專門的接口比使用單一的總接口總要好。

這個原則起源於施樂公司,他們須要創建了一個新的打印機系統,能夠執行諸如裝訂的印刷品一套,傳真多種任務。此係統軟件建立從底層開始編制,並實現了這些 任務功能,可是不斷增加的軟件功能卻使軟件自己愈來愈難適應變化和維護。每一次改變,即便是最小的變化,有人可能須要近一個小時的從新編譯和從新部署。這 是幾乎不可能再繼續發展,因此他們聘請羅伯特Robert幫助他們。他們首先設計了一個主要類Job,幾乎可以用於實現全部任務功能。只要調用Job類的 一個方法就能夠實現一個功能,Job類就變更很是大,是一個胖模型啊,對於客戶端若是隻須要一個打印功能,可是其餘無關打印的方法功能也和其耦合,ISP 原則建議在客戶端和Job類之間增長一個接口層,對於不一樣功能有不一樣接口,好比打印功能就是Print接口,而後將大的Job類切分爲繼承不一樣接口的子 類,這樣有一個Print Job類,等等。

很差的寫法

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.animationModule.setup();
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName("body"),
  animationModule() {} // Most of the time, we won't need to animate when traversing.
  // ...
});

好的寫法

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.options = settings.options;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.setupOptions();
  }

  setupOptions() {
    if (this.options.animationModule) {
      // ...
    }
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName("body"),
  options: {
    animationModule() {}
  }
});

依賴倒置原則(DIP)

依賴倒置原則: 依賴倒置原則 認爲一個方法應該聽從「依賴於抽象而不是一個實例」 的概念。依賴注入是該原則的一種實現方式。

依賴倒置原則(Dependency Inversion Principle,DIP)規定:代碼應當取決於抽象概念,而不是具體實現。

  • 高層模塊不要依賴低層模塊
  • 高層和低層模塊都要依賴於抽象;
  • 抽象不要依賴於具體實現
  • 具體實現要依賴於抽象
  • 抽象和接口使模塊之間的依賴分離

類可能依賴於其餘類來執行其工做。可是,它們不該當依賴於該類的特定具體實現,而應當是它的抽象。這個原則實在是過重要了,社會的分工化,標準化都 是這個設計原則的體現。顯然,這一律念會大大提升系統的靈活性。若是類只關心它們用於支持特定契約而不是特定類型的組件,就能夠快速而輕鬆地修改這些低級 服務的功能,同時最大限度地下降對系統其他部分的影響。

很差的寫法

class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryTracker {
  constructor(items) {
    this.items = items;

    // BAD: We have created a dependency on a specific request implementation.
    // We should just have requestItems depend on a request method: `request`
    this.requester = new InventoryRequester();
  }

  requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}

const inventoryTracker = new InventoryTracker(["apples", "bananas"]);
inventoryTracker.requestItems();

好的寫法

class InventoryTracker {
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }

  requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}

class InventoryRequesterV1 {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryRequesterV2 {
  constructor() {
    this.REQ_METHODS = ["WS"];
  }

  requestItem(item) {
    // ...
  }
}
const inventoryTracker = new InventoryTracker(
  ["apples", "bananas"],
  new InventoryRequesterV2()
);
inventoryTracker.requestItems();

原文:https://github.com/ryanmcderm...

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

交流

文章每週持續更新,能夠微信搜索「 大遷世界 」第一時間閱讀和催更(比博客早一到兩篇喲),本文 GitHub https://github.com/qq449245884/xiaozhi 已經收錄,整理了不少個人文檔,歡迎Star和完善,你們面試能夠參照考點複習,另外關注公衆號,後臺回覆福利,便可看到福利,你懂的。

本文同步分享在 博客「前端小智」(SegmentFault)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索