zoukankan      html  css  js  c++  java
  • 代码整洁之道——5、SOLID

    面向对象编程,五大原则:(这里只讲到一小部分,深入理解需要单独看设计模式)

    1. The Single Responsibility Principle(单一职责 SRP)
    2. The Open/Closed Principle(开闭原则 OCP)
    3. The Liskov Substitution Principle(里氏替换原则 LSP)
    4. The Interface Segregation Principle(接口分离原则 ISP)
    5. The Dependency Inversion Principle(依赖反转原则 DIP)

    一、S 单一职责原则

    正如代码整洁之道所述:“永远不要有超过一个理由去改变一个类”。给一个类很多功能,类似于你只能带一个行李箱上飞机。这样做的问题是,你的类不是高内聚,并且将会有很多理由要去改变这个类。减少改变一个类的次数是很重要的,因为一个类有多个函数,你修改了其中一部分,将很难搞清楚会影响代码库中的哪些其他地方。

    Bad:
    class UserSettings {
      constructor(user) {
        this.user = user;
      }
    
      changeSettings(settings) {
        if (this.verifyCredentials()) {
          // ...
        }
      }
    
      verifyCredentials() {
        // ...
      }
    }
    
    Good:
    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()) {
          // ...
        }
      }
    }

    二、开闭原则

    正如Bertrand Meyer所说的,软件整体(类、模块、函数等)都应该都扩展开放,对修改关闭。这是什么意思呢?这个原则基本阐述了,在不改变现有代码的基础上你应该允许用户增加新功能

    Bad:
    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 === 'httpNodeAdapter') {
          return makeHttpCall(url).then((response) => {
            // transform response and return
          });
        }
      }
    }
    
    function makeAjaxCall(url) {
      // request and return promise
    }
    
    function makeHttpCall(url) {
      // request and return promise
    }
    
    Good:
    //将各自的请求放在各自的类中,直接区分不通过判断区分
    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
        });
      }
    }

    三、里氏替换原则

    里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但是、不能改变父类原有功能。(这句话不是翻译的原文)

    最好的解释就是,你有一个父类和一个子类,那么子类和基类可以互换使用且不发生错误。这可能仍然很迷惑,所以我们看下矩形和正方形的经典例子。数学角度来讲,正方形是矩形,但是你用“is-a”的关系通过继承来实现,你很快就会遇到麻烦。

    Demo

     1 Bad:
     2 class Rectangle {
     3   constructor() {
     4     this.width = 0;
     5     this.height = 0;
     6   }
     7 
     8   setColor(color) {
     9     // ...
    10   }
    11 
    12   render(area) {
    13     // ...
    14   }
    15 
    16   setWidth(width) {
    17     this.width = width;
    18   }
    19 
    20   setHeight(height) {
    21     this.height = height;
    22   }
    23 
    24   getArea() {
    25     return this.width * this.height;
    26   }
    27 }
    28 
    29 class Square extends Rectangle {
    30   setWidth(width) {
    31     this.width = width;
    32     this.height = width;
    33   }
    34 
    35 //正方形继承了矩形这个类,但是这里重写了父类中的方法,setWidth同理
    36   setHeight(height) {
    37     this.width = height;
    38     this.height = height;
    39   }
    40 }
    41 
    42 function renderLargeRectangles(rectangles) {
    43   rectangles.forEach((rectangle) => {
    44     rectangle.setWidth(4);
    45     rectangle.setHeight(5);
    46     const area = rectangle.getArea(); // 应该返回20,但是返回的却是25
    47     rectangle.render(area);
    48   });
    49 }
    50 
    51 const rectangles = [new Rectangle(), new Rectangle(), new Square()];
    52 renderLargeRectangles(rectangles);
    53 
    54 Good:
    55 //Rectangle和Square都继承了Shape,各自有自己的getArea方法互不影响
    56 class Shape {
    57   setColor(color) {
    58     // ...
    59   }
    60 
    61   render(area) {
    62     // ...
    63   }
    64 }
    65 
    66 class Rectangle extends Shape {
    67   constructor(width, height) {
    68     super();
    69     this.width = width;
    70     this.height = height;
    71   }
    72 
    73   getArea() {
    74     return this.width * this.height;
    75   }
    76 }
    77 
    78 class Square extends Shape {
    79   constructor(length) {
    80     super();
    81     this.length = length;
    82   }
    83 
    84   getArea() {
    85     return this.length * this.length;
    86   }
    87 }
    88 
    89 function renderLargeShapes(shapes) {
    90   shapes.forEach((shape) => {
    91     const area = shape.getArea();
    92     shape.render(area);
    93   });
    94 }
    95 
    96 const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
    97 renderLargeShapes(shapes);

    四、接口分离原则(ISP)

    JS没有接口所以,这个原则用起来不像其他原则一样严格。但是,对于js这种缺少类型的语言仍然很重要。

    ISP原则指出:“客户端不应该强制依赖他们用不到的接口”。因为JS是弱类型语言,接口对它来说是模糊的。

    在JS中,类需要大量的配置对象可以很好的说明这个原则。不需要客户端设置大量的选项是有好处的,因为很多时候,他们不需要所有的配置。让他们可选,有助于避免拥有一个很大的接口。

    Bad:
    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.
      // ...
    });
    
    
    Good:
    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)

    这个原则说明了两件重要的事情:

    1、高级模块不应依赖于低级模块,但两者都需要依赖于抽象。

    2、抽象不应该依赖于具体实现。具体实现应该依赖于抽象。

    起初,这个很难理解,但是如果你用过AngularJs,你已经通过依赖注入看到过这个原则。虽然他们不是同一个概念,依赖反转原则让高级模块原理低级模块及他们的配置。耦合是一个很差的开发模式,因为它使得代码难以重构。

    如上所述,JS没有接口,所以抽象依赖于隐式契约。这说明,一个对象的方法和类直接暴露给其他方法和类。在下面的例子中,隐式契约就是InventoryTracker 的任何Request模块将会有一个requestItems方法。

    Bad:
    class InventoryRequester {
      constructor() {
        this.REQ_METHODS = ['HTTP'];
      }
    
      requestItem(item) {
        // ...
      }
    }
    
    class InventoryTracker {
      constructor(items) {
        this.items = items;
    
    //我们创建了一个具体请求实现的依赖。我们应该只有requestItem依赖request方法
        this.requester = new InventoryRequester();
      }
    
      requestItems() {
        this.items.forEach((item) => {
          this.requester.requestItem(item);
        });
      }
    }
    
    const inventoryTracker = new InventoryTracker(['apples', 'bananas']);
    inventoryTracker.requestItems();
    
    
    Good:
    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) {
        // ...
      }
    }
    
    //通过外部创建依赖并注入,我们可以轻松地用一个新的websockets,替换我们的请求模块
    const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2());
    inventoryTracker.requestItems();
  • 相关阅读:
    request和request.form和request.querystring的区别
    VS2010中如何创建一个WCF
    ASP.Net MVC 3.0 之 MVCContrib的使用
    C# 请假小时数的计算(完整代码)
    C#调用WebService
    .Net Framework 框架工作原理
    做程序员的感悟
    WCF入门简单教程(图文) VS2010版
    仿淘宝的浮动工具栏(兼容IE6和其他浏览器)
    (原创)LINQ To SQL简单入门
  • 原文地址:https://www.cnblogs.com/xxchi/p/7241951.html
Copyright © 2011-2022 走看看