# 设计模式概述

# 设计模式分类

  • 创建型模式

    用于描述 “怎样创建对象”, 它的主要特点是 “将对象的创建与使用分离”. 有单例,原型,工厂方法,抽象工厂,建造者等 5 中创造性模式

  • 结构型模式

    用于描述如何将类或对象按某种布局组成更大的结构,有代理,适配器,桥接,装饰,外观,享元,组合等 7 中结构性模式

  • 行为型模式

    用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责,有模板方法,策略,命令,职责链,状态,观察者,中介者,迭代器,访问者,备忘录,解释器等 11 种行为模式

# 类与类之间关系的表示方式

# 关联关系

关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系。关联关系是类与类之间最常用的一种关系,分为一般关联关系,聚合关系和组合关系

关联又分为单项关联,双向关联,自关联

  1. 单向关联

    image-20221118170750539

    在 UML 类图中,单项管理用一个带箭头的实线标识。上图表示每个顾客都有一个地址,这通过让 Customer 类持有一个类型为 Address 的成员变量类实现

  2. 双向关联

    image-20221118171055776

    在 UML 类图中,双向关联用一个不带箭头的直线表示。上图中在 Customer 类中维护一个 List<Product> , 表示一个顾客可以购买多个商品;在 Product 类中维护一个 Customer 类型的成员变量表示这个产品被哪个顾客所购买

  3. 自关联

    image-20221118171254138

    自关联在 UML 图中用一个带右箭头且指向自身的线表示。上图的意思就是 Node 类包含类型为 Node 的成员变量,也就是 “自己包含自己”

# 聚合关系

聚合关系是关联关系中的一种,是强关联关系,是整体和部分之间的关系

聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在

image-20221118171703027

在 UML 类图中,聚合关系可以用带空心菱形的实现来表示,菱形指向整体

# 组合关系

组合关系表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系

在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在

image-20221118172036669

在 UML 类图中,组合关系用实心菱形的实线来表示,菱形指向整体

# 依赖关系

依赖关系是一种使用关系,他是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量,方法的参数或者对静态方法的调用来访问另一个类 (被依赖类) 中的某些方法来完成一些职责

image-20221118172239205

在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从实用类指向被依赖的类.

# 继承关系

继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系

image-20221118172442190

在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系

# 实现关系

实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作

image-20221118172706292

在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口

# 软件设计原则

在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据 6 条规则来开发程序,从而提高软件开发效率,节约软件开发成本和维护成本

# 开闭原则

对扩展开放,对修改关闭. 在程序需要进行扩展的时候,不能去修改原有的代码,而是通过继承,去重写,将父类原本方法的行为改造为我们现阶段需要的行为;或者通过实现和父类一样的接口来完成一样的效果

image-20221118174758564

# 里氏代换原则

里氏代换原则是面向对性设计的基本原则之一

里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法

如果通过重写父类方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大

image-20221120172921147

如上图,设置一个长方形类,而正方形类继承于长方形类,resize 方法中使用 while 并使 width++ 直到 width>length, 按照里氏代换原则,这里可以使用子类,也就是正方形类,但是使用正方形类会出现死循环.

所以为了不破坏里氏代换原则,对以上结构做出如下改进 :

image-20221120172843146

resize 方法参数依然为 Rectangle, 而由于 Square 实现 Quadrilateral 接口而并非继承 Rectangle, 所以不破坏里氏代换原则

# 依赖倒转原则

高层模块不应该依赖底层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合

image-20221120175337147

上图为电脑中的硬件,通过抽象类别,从而达到能自由更换品牌的能力

# 接口隔离原则

客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上

image-20221120180625467

如上图,需要创建一个安全门,该安全门具有防盗,防火,防水三个属性,我们可以将这三个属性提取成一个接口并实现。但是如果需要创建另一种安全门,只需要实现防盗属性,显然 SafetyDoor 接口就不适合了。其违背了接口隔离原则,所以我们应该进行以下修改

image-20221120180835229

如上图,可以将所有的属性单独做成接口,并使需要该属性的类依次实现不同的接口。如此遵守了接口隔离原则

# 迪米特法则

迪米特法则又叫做最少知识法则

只和你的直接朋友交谈,不跟 “陌生人” 说话

如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性

迪米特法则中的 “朋友” 是指:当前对象本身,当前对象的成员对象,当前对象所创建的对象,当前对象的方法参数等,这些对象同当前对象存在关联,聚合或组合关系,可以直接访问这些对象的方法

image-20221120210219078

# 合成复用原则

尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现

通常类的服用分为继承复用和合成复用两种

继承服用虽然有简单和易实现的优点,但它也存在以下缺点 :

  1. 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为 “白箱” 复用
  2. 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护
  3. 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化

采用组合或聚合复用时,可以将已有对象纳入新对象中,使之称为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点 :

  1. 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为 “黑箱” 复用
  2. 对象间的耦合度低。可以在类的成员位置声明抽象
  3. 复用的灵活性高。这种服用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象

image-20221120211026953

上图是继承复用的例子,可以看到,为了加入不同的颜色,需要频繁地创建子类,且只要父类 Car 中的 move 方法有改变,子类都会受到影响。或者需要添加一些车的特性,比如动力源,那么又需要创建子类,这样的话就会创建十分多的子类

image-20221120211241374

上图是聚合复用的例子,可以看到,将颜色接口与 Car 类聚合,并使各种颜色实现此接口,以实现复用的目的。如果我们需要添加一些车辆属性,可以比较方便的创建新接口,并与 Car 类聚合.