设计模式详解

持续更新

相关代码地址

设计模式


设计模式的意义

编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,复用性,灵活性等多方面的挑战,设计模式是为了让程序(软件),具有更好的

  • 代码复用性
  • 可读性
  • 可扩展性
  • 可靠性 (当我们增加新的功能后,对原来的功能没有影响)
  • 使程序呈现高内聚,低耦合的特性

1. 七大原则

设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础

设计模式常用的七大原则有:

  • 单一职责原则
  • 接口隔离原则
  • 依赖倒转(倒置)原则
  • 里氏替换原则
  • 开闭原则
  • 迪米特法则
  • 合成复用原则

1.1 单一职责原则

描述
一个类应该只负责一项职责。
如类A负责两个不同职责:职责1,职责2。 当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为 A1,A2。

实例
假设有一个“交通工具”类,他的作用只有一个,就是“运行交通工具”,假设它只有一个run方法,打印“交通工具 xx 在地上跑”这句话。

如果我们的交通工具只是车,这个类没有问题,如果交通工具加上“飞机”、“船”,那么“交通工具 飞机 在地上跑”、“交通工具 船 在地上跑”就不符合实际。

由于交通工具有多个,因此这个类不符合“单一职责原则”。
我们可以将其改为3个类,“水上交通工具”、“空中交通工具”、“陆地交通工具”,分别对海陆空负责(单一职责)。

此外,由于这个类的功能比较单一,只有run方法,我们也可以在方法级别上实现单一职责原则,即为该类创建“水上运行”、“空中运行”、“陆地运行”方法。

注意事项与细节

  • 降低类的复杂度,一个类只负责一项职责。
  • 提高类的可读性,可维护性
  • 降低变更引起的风险
  • 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违 反单一职责原则;如果类中方法数量足够少,可以在方法级别保持单一职责原则

1.2 接口隔离原则

Interface Segregation Principle
描述
客户端不应该依赖它不需要的接 口,即一个类对另一个类的依赖应该建立在最小的接口上。

实例

A通过调用B,需要操作1、2、3,
C通过调用D,需要操作1、4、5,
但是B、D都实现了1、2、3、4、5,显然B、D都实现了多余的方法。


若要符合“接口隔离原则”,只需要让B、D实现必需的接口即可。

然而,实际中我们不一定能确定B是否真的不需要4、5方法,也不能确定D是否真的不需要2、3方法。
因此,不是说学好设计模式就万事大吉的。
实际还得多方面考虑。

1.3 依赖倒转(倒置)原则

Dependence Inversion Principle
描述

  • 高层模块不应该依赖低层模块,二者都应该依赖其抽象
  • 抽象不应该依赖细节,细节应该依赖抽象
  • 依赖倒转(倒置)的中心思想是面向接口编程
  • 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。(在java中,抽象指的是接口或抽象类,细节就是具体的实现类)
  • 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成

就是一句话,前期设计应当从最基本、最核心的抽象入手,构建接口。

实例
用户(User类)通过receive方法接收信息(Message类)。
如果用户接受的消息包括Email、QQ、WeChat…等多种方式,那么我们就需要在User类中写多个receive重载函数分别接收不同的消息类。
然而,谁也不知道以后还会有什么消息类,这就导致每次增加一个消息类,我们都得对User类进行修改。

如果Message不是类,而是一个接口,receive接收的是Message接口,就能解决问题。
只需要让各种不同的消息类实现Message接口,receive就能够接收他们;如果出现新的消息类,也只需要增加该消息类并实现Message接口即可,不需要对原有代码进行更改。

依赖关系传递的方式

  • 声明接口传递:在普通方法参数中声明接口
  • 构造方法传递:在构造方法参数中声明接口
  • setter方法传递:类中有个接口成员,该成员通过setter声明

注意事项和细节

  • 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
  • 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在 一个缓冲层,利于程序扩展和优化
  • 继承时遵循里氏替换原则

1.4 里氏替换原则

OO中的继承,产生的问题

  • 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
  • 继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承, 则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障

解决以上问题,考虑里氏替换原则。

描述
Liskov Substitution Principle

  • 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
  • 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
  • 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。.

实例
类A的fun1是减法器,类B继承了类A;但是类B不小心将fun1重写成了加法器。
假设极端情况,类A就只有fun1方法,那么类B继承类A就没有必要了,把A唯一的方法都重写了。

实际编程中常常重写父类的方法,但是整个继承体系的复用性、稳定性较差。
一般可以这么做:让原来的父类A和子类B都继承一个更通俗的基类,取消AB继承关系,AB直接采用聚合、组合、依赖的关系实现方法调用。

比如上述实例,A和B都继承一个基础类Base(为了保证一些基本方法),然后B中声明一个成员类A,此时B可以写一个方法调用A的专有方法即可。

1.5 开闭原则

Open Closed Principle
描述

  • 开闭原则是编程中最基础、最重要的设计原则
  • 一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节
  • 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
  • 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则

1.6 迪米特法则

描述

  • 一个对象应该对其他对象保持最少的了解
  • 类与类关系越密切,耦合度越大

迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public 方法,不泄露任何信息

迪米特法则还有个更简单的定义:只与直接的朋友通信

直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系, 我们就说这两个对象之间是朋友关系。
耦合的方式很多,依赖,关联,组合,聚合 等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

此外,有时候我们也会引入了“陌生的朋友”而不自知。
比如较长的调用链,调用链中可能生成了多个陌生的类,这也是不被允许的。

迪米特法则的目的在于降低类之间的耦合度。

1.7 合成复用原则

描述
尽量使用合成/聚合的方式,而不是使用继承。

如果仅仅是为了让B类使用A类的方法,就让B继承A,只是徒增耦合。
我们只需要在B中聚合一个A的对象,或者将A作为B的某个方法参数。

小结

  • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
  • 针对接口编程,而不是针对实现编程。
  • 为了交互对象之间的松耦合设计而努力

2. UML类图

UML类图用于描述系统中的类(对象)本身的组成和类(对象)之间的各种静态关系。
类之间的关系:依赖、泛化(继承)、实现、关联、聚合与组合

note:注释
dependency依赖:用到了对方,就是依赖

  • 类中用到了对方
  • 是类的成员属性
  • 是方法的返回类型
  • 是方法接收的参数类型
  • 方法中使用到
    association关联:一对一、一对多、多对多
    generalization泛化(继承):类之间的继承
    realization实现:类实现了接口
    aggregation聚合:整体和部分的关系,两者具有各自的声明周期,通常通过作为类属性声明,set方法注入。
    composite组合:不可分割的关系,两者共享生命周期(比如级联创建\删除),通常直接在声明属性处new出来。

详细拓展点击此处

3. 设计模式

设计模式分为三种类型,共23种

  • 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
  • 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、***模式。
  • 行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者 模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。

3.1单例模式

采取一定的方法保证在整个的软件系统中,对某个类 只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

单例模式有八种实现方法:

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(线程安全,同步方法)
  • 懒汉式(线程安全,同步代码块)
  • 双重检查
  • 静态内部类
  • 枚举

3.11饿汉式(静态常量)

3.12 饿汉式(静态代码块)

3.13 懒汉式(线程不安全)

3.14 懒汉式(线程安全,同步方法)

3.15 懒汉式(线程安全,同步代码块)

3.16 双重检查

3.17 静态内部类

3.18 枚举

3.2抽象工厂模式

3.3原型模式

3.4建造者模式

3.5工厂模式

3.6适配器模式

3.7桥接模式

3.8装饰模式

3.9组合模式

3.10外观模式

3.11享元模式

3.12***模式

3.13模版方法模式

3.14命令模式

3.15访问者模式

3.16迭代器模式

3.17观察者模式

3.18中介者模式

3.19备忘录模式

3.20解释器模式(Interpreter模式)

3.21状态模式

3.22策略模式

3.23职责链模式(责任链模式)

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务