设计模式
设计模式
设计模式概述
设计模式:是前辈们对软件设计所遇到的问题和解决方案等经验的总结。是一些套路和模板。本质是面向对象原则设计原则的实际应用。
UML中的类图:用于描述类的内部构成和类之间的关系,系统分析和设计阶段的重要产物,是系统编码和测试的重要模式。
- 类的表示方法如下:

- 类和类之间的表示关系:
- 关联关系:一个类中包含了某个类,说明这两个类有关联,从包含类指向被包含类,实线实心三角箭头表示。
- 聚合关系:也是一种关联关系,表示一种类由另一种类聚合而成,由“大类”指向“小类”,比如学校指向老师,这里如果学校不存在了,但是老师依旧存在,使用实线空心菱形箭头表示。
- 组合关系:更强的一种关联关系,表示一种类由另一种类组合而成,由“大类”指向“小类”,比如头指向嘴,如果头不存在了嘴就一定也不存在了,使用实线实心菱形箭头表示。
- 依赖关系:表示某个类中某个方法依赖某个类,比如Driver的drive(Car car),需要依赖Car类,由Driver指向Car,使用虚线箭头表示
- 继承关系:比较好理解,使用实线空心三角箭头表示,由子类指向父类
- 实现关系:使用虚线空心三角箭头表示,由实现类指向接口
软件设计原则:
- 开闭原则:对扩展开放,对修改关闭。使用接口和抽象类实现比较好。
- 里氏替换原则:任何基类(父类)出现的地方,子类一定可以出现,子类可以扩展父类的功能,但是不能改变父类原有的功能,也就是说在该位置 子类能够完全替换父类(父类原有的功能没有改变)。子类继承父类时,除了添加新方法外,尽量不要重写父类的方法.
- 依赖倒置原则:高层模块不依赖于低层模块,高层模块和低层模块都依赖于抽象;抽象不依赖于细节,细节应该依赖于抽象。
- 接口隔离原则:客户端不应该被迫依赖于其不使用的方法(比如A类有方法1和方法2,B类继承了A类,但是B类只使用方法1,不需要方法2,则违背了接口隔离原则)
- 迪米特法则(最少知识原则):如果两个实体无须直接通信,那么不应当发生直接的相互调用,可以通过第三方转发该调用。
- 合成复用原则:优先考虑使用聚合或者组合等关联关系来实现,其次才考虑使用继承关系来实现(如果是继承复用会产生很多类,如果用聚合复用则不会产生多个子类)
- (单一职责原则?)
创建者模式
用于描述“怎么创建对象”,它的主要特点是“将对象的创建和使用分离”。五种:单例模式、原型模式、工厂方法模式、抽象工厂模式、建造者模式。
单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了唯一的对象访问方式,可以直接访问,不需要实例化该类对象。
单例的两种类
- 单例类:只能创建一个实例的类
- 访问类: 使用单例类
单例模式的两种实现
- 饿汉式:类加载时就会导致该实例对象被创建
实现方式:静态变量的方式、静态代码块方式、枚举(枚举最简单是线程安全的且只会装载一次,JVM底层实现不会被破坏)
因为类加载时就会创建对象,所以不管你用不用该对象,都会存在于内存中,导致内存浪费。 - 懒汉式:类加载不会导致该实例对象被创建,而是首次使用该对象时创建。
实现方式:双重检查锁,静态内部类。
在getInstance方法中实例化对象,这样就有一个问题,在写操作时如果有大量请求,不加锁可能会导致实例多个对象,可以给方法加上synchronized关键字。但是实际上大部分操作是读操作,给方法加锁会导致性能低下,那么我们可以优化为双重检查锁。双重检查锁要使用volatile关键字,防止JVM指令重排出现空指针。
存在的问题 - 破坏单例模式(除了枚举):
- 序列化反序列化
使用输入流读取并序列化多个对象,这些对象不是同一个
解决方法:在单例类中写readResolve()方法,反序列化时会自动调用该方法,将该方法的返回值直接返回 - 反射
创建Class和Constructor对象,通过newInstance()方法反射创建的对象也不是同一个对象
构造方法中进行判断
- 序列化反序列化
工厂模式
如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们只需要和工厂打交道就可以了,和具体对象解耦了。
- 简单工厂模式 :将业务类和具体类之间加一个简单工厂类,解耦。工厂类与其他类会耦合,违背开闭原则。
- 静态工厂:将方法设置成静态的,这样就不需要创建静态工厂对象了。
- 工厂方法模式:解决了耦合问题,符合开闭原则。将工厂抽象出来,底下有具体实现的工厂依赖于其他具体类。这样的话增加一个新类时,不需要修改抽象类的方法,而是增加一个工厂类。
优点的话是符合开闭原则。缺点的话是需要频繁创建具体类,增加系统复杂度。 - 抽象工厂模式:前面的工厂方法模式只考虑生产同级别的产品,比如咖啡工厂只生产咖啡,但是现实中会有综合工厂生产多级别产品,比如苹果公司生产电脑、手机等,就可以通过抽象工厂模式解决。
抽象工厂中写多个生产方法。 - 扩展:简单工厂+配置文件:配置文件可以定义类的全限定名
jdk中:Collection为抽象工厂,ArrayList是具体工厂,Iterator为抽象产品类,ArrayList内部的Itr私有类就是具体产品类
原型模式
以一个创建的对象为原型,通过复制该原型创建一个相同对象的实例
组成:抽象原型类规定具体原型类必须实现clone()方法,具体原型类必须实现clone()方法,访问方法使用具体原型类中的clone()方法访问对象
其实就是浅拷贝和深拷贝
建造者模式
将一个复杂对象的构建和表示分离,使得对一个对象的相同构建可以有多个不同的表示。指挥者指挥构建者构建各个具体产品,指挥者指挥构建然后组装。
工厂模式是直接创建一个对象,而建造者模式则是将各个组件进行生产并组装起来。
结构型模式
用于描述如何将类和对象通过一种布局组成更大的结构。七种:代理模式、适配器模式、桥接模式、装饰模式、外观模式、享元模式、组合模式。
代理模式
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时访问对象不适合直接访问目标对象,就需要用两者之间的中介。
静态代理
将代理生成写在代码中。
JDK-动态代理
基于接口实现的动态代理。当调用动态代理的方法时,动态地生成代理对象并执行方法。
CGLib-动态代理
基于继承的动态代理。代理类是被代理类的子类。
三种代理模式的优缺点:JDK动态代理1.8之后比cglib要高;cglib继承的类的方法如果使用了final修饰那么无法增强该方法。
使用代理模式的优缺点:
- 优点:起到中介和保护目标对象的作用;可以对目标对象进行增强;降低了系统的耦合度;
- 缺点:增加了系统的复杂度
使用场景:远程代理(RPC);防火墙代理(VPN);保护代理
适配器模式
将一个类的接口转换为另一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
分为类适配器模式和对象适配器模式,前者类之间耦合度比后者高,使用相对较少。
主要有三个角色:目标接口;适配者类;适配器类;
类适配器:适配器类要继承适配者,实现目标接口
对象适配器:不需要继承适配者,符合合成复用原则,将适配者聚合至适配器类中。
应用场景:以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
使用第三方组件,但组件几口定义和自己要求的接口定义不一样。
行为型模式
用于描述类和对象之间怎样相互协作共同完成单个对象无法完成的任务,以及如何分配职责。十一种:模板方法模式、策略模式、命令模式、职责链模式、状态模式、观察者模式、中介者模式、迭代器模式、访问者模式、备忘录模式、解释器模式。
