设计模式(一)——七大原则

设计模式(一)——七大原则

内容来自 《JavaScript设计模式》张容铭 著 (2015年)、《大话设计模式》程杰 著、“Java设计模式” C语言中文网
文章首发于 掘金
作者:MiyueFE
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


序:

设计模式(Design Patterns),指软件/程序开发过程中被经常使用的一种代码逻辑设计经验的集合,目的是为了提高代码的安全性、可靠性、可读性、可维护性、可拓展性。

使用设计模式来进行开发,就是为了降低代码的耦合度,增加代码复用的可能性。


0. 前置内容

在学习设计模式之前,我们先要了解一下内容:

  1. JavaScript 对象、函数和类(Class: ES6新增)
  2. 抽象类:基础类,目的是为它的子类定义公共方法,通常不对抽象类直接实例化,而是通过抽象类派生出来具体类型
  3. 组合优先于继承

设计模式遵循的七大原则:

  1. 单一职责原则:就一个类而言,应该只有一个引起它变化的原因。
  2. 开闭原则:开放扩展、关闭修改,即不能修改原来的代码
  3. 里氏代换原则:超类(基础类)拥有的属性和方法在派生类的实例中也能找到,在可以使用超类(基础类)的地方也一定可以使用派生类,反过来则不成立
  4. 依赖倒置原则:高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象
  5. 接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上
  6. 最少知道原则:个软件实体应当尽可能少地与其他实体发生相互作用。
  7. 合成复用原则:尽量使用对象组合/,而不是继承关系达到软件复用的目的

设计模式目前常用的(或者说见得最多的)有23个,每个设计模式都有不同的用途。按照它们的用途大致分类,可以分为3种:

  1. 创建型 Creational Pattern:与对象创建相关

    • 简单工厂模式 Simple Factory (非23个标准模式)
    • 工厂方法模式 Factory Method
    • 抽象工厂模式 Abstract Factory
    • 建造者模式 Builder
    • 原型模式 Prototype
    • 单例模式 Singleton
  2. 结构型 Structural Pattern:处理类或者对象的"组合"

    • 适配器模式 Adapter
    • 桥接模式 Bridge
    • 组合模式 Composite
    • 装饰模式 Decorator
    • 外观模式 Facade
    • 享元模式 Flyweight
    • 代理模式 Proxy
  3. 行为型 Behavioral Pattern:描述类或者对象的交互方式和职责分配

    • 职责链模式 Chain of Responsibility
    • 命令模式 Command
    • 解释器模式 Interpreter
    • 迭代器模式 Iterator
    • 中介者模式 Mediator
    • 备忘录模式 Memento
    • 观察者模式 Observer
    • 状态模式 State
    • 策略模式 Strategy
    • 模板方法模式 Template Method
    • 访问者模式 Visitor

设计模式按照作用范围可以为分为两种,作用于类的类模式、作用于对象(实例)的对象模式

  1. 类模式
    • 工厂方法模式 Factory Method
    • 适配器模式 Adapter
    • 解释器模式 Interpreter
    • 模板方法模式 Template Method
  2. 对象模式(包含剩下的19种)

“类模式” 主要集中处理类与子类之间的关系,这些关系通过继承来建立,再确定后便不再更改,是静态的。

“对象模式” 主要处理各对象之间的关系,可以变化,更具有动态性

1. 七大原则解读

1.1 单一职责原则 Single Responsibility Principle

定义:一个类或者模块应该有且只有一个改变的原因。

目的是为极端的降低代码的耦合性和复杂性。

遵守单一职责原则,可以是代码逻辑和功能变得更加明确,并且更加易于扩展与维护,遇到错误在排查的时候也会更加方便;但是这样会严重增加代码量。

example:

class ShoppingCart {
     
    constructor() {
     
        this.goods = []
    }
	addGood(good) {
     
        this.goods.push(good)
    }
    deleteGood(goodIndex) {
     
        if(goodIndex < 0) {
     
           throw new Error("The serial number of this product cannot be less than 0")
        }
        if(goodIndex > this.goods.length - 1) {
     
           throw new Error("The serial number of this product cannot be greater than the total number of all products")
        }
        this.goods.splice(goodIndex, 1);
    }
    getGoods() {
     
        return this.goods;
    }
}

比如这个购物车 ShoppingCart 类,只针对本身做增删查的功能,而不会影响到其他类。

1.2 开闭原则 Open-Closed Principle

定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。模块应尽量在不修改“原代码”的情况下进行扩展。

开闭原则的目的是为了不改变程序原有设计和代码,在原来的基础上对其进行扩展,并保证原有代码的稳定性和正确性。

要实现开闭原则,需要在程序设计开始时就对程序进行抽象化设计。在抽象化模块设计完成之后,不允许修改接口或者抽象类的属性、方法;方法的参数类型、引用对象也必须是接口或者抽象类,尽量保证抽象层的稳定性;在进行扩展时必须定义具体实现的方法。

example:

class DrawChart {
     
    constructor() {
     }
    draw() {
     }
}

class DrawBar extends DrawChart {
     
    draw() {
     
        // 重写基础类的 draw 方法
        // ...
    }
}

1.3 里氏代换原则 Liskov Substitution Principle

定义:所有引用基类的地方必须能透明地使用其子类的对象,也可以简单理解为任何基类可以出现的地方,子类一定可以出现。

超类(基础类)拥有的属性和方法在派生类的实例中也能找到,在可以使用超类(基础类)的地方也一定可以使用派生类,反过来则不成立。只有当派生类可以替换掉超类且程序功能不受影响时,超类才能真正的被复用。

里氏代换原则是对"开闭原则"的补充,也是继承的基础。

该原则中,超类(基础类)SuperClass 派生出一个子类 SubClass,如果一个方法可以接收一个 SuperClass 类型的实例对象 superObject 的话,那么这个方法一定也可以接收一个 SubClasss 类型的实例对象 subObject,但是反过来则不能成立。

实现里氏代换原则:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
  • 子类中可以增加自己特有的方法
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
  • 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等

– 摘自 C语言中文网-设计模式

example:

// 抽象类,通信设备
class CommunicationEquipment {
     
    call() {
     
        throw "Abstract methods cannot be called"
    }
}
// 子类,手机
class Phone extends CommunicationEquipment {
     
    // 实现父类的抽象方法
    // number 
    call(number) {
     
        console.log(number, "拨号中...")
    }
    // 提供一个非抽象方法
    isMobile() {
     
        console.log("是移动设备...")
    }
}
// 具体类,苹果手机
class IPhone extends Phone {
     
    // 可以重写父类方法,但是参数要更严格或者相等
    call(number) {
     
        console.log(number, "iphone拨号中...")
    }
    // 可以增加子类自身的方法
    noCharger() {
     
        console.log("没有充电器...")
    }
}

1.4 依赖倒置原则 Dependence Inversion Principle

定义:指一种特定的解耦形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象(来自维基百科)。

简单来说,不管是高层模块还是底层模块,都不应该依赖具体的实现方法,高层模块也不应该依赖于底层模块,而应该依赖于接口或者抽象类。

example:

// 抽象,车
class Vehicle {
     
    shoot(){
     
        throw "Abstract methods cannot be called";
    }
}
// 具体类,SUV
class SUV extends Vehicle {
     
    motion(){
     
        console.log("SUV in motion...");
    }
}
// 具体类,Sedan
class Sedan extends Vehicle {
     
    motion(){
     
        console.log("Sedan in motion...");
    }
}

1.5 最少知道原则 Least Knowledge Principle

定义:最少知道原则(LKP),即迪米特法则(Law of Demeter, LOD),只与直接相关的实体之间发生联系,如果两个实体(实例对象)之前不需要互相通信,那么这两个对象就不应该有任何直接联系或者相互作用。

最少知道原则的作用就在于降低类与类之间的耦合性。

example:

class Star {
     
	constructor(name) {
     
        this.name = name
    }
}

class Fans {
     
    // ...
}

class Company {
     
    // ...
}

class Agent {
     
    setStar(star) {
     
        this.star = star
    }
    setFans(fans) {
     
        this.fans = fans
    }
    setCompany(company) {
     
        this.company = company
    }
    meeting() {
     
        console.log(`${
       this.star}${
       this.fans}见面了`)
    }
    business() {
     
        console.log(`${
       this.star}${
       this.company}洽淡业务`)
    }
}

Star 类不与 FansCompany 发生直接联系,而是由 Agent 在中间进行连接。

1.6 接口隔离原则 Interface Segregation Principle

定义:客户(client)不应被迫使用对其而言无用的方法或功能。

即将提供复杂功能的单一接口,变为实现单一功能的多个接口。

这样可以降低接口的复杂程度与耦合性,使接口变得更灵活。

接口隔离原则的优点:

  1. 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
  2. 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
  3. 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
  4. 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
  5. 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。

– 摘自 C语言中文网-设计模式

example:

// 没有分离的时候
interface ClickListener {
     
    click(): void;
    dblClick(): void;
    rightClick(): void;
    longClick(): void;
}

// 分离之后
interface ClickListener {
     
    listener(): void;
}
interface DblClickListener {
     
    listener(): void;
}
interface RightClickListener {
     
    listener(): void;
}
interface LongClickListener {
     
    listener(): void;
}

1.7 合成复用原则 Composite Reuse Principle

定义:又名"组合/聚合复用原则(Composition/Aggregate Reuse Principle)",尽量使用合成/聚合,而不是通过继承达到复用的目的。

合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。

合成复用是通过将已知对象A纳入新对象B作为成员对象,B对象可以调用A对象的方法,从而实现复用。

组合:一种"强"拥有关系,拥有共同的生命周期。比如一个A对象包含B对象,那么A对象被销毁的时候B对象也一样被销毁。

聚合:一种"弱"拥有关系,但是彼此都可以单独存在。

example:

class Car {
     
    constructor(color) {
     
        this.color = new Color(color)
    }
    move() {
     
        throw "Abstract methods cannot be called"
    }
}

class Color {
     
    constructor(color) {
     
        this.color = color
    }
    getColor() {
     
        return this.color
    }
}

你可能感兴趣的:(设计模式,设计模式,javascript,typescript)