1 设计模式的目的
- 代码重用性,不写重复代码
- 可读性,其他程序员也能读懂
- 可扩展性,增加功能时非常方便,易维护
- 可靠性,增加新功能对原功能没影响
- 使程序具备【高内聚低耦合】的特性
2 设计模式的七大原则
- 单一职责原则
- 接口隔离原则
- 依赖倒置原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成复用原则
2.1 单一职责原则
对类来说,一个类只负责一项职责
2.2 接口隔离原则
客户端不应该依赖他不需要的接口, 即一个类对另一个类的依赖应该建立在最小的接口上,用不到的方法进行隔离
2.3 依赖倒置原则
- 高层模块不应该依赖低层模块,二者都应该依赖于抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒置的中心思想是面向接口编程
- 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间就存在一个缓冲层,利于程序扩展和优化
2.4 里氏替换原则
- 使用继承时,在子类中尽量不要重写父类的方法
- 所有引用基类的地方必须能透明的使用其子类的对象
- 继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合、组合、依赖来解决问题
2.5 开闭原则
- 一个软件实体如类、模块、函数应该对扩展开放、对修改关闭,用抽象构建框架,用实现扩展细节
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
2.6 迪米特法则
- 一个对象应该对其他对象保持最少的了解
- 又叫最少知道原则,即一个类对自己依赖的类知道的越少越好,也就是说,对于被依赖的类不管都么复杂,都尽量将逻辑封装在类的内部,对外除了提供的public方法,不对外泄露任何信息(只和直接的朋友通信)
2.7 合成复用原则
- 尽量使用合成/聚合的方式,而不是使用继承
3. 设计模式分类
3.1 创建型设计模式
3.1.1 单例模式
描述
采取一定的方法保证在整个软件系统中,对某个类智能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
使用场景
需要频繁的创建和销毁的对象、创建对象时耗时过多或耗费资源过多,但又经常用到的对象,工具类对象、频繁访问数据库或文件的对象(数据源、session工厂)
单例模式的写法
-
饿汉式(静态变量)
-
构造器私有化(防止new)
-
类的内部创建对象
-
向外暴露一个静态的公共方法
class Singleton { private Singleton{} private final static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } } -
优点:类装载就完成实例化,避免了线程安全问题
-
缺点:如果从未使用过这个实例,会造成内存浪费
-
-
饿汉式(静态代码块)
- 优点:类装载就完成实例化,避免了线程安全问题
- 缺点:如果从未使用过这个实例,会造成内存浪费
- 代码实现
class Singleton { private Singleton{} private static Singleton instance; static { instance = new Singleton(); } public static Singleton getInstance() { return instance; } } -
懒汉式(线程不安全)
- 优点: 起到了懒加载的效果,避免资源浪费,但是只能在单线程下使用
- 缺点:多线程环境下会产生多个实例,无法保证完全单例
- 代码实现
class Singleton { private Singleton{} private static Singleton instance; public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } -
懒汉式(线程安全,同步方法)
-
优点: 解决了线程安全问题
-
缺点:效率太低,每个线程在想获取实例的时候都要进行同步
-
代码实现
class Singleton { private Singleton{} private static Singleton instance; public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
-
-
懒汉式(线程安全,同步代码块)
- 优点:提高了执行效率
- 缺点:不能起到线程同步的作用。多线程环境下会产生多个实例,无法保证完全单例
- 代码实现
class Singleton { private Singleton{} private static Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { instance = new Singleton(); } } return instance; } } -
懒汉式(双重检查锁)
-
优点: 实现懒加载,同时解决了线程安全问题,同时保证了效率(避免反复同步)
-
缺点:无
-
代码实现
class Singleton { private Singleton{} private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
-
-
静态内部类
- 特点
- 外部类装载时,内部类不会装载
- 静态内部类使用时才装载,且只会装载一次,不存在线程安全问题
- 优点:采用类装载机制来保证初始化实例时只有一个线程,避免了线程不安全
- 缺点:无
- 代码实现
class Singleton { private Singleton{} private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.INSTANCE; } } - 特点
-
枚举
-
特点: 借助枚举实现单例模式
-
优点: 避免多线程同步问题,而且还能防止反序列化重新创建新的对象
-
缺点:不适用于类实例私有化场景
-
代码实现
enum Singleton { INSTANCE; public void a() { } }
-
3.1.2 抽象工厂模式
3.1.2.1 简单工厂模式
描述
由一个工厂对象决定创建出哪一种产品类的实例,定义一个创建对象的类,由这个类来封装实例化对象的行为
使用场景
当我们会用到大量的创建某种、某类或某批对象时,就会使用到工厂模式
3.1.2.2 工厂方法模式
描述
定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类
使用场景
当我们需要创建满足多种特征的对象时,如北京的胡椒披萨、法国的草莓披萨,对创建对象的方法进行抽象,由不同的子类实现,将对象的实例化推迟到子类。
3.1.2.3 抽象工厂模式
描述
定义了一个接口用于创建相关或有依赖关系的对象簇,而无需指明具体的类,可以将简单工厂模式和工厂方法模式进行整合。
从设计层面看,抽象工厂模式就是对简单工厂模式的进一步抽象。
将工厂抽象为两层,抽象工厂和具体实现的工厂子类,程序员可以根据对象类型使用对应的工厂子类。
3.1.3 原型模式
描述
用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
工作原理
通过将一个原型对象传给那个要发起创建的对象,这个要发起创建的对象通过请求原型对象拷贝他们自己来实施创建,即对象.clone()
原型模式的实现
- 实现Cloneable接口
- 重写clone方法
原型模式在spring源码的应用
配置bean时指定scope为prototype即可将bean创建设置为原型模式,具体可查看ApplicationContext.getBean()的源码实现
成员变量浅拷贝的处理方式
-
浅拷贝是使用默认的clone()方法来实现。
-
对于基本数据类型,浅拷贝会直接进行值传递,也就是将该属性的值复制一份给新的对象。
-
对于引用数据类型,浅拷贝会进行引用传递,也就是只是将该成员变量的引用(内存地址)复制一份给新的对象,因为实际上两个对象的该成员变量都是指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
成员变量深拷贝的处理方式
- 对于基本数据类型,复制对象的成员变量值
- 对于引用数据类型,为其申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象,也就是说,对象进行深拷贝要对整个对象进行拷贝
- 实现方式
- 重写clone()方法实现深拷贝
- 通过对象序列化实现深拷贝(使用ObjectInputStream)
3.1.4 建造者模式
描述
建造者模式又叫生成器模式,是一种对象构建模式,他可以将复杂对象的建造过程抽象出来,使这个抽象过程的不同实现方法可以构造出不同表现的对象。
建造者模式是一步一步创建一个复杂的对象,他允许用户只通过指定复杂对象的类型和内容就可以构建他们,用户不需要知道内部·的具体构建细节,符合开闭原则。
建造者模式的角色
- Product(产品角色):一个具体的产品对象
- Builder(抽象建造者):创建一个product对象的各个部件指定的接口/抽象类
- ConcreateBuilder(具体建造者):实现接口,构建和装配各个部件
- Director(指挥者):构建一个使用Builder接口的对象
StringBuilder中建造者角色分析
- Builder(抽象建造者):Appendable接口定义了多个append方法(抽象方法)
- ConcreateBuilder(具体建造者):AbstractStringBuilder实现了Appendable接口方法;StringBuilder也充当了具体建造者,因为继承了AbstractStringBuilder
- Director(指挥者):StringBuilder也是指挥者
使用场景
要求按照指定的蓝图建造产品,他的主要目的是通过组装零配件而产生一个新产品。
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式
3.1.5 工厂模式
工厂模式的意义
将实例化的代码提取出来,放到一个类中统一管理和维护,达到依赖关系的解耦,从而提高项目的扩展和可维护性
工厂模式设计原则
- 创建对象实例时,不要直接new类,而是把这个new类的动作放在一个工厂的方法中,并返回,变量不要直接持有具体类的引用
- 不要让类继承具体类,而是继承抽象类或者是实现接口
- 不要覆盖基类中已经实现的方法
3.2 结构型设计模式
3.2.1 适配器模式
描述
适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作,其别名为包装器。
适配器模式属于结构性模式
分类
- 类适配器模式
- 对象适配器模式
- 接口适配器模式
工作原理
- 将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容
- 从用户的监督看不到被适配者,是解耦的
- 用户调用适配器转换出来的目标接口方法,适配器在调用被适配者的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口交互
3.2.1.1 类适配器
要点
- Java是单继承机制,所以类适配器需要继承源类这一点算是一个缺点,因为这要求目标类必须是接口,有一定局限性
- 源类的方法在Adapter中都会暴露出来,也增加了使用的成本
- 由于其继承了源类,所以他可以根据需求重写源类的方法,使得adapter的灵活性增强了
3.2.1.2 对象适配器
介绍
- 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承源类,而是持有源类的实例,以解决兼容性的问题,即:持有源类,实现目标类接口,完成src->dist的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系
- 对象适配器模式是适配器模式常用的一种
3.2.1.3 接口适配器
介绍
- 当不需要全部实现接口提供的方法时, 可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
- 适用于一个接口不想使用其所有的方法的情况
- 根据“接口隔离原则”, 非必要不实现无关方法
三种适配器的区别
三种命名方式,是根据src是以怎样的形式给到adapter来命名的
- 类适配器:以类给到,在adapter里,就是将src当做类,继承
- 对象适配器:以对象给到,在adapter里,就是将src作为一个对象,持有
- 接口适配器:以接口给到,在adapter里,就是将src作为一个接口,实现
3.2.1.4 springmvc适配器源码分析
-
SpringMVC中的HandlerAdapter,就使用到了适配器模式
-
SpringMVC处理流程
-
通过HandlerMapping来映射Controller
MappedHandler mappedHandler = getHandler(processRequest); -
获取适配器,遍历HandlerAdapters,执行
ha.supports(handler),为true则返回。HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); -
通过适配器调用Controller的方法返回ModelAndView
mv = ha.handle(processRequest, response, mappedHandler.getHandler());
-
-
使用HandlerAdapter的原因分析
可以看到处理器的类型不同,由多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断使用if else来判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样就违背了开闭原则
3.2.2 桥接模式
描述
- 将实现与抽象放在两个不同的层次中,使两个层次可以独立改变。
- 是一种结构型设计模式
- 桥接模式基于类的单一职责原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。他的主要特点是把抽象与行为实现分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。
原理类图
桥接模式在JDBC源码的应用
- JDBC的Driver接口,如果从桥接模式看,Driver就是一个接口,下面可以有MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类