如何写出优雅的 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和完善,你们面试能够参照考点复习,另外关注公众号,后台回复福利,便可看到福利,你懂的。

相关文章
相关标签/搜索