持续更新
设计模式
- 设计模式的意义
- 1. 七大原则
- 2. UML类图
- 3. 设计模式
设计模式的意义
编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,复用性,灵活性等多方面的挑战,设计模式是为了让程序(软件),具有更好的
- 代码复用性
- 可读性
- 可扩展性
- 可靠性 (当我们增加新的功能后,对原来的功能没有影响)
- 使程序呈现高内聚,低耦合的特性
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单例模式
采取一定的方法保证在整个的软件系统中,对某个类 只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
单例模式有八种实现方法:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举