Java设计模式
# 概述
软件设计模式 , 又称设计模式 , 是套被反复使用且多数人知晓的 , 代码设计经验的总结 . 是前辈们 在不断出现问题中总结出来的设计模式
必要性
- 提高 思维&编程&设计 能力
- 使程序 标准化&工程化 , 开发效率大大提升 , 从而缩短软件开发周期
- 使设计思路 重用性&可读性&可靠性&灵活性&维护性 有进阶的提升
# 分类
# 创建型模式
描述 怎样创建对象 , 主要是将 对象的创建与使用分离
该类型有以下设计模式 :
- 单例模式(Singleton Pattern)
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 原型模式(Prototype Pattern)
- 建造者模式(Builder Pattern)
# 结构型模式
描述 如何将 类/对象 按照某种布局组成的结构
该类型有以下设计模式 :
- 代理模式(Proxy Pattern)
- 适配器模式(Adapter Pattern)
- 装饰器模式(Decorator Pattern)
- 桥接模式(Bridge Pattern)
- 外观模式(Facade Pattern)
- 组合模式(Composite Pattern)
- 享元模式(Flyweight Pattern)
# 行为型模式
描述 类/对象 之间怎样相互共同完成单个对象无法单独完成的任务
该类型有以下设计模式 :
- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 备忘录模式(Memento Pattern)
- 观察者模式(Observer Pattern)
- 状态模式(State Pattern)
- 空对象模式(Null Object Pattern)
- 策略模式(Strategy Pattern)
- 模板模式(Template Pattern)
- 访问者模式(Visitor Pattern)
# UML
统一建模语言 , 用来设计软件的可视化建模语言 . 主要以图形的形式展示出信息
UML从不同角度出发 , 可分为 : 例图 , 类图 , 对象图 , 状态图 , 活动图 , 时序图 , 协作图等...
以类图进行讲述 , 更直观的了解UML图
# 类图
类图 是展示一个对象的信息 , 里面包括有 属性&方法&构造方法&方法
示例图 :
通过分割线分割出来的矩形 , 可以看出 类名和属性和方法 是分割区分的 . 还有左侧的锁头代表是否开放的意思
# 软件设计原则
在开发中 , 为了提高系统 维护性和可复用性 以及 拓展性和灵活性 , 在开发时候尽量以 6条原则进行开发 , 从而缩短开发周期 !
# 开闭原则
对拓展开放 , 对修改关闭 . 在程序需要拓展时 , 不会影响到源代码 , 实现了热插拔效果
实现一般需要通过 接口/抽象类 的形式进行拓展
示例 :
某软件 皮肤/主题 的设计
官方有默认的主题 , 不过官方提供了自定义的设置 (这意味着可以添加自己喜好的元素进去) . 这一过程 体现出了 应用的拓展性 !
仓库代码 : 01principle.openclosedprinciple (opens new window)
# 里氏代换原则
里氏代换原则 是 面向对象 的基本原则之一 , 任何基类可以出现的地方 , 子类一定可以出现
解释 : 父类有的功能 , 其子类也有 , 但子类不能更改父类原有的功能 (如果重写了就违背了 里氏代换设计原则)
示例 :
长方形和正方形 对象的应用
长方形和正反形 都继承了四边形进行实现 , 并非他们自己继承关系 . 如果是继承关系 , 那么重写的方法会导致子类引用时容易异常问题
仓库示例 : 反例 : 01principle.liskovSubstitutionPrinciple (opens new window)
示例 : 01principle.liskovSubstitutionPrinciple (opens new window)
# 依赖倒转原则
高层模块不应该依赖低层模块 , 两者应该依赖其抽象 (抽象没有依赖细节 , 但需要具体化) , 这样就提高模块间的耦合
示例代码 :
组装电脑案例
一台台式主机电脑 , 需要配件 CPU&硬盘&内存条 等... 计算机才能运行 !
仓库示例 :
反例 : 01principle.dependencyInversionPrinciple (opens new window)
示例 : 01principle.dependencyInversionPrinciple (opens new window)
# 接口隔离原则
父类有多个方法 , 子类自需要使用一个方法 , 那么子类会迫依赖其他方法 !
解决方案 : 以接口形式进行对最小个数方法抽象化 , 以便继承使用
(一个接口实现一个方法)
示例 :
安全门案例
一个品牌的安全门 , 一般有多个功能 , 如: 防盗&防水&防火等功能... , 如果我们以一个接口整合这些功能 , 那么其他品牌没有其功能也会被迫依赖其功能 , 因此 我们将这些功能 , 以最小功能个数为单位进行区分化 进行实现 (类不能多继承 , 接口可以多实现)
仓库示例 :
反例 : 01principle.interfaceSegregationPrinciple (opens new window)
示例 : 01principle.interfaceSegregationPrinciple (opens new window)
# 迪米特法则
迪米特法则 , 又称最小知识原则
如果两个对象无需直接通信 , 那么就不会发生相互调用 , 需要通过第三方进行转发调用 . 其目的是降低耦合度 , 提高模块独立性
示例 : 明星经纪人代理案例
明星投入艺术上 , 很多事情由经纪人负责处理 , 如 粉丝见面&公司合租等业务...
仓库示例 : 01principle.lawOfDemeter (opens new window)
# 合成复用原则
尽量先使用 组合/聚合 等关系关联实现 , 其次考虑继承关系实现
继承复用 优缺点 :
- 继承复用破坏了类的封装性 , 继承会暴露父类实现细节(相对子类是透明的)
- 父子类耦合度高 . 父类一旦改变其子类也会跟着改变 , 不利维护
- 限制了复用的灵活性 , 从父类继承来的实现是静态的 , 编译已经定义了 , 所以在运行是不可能发生变化
组合/聚合 优缺点 : (纳入对象 , 成为新对象的一部分 , 可以调用纳入对象的已有功能)
- 维护了类的封装性 , 引入的对象是看不到内部细节 , 相对安全
- 对象间耦合度低 . 一般引用在 类的成员属性位置上
- 复用灵活性高 , 在运行时应用 , 新对象可动态的引用与类型相同对象
仓库示例 : 01principle.syntheticReusePrinciple (opens new window)
汽车分类管理程序
汽车 可分为 汽油车&电力车 , 两种类型的车可以分为多中颜色的车因此可以看下图
继承 类图呈现 :
聚合 类图呈现 :
# 创建者模式
# 单例模式
单例模式 一个类只有一个实例 , 且该类是自行创建这个实例的一种模式(我创建我自己)
特点:
- 单例类只有一个实例对象
- 该单例对象必须由 自己进行创建
- 单例类对外提供一个访问该单例的全局访问点
优点:
- 能够保证在内存里只有一个实例 , 不会有多余的开销
- 可避免内存多重占用
- 可设置全局访问 , 实现优化和资源共享资源的访问
缺点:
- 无接口 , 扩展功能麻烦 , 除了修改源代码(违背了开发原则
- 不利于并发测试
加载类型 :
- 懒汉式
类加载时不会没有生成单例 , 只有当第一次调用
getlnstance()
方法 时去创建单例 - 饿汉式
类一旦加载就创建一个单例 , 保证在调用
getInstance()
方法 之前单例已经存在了 . 如果不使用会造成资源浪费
搭配一下 代码示例进行使用
仓库示例 : 01principle.syntheticReusePrinciple (opens new window)
代码示例 :
/** 懒汉式 (双重检查锁机制 volatile关键字&同步锁
* 该模式的特点是类加载时没有生成单例 , 只有当第一次调用 getlnstance 方法时才去创建这个单例
*/
public class LazySingleton {
// 静态 保证 所有线程中都是同步
private static volatile LazySingleton instance = null;
// 构造器 私有化
private LazySingleton() {}
// 同步锁 防止多线程共同创建可能产生一个以上的实例
public static LazySingleton getInstance() {
// 第一次判断 , 如果instance不为null , 不进入抢锁阶段 , 直接返回实际
if (instance == null) {
synchronized (LazySingleton.class) {
// 抢到锁之后再次判断是否为空
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
/** 饿汉式
* 该模式的特点是类一旦加载就创建一个单例 , 保证在调用 getInstance 方法之前单例已经存在了
*/
public class HungrySingleton {
// 构造方法私有化
private HungrySingleton(){}
private static class Singleton {
private static final HungrySingleton INSTANCE = new HungrySingleton();
}
public static HungrySingleton getInstance() {
return Singleton.INSTANCE;
}
}
以上方式以最优解决方案
# 问题
单例模式破坏情况 : (创建的对象并非一个)
- 序列化
- 反射
仓库示例 :
- 序列化破坏问题 (opens new window) & 序列化解决方案 (opens new window)
- 反射破坏问题 (opens new window) & 反射解决方案 (opens new window)
序列化破坏示例 :
package com.singleton.qaDemo;
import com.singleton.HungrySingleton;
import java.io.*;
// 序列化 破坏单例模式
public class SerializationBreaks {
// 桌面a.txt文件
private static String path = "C:\\Users\\Sans\\Desktop\\a.txt";
public static void main(String[] args) throws Exception{
// HungrySingleton类 需要序列化
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
// 序列化存储
writeObjectFile(hungrySingleton);
// 反序列化提取
HungrySingleton hungrySingleton1 = readObjectFromFile();
// 根据地址进行判断他们是否相同
System.out.println("hungrySingleton = " + hungrySingleton);
System.out.println("hungrySingleton1 = " + hungrySingleton1);
}
// 从文件中读取对象
public static HungrySingleton readObjectFromFile() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
HungrySingleton instance = (HungrySingleton) ois.readObject();
return instance;
}
// 写入对象文件
public static void writeObjectFile(HungrySingleton instance) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
//将instance对象写出到文件中
oos.writeObject(instance);
}
}
/* 结果 (地址显然就不对了 , 两个对象
hungrySingleton = com.singleton.HungrySingleton@78308db1
hungrySingleton1 = com.singleton.HungrySingleton@3d494fbf
*/
序列化 解决方案 :
在序列化类中添加 readResolve()
方法 , 在反序列化时会判断该方法是否存在 , 存在则调用其方法 , 否则 new新对象进行返回
// Singleton.INSTANCE 内部类提取出来的单例对象
Singleton.INSTANCE;private Object readResolve(){
return Singleton.INSTANCE;
}
反射破坏示例 :
// 反射 破坏单例模式
public class ReflectionDamage {
public static void main(String[] args) throws Exception {
Class aClass = LazySingleton.class;
// 获取对象构造方法
Constructor cons = aClass.getDeclaredConstructor();
// 取消访问检查 (越过权限修饰符
cons.setAccessible(true);
LazySingleton singleton = (LazySingleton) cons.newInstance();
LazySingleton singleton2 = (LazySingleton) cons.newInstance();
System.out.println("singleton = " + singleton);
System.out.println("singleton2 = " + singleton2);
}
}
反射 解决方案 :
通过一个布尔值控制构造方法创建对象 , 如果已经存在了 抛出异常即可
// 控制对象创建
private static Boolean flag = false;
private LazySingleton() {
synchronized (LazySingleton.class){
if (flag){
throw new RuntimeException("不能创建多个对象");
}
flag = true;
}
}
# 工厂模式
定义一个创建产品对象的工厂接口 , 将产品对象的实际创建工作推迟到具体子工厂类当中 . 这满足创建型模式中所要求的“创建与使用相分离”的特点
3 种实现方式:
- 简单工厂模式
- 方法工厂模式
- 抽象工厂模式
工厂了解示例 : 02creatorMode.factory.before (opens new window)
设计一个咖啡类(Coffee) , 并定义其两个子类 美式咖啡(AmericanCoffee)&拿铁咖啡(LatteCoffee) , 再设计一个咖啡店类(CoffeeStore) , 咖啡店具有点咖啡的功能
# 简单工厂模式
我们把被创建的对象称为“产品” , 把创建产品的对象称为“工厂” . 如果要创建的产品不多 , 只要一个工厂类就可以完成 , 这种模式叫“简单工厂模式”
在简单工厂模式中创建实例的方法通常为 静态方法
简单工厂结构 :
- 抽象产品 : 定义产品规范 , 描述功能特性等..
- 具体产品 : 继承/实现 抽象产品的子类
- 具体工厂 : 提供创建产品的方法 , 通过方法获取产品
优点:
- 工厂方法中可以通过明确的值来确定产品(工厂和产品的职责区分明确
- 用户获取产品对象时 , 只需 静态方法 填写参数获取即可
- 也可以引入配置文件 , 在不修改客户端代码的情况下 更换/添加 新的具体产品类
缺点:
- 简单工厂模式的工厂类单一 , 负责所有产品的创建 , 职责过重 , 一旦异常 , 整个系统将受影响
- 使用简单工厂模式会增加系统中类的个数(引入新的工厂类), 增加系统的复杂度和理解难度
- 系统扩展困难 , 一旦增加新产品不得不修改工厂逻辑 , 在产品类型较多时 , 可能造成逻辑过于复杂
- 由于工厂使用了静态方法 , 造成工厂角色无法形成基于继承的等级结构
仓库示例 : 02creatorMode.factory.simple (opens new window)
点咖啡后 , 会通过 咖啡工厂(CoffeeFactory) 进行生成咖啡 !
简单示例 :
// 抽象产品
interface Product{
void show();
}
// 具体产品1
class ConcreteProduct1 implements Product{
@Override
public void show() {
System.out.println("产品1");
}
}
// 具体产品2
class ConcreteProduct2 implements Product{
@Override
public void show() {
System.out.println("产品2");
}
}
class ProductFactory{
static final int PRODUCT_A = 0;
static final int PRODUCT_B = 1;
public static Product makeProduct(int productId){
switch (productId) {
case PRODUCT_A:
return new ConcreteProduct1();
case PRODUCT_B:
return new ConcreteProduct2();
default: break;
}
return null;
}
}
在原有的基础上通过工厂形式进行获取
# 方法工厂模式
是对 简单工厂模式的进一步抽象化 , 其好处是可以使系统在不修改原来代码的情况下引进新的产品 , 即满足 开闭原则
方法工厂结构 :
- 抽象工厂 : 提供创建产品的接口 , 通过接口进行调取具体工厂来创建产品
- 具体工厂 : 主要实现抽象方法 , 完成具体产品的创建
- 抽象产品 : 定义产品规范 , 描述功能特性等...
- 具体产品 : 实现产品接口 , 最后由其工厂来创建 , 工厂和产品一一对应
优点:
- 用户只需知道工厂名称 , 无需理解工厂创建过程
- 新产品需要创建对应的具体工厂类
缺点:
- 类的个数容易多(增加复杂度
仓库示例 : 02creatorMode.factory.method (opens new window)
以下是根据不同的咖啡设计出的不同工厂 (AmericanCoffeeFactory&LatteCoffeeFactory) , 实现的接口都有生产咖啡的方法 , 在客户点咖啡后 , 咖啡店只关心咖啡工厂即可提供客户咖啡 (要区分工厂类型) !
简单示例 :
// 抽象产品:提供产品的接口
interface Product{
void show();
}
// 抽象工厂:提供厂的生成方法
interface AbstractFactory {
Product newProduct();
}
// 具体产品1
class ConcreteProduct1 implements Product{
@Override
public void show() {
System.out.println("显示产品1");
}
}
// 具体产品2
class ConcreteProduct2 implements Product{
@Override
public void show() {
System.out.println("显示产品2");
}
}
// 具体工厂1:实现产品生成方法
class ConcreteFactory1 implements AbstractFactory {
@Override
public Product newProduct() {
System.out.println("具体工厂1-->具体产品1");
return new ConcreteProduct1();
}
}
// 具体工厂2:实现产品生成方法
class ConcreteFactory2 implements AbstractFactory {
@Override
public Product newProduct() {
System.out.println("具体工厂2-->具体产品2");
return new ConcreteProduct2();
}
}
# 抽象工厂模式
抽象工厂 可以理解为 工厂的工厂 . 提供一个 创建相关的/相互依赖的接口 , 无需指定具体类 , 就能得到该品牌(工厂)不同类型的产品(手机/电脑/电子手环 等用电器...)
可以了解为 还TCL品牌不单单只是买电视机 , 还有其他用电器..
抽象工厂结构 :
- 抽象工厂 : 提供产品接口 , 包含有多个创建 产品的方法
- 具体工厂 : 实现 抽象工厂接口的方法 进行生产产品
- 抽象产品 : 定义产品 规范&特征&功能 的抽象方法
- 具体产品 : 实现抽象产品接口 , 创建时可具体化产品信息
优点 :
- 当一个工厂中的多个对象被设计成一起工作时 , 它能保证客户端始终只使用同一品牌(工厂)的对象
缺点 :
- 当该品牌增加新产品时 , 所有相关的工厂类都需要进行修改
仓库示例 : 02creatorMode.factory.abstractFactory (opens new window)
咖啡店添加新产品甜点(Dessert) 分别有 提拉米苏(Tiramisu)&抹茶慕斯(MatchaMousse) , 工厂是根据不同地域产生的不同类型的 咖啡和甜点 , 这些工厂分别有 意大利甜点厂(IatlyDessertFactory)&美国甜点厂(AmericanDessertFactory) . 解决了 方法工厂模式 类爆炸的问题(类多)
简单示例 :
// 具体工厂:实现产品生成的方法
class ConcreteFactory1 implements AbstractFactory {
@Override
public product1 newProduct1() {
System.out.println("具体工厂1-->具体产品1");
return new product1();
}
@Override
public product2 newProduct2() {
System.out.println("具体工厂1-->具体产品2");
return new product2();
}
}
// 产品1
class product1 { }
// 产品2
class product2 { }
# 模式扩展
该扩展是 简单工厂&配置文件 的组合使用 , 主要是解决了工厂对象耦合问题
配置文件已 properties格式进行存储 , 键值对(键 : 名称 ; 值 : 全限定类名)
## 获取产品通过 K 获取 , 对象则是通过 K对应的V 获取
american=com.factory.modeConfigFactory.AmericanCoffee
latte=com.factory.modeConfigFactory.LatteCoffee
仓库代码 : 02creatorMode.factory.modeConfigFactory (opens new window)
# 原型模式
用一个已经创建的实例作为原型 , 通过复制该原型对象来创建一个和原型相同的新对象
原型模式结构 :
- 抽象原型类 : 规定具体原型对象必须实现的的
clone()
方法 - 具体原型类 : 实现抽象原型接口 重写
clone()
方法 , 返回的是复制原型类 - 访问测试类 : 使用原型的实例对象进行调用其
clone()
方法 来复制对象
克隆模式 :
- 浅克隆 : 克隆新对象的属性和原有对象相同 , 对于非基本数据类型的属性仍会指向原有对象地址
- 深克隆 : 克隆新对象的属性也会一一拷贝一份 , 且他们引用属性对象的地址和原有引用的地址不同
克隆羊"多莉"实验案例 , 其原型是非克隆羊
仓库示例 了解结构: 02creatorMode.prototype (opens new window)
仓库示例 (浅克隆&深克隆): 02creatorMode.prototype (opens new window)
PS :
- 克隆前提需要
Cloneable
接口 , 并重写clone()
方法 返回其拷贝对象- 序列化 需要 序列化对象 (实现
Serializable
接口)- JSON化 需要 jar包 (gson)
# 建造者模式
将一个复杂对象的构建与表达分离 , 使得相同的构建过程可以有不同的表示
解释 : 将复杂对象进行拆分装配 , 不同 构建器&装配顺序 都会建出 不同对象 (复杂对象拆分创建)
建造者模式结构 :
- 抽象创建者类 (Builder) : 规范化复杂对象创建方式 , 不涉及具体对象创建
- 具体创建者类 (ConcreteBuilder) : 实现 Builder接口 , 完成复杂产品具体部件创建方法
- 产品类 (Product) : 复杂对象
- 指挥者类 (Director) : 调用 Builder方法 , 分别创建复杂对象部件 , 部件创建的顺序是自定义的 , 最终将创建完整的对象进行返回
优点 :
- 构建过程复杂的对象
- 建造者封装性好
- 无需知道产品组成细节 , 产品本身和创建过程是以解耦形式创建 , 使得创建时会有不同产品对象
- 易扩展 , 如有新产品 , 只需创建新即建造者类即可 . 也满足 开闭原则
缺点 :
- 产品会有较多的共同点 , 组成部分相似 , 不适合差异较多的复杂对象使用 , 有范围局限性
仓库示例 : 02creatorMode.creator (opens new window) (PS : 抽象创建者类可以为接口
生产自行车案例
生产自行车是一个复杂的过程 , 它包含了车架(frame)&车座(seat)等组件的生产 . 车架用什么材质 , 车座又用什么材质等组件都需要细化 . 自行车(Bike) 包含有这些组件都需要细化 , MobikeBuilder和 OfoBuilder是具体的建造者 (具体的类型使用的材质) , 抽象建造者(Builder) 里面的组件即将会被 指挥者(Director) 指定的顺序组装 完成 (顺序有指挥者制定)
仓库示例2 (优化版) : 02creatorMode.creator (opens new window)
优化版 整合了指挥者类进行 , 整合后不能以接口形式呈现 , 因 接口只能又抽象方法 (需要构建部件创建顺序)
解释 : 过于复杂的对象需要指挥者创建
# 模式拓展
除了以上用法 , 在开源中也常见的一种使用方式 , 链式构建对象 , 能够更直观构造一个对象的属性 ! 且特别适合单对象的构建 !
示例 :
组装电脑案例
一台台式主机电脑 , 需要配件 CPU&硬盘&内存条 等... 计算机才能运行 !
构造器构建对象方式 : (显然可以看到可读性比较差
public static void main(String[] args) {
//构建Computer对象 (构造器属性顺序 cpu,硬盘,内存条,主板
Computer computer = new Computer("intel9400","西数硬盘500G","金士顿内存条16G","华硕主板");
System.out.println(computer);
}
建造者构建对象方式 :
public static void main(String[] args) {
Computer computer = new Computer.Builder()
.cpu("intel9400")
.hardDisk("西数硬盘500G")
.mainboard("华硕主板")
.memory("金士顿内存条16G")
.build();
System.out.println(phone);
}
仓库示例 : 02creatorMode.creator.extend (opens new window)
# 创建者模式对比
应用场景 :
- 单例模式 : 单对象重复引用情况
- 原型模式 : 多次拷贝原对象情况
- 工厂模式 : 多对象不同形态情况
- 建造者模式 : 创建复杂对象情况
创建形式 :
- 单例模式&工厂模式 : 直接new实例对象
- 原型模式 : 复制对象
- 建造者模式 : 细分创建对象部件过程
# 结构型模式
# 代理模式
为对象提供一个代理使其对象可访问 , 访问对象不能直接访问目标引用对象 , 代理对象作为中介进行交互
解释 : 打电话给同事 , 需要引用手机进行与对方交互 (手机充当代理对象) , 不能直接进行对面交互(同事不在附近)
代理模式结构 :
- 抽象目标对象 (Subject) : 通过 接口/抽象类 定义 真实目标对象 即将实现的业务方法
- 目标对象 (Real Subject) : 具体化抽象目标对象的业务方法 , 是最终要引用的真实目标对象
- 代理 (Proxy) : 提供了与真实目标对象交互的接口 , 其内部含有对真实主题的引用 , 它可 访问&控制/增强 目标对象的功能
# 静态代理
将 服务性代码 分离出来 . 通过接口进行静态代理
缺点 :
- 每次新添加业务方法 都要添加 , 维护成本高
仓库示例 : 03StructuralPattern.proxy (opens new window)
火车站卖票
早期互联网没有完善时 , 购买票是需要去火车站进行的 , 排队等一系列的操作 , 非常麻烦 . 因此 不少地方也有了 代售点 进行代理卖票 . 以上案例不难看出 , 目标对象是火车站(TrainStation) , 代售点是代理对象(ProxyPoint)
类 | 角色 |
---|---|
ProxyPoint (代理类) | 代理角色 |
TrainStation (火车站类) | 目标角色 |
# JDK动态代理
JDK动态代理 是Java中提供的代理类 . 程序运行的时候 , 根据要被代理的对象 动态生成代理类
Java提供 Proxy.newProxyInstance()
方法 获取 代理对象
/** 参数:
* @param ClassLoader loader : 代理对象的类加载器
* @param Class<?>[] interfaces : 代理对象要实现的接口列表
* @param InvocationHandler h : 代理对象的处理程序
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
InvocationHandler.invoke()
方法 (增强业务)
/** 参数:
* @param Object proxy : 代理对象
* @param Method method : 对接口的方法进行封装成的对象
* @param Object[] args : 调用方法的参数
* return 对应代理对象方法执行的返回值 (如果 void 则 null)
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
仓库示例 : 03StructuralPattern.proxy (opens new window)
以上实例的代理对象是自动生成的 , 因此我们查看需要借助 阿里巴巴开源
arthas-boot.jar
诊断工具 来查看程序运行中指定类结构
查看 自动生成的代理类 :
- 下载
arthas-boot.jar
诊断工具 (仓库同步有) - 运行程序 , 保持运行状态 (为了防止
main()
方法 执行结束 , 在末尾添加死循环) 记住控制台打印的代理类的 全限定类名 - 进入终端 (cmd) , 进入
arthas-boot.jar
路径内 , 执行以下指令 ==java -jar arthas-boot.jar== (执行加载包) - 选择刚刚运行的Java进程 (根据序号选择进入)
- 选择指定类进行查看 , 输入以下指令 ==jad 全限定类名==
PS : 该 诊断工具 需要 Java程序也是运行 jdk17版本 , 否则 无法诊断 该Java线程
代码示例 :
// 程序运行过程中自动生成的代理类 (该对象的原代码较多 , 简化去掉不必要代码)
public final class $Proxy0 extends Proxy implements SellTickets {
// SellTickets接口的sell()方法
private static final Method m3;
// 该参数赋予在 Proxy.newProxyInstance()方法 第三个参数赋予
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
m3 = Class.forName("com.proxy.jdk.SellTickets").getMethod("sell", new Class[0]);
}
// 步骤 2
public final void sell() {
// 步骤 3
this.h.invoke(this, m3, null);
}
}
// Java提供的动态代理相关类
public class Proxy implements java.io.Serializable {
// 代理对象的处理程序
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
this.h = h;
}
}
// 代理工厂
// PS: 该工厂不是代理类 , 而是程序运行过程在内存生成的类
public class ProxyFactory {
// 目标对象
private TrainStation station = new TrainStation();
// 返回单例对象
public SellTickets getProxyObject() {
return (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 步骤 4
// 增强业务
System.out.println("卖票 前置通知");
// 执行 业务方法 , 并且返回
// 步骤 5
Object obj = method.invoke(station, args);
System.out.println("卖票 后置通知");
return obj;
}
}
);
}
}
package com.proxy.jdk;
// 代理模式 jdk动态代理 测试类
public class Main {
public static void main(String[] args) {
// 获取代理工厂代理对象
ProxyFactory factory = new ProxyFactory();
// 获取代理对象 (该对象是动态生成的
SellTickets proxyObject = factory.getProxyObject();
// 卖票方法
// 步骤 1
proxyObject.sell();
System.out.println("proxyObject = " + proxyObject.getClass());
while (true) {}
}
}
执行流程解析 : (对应注释步骤走向)
- 测试类 调用代理对象
sell()
方法 - 多态特性 继承了
SellTickets
接口, 代理类 ($Proxy0) 中的sell()
方法 - 代理类 ($Proxy0) 中的
sell()
方法 中又调用了InvocationHandler
接口 实现invoke()
方法 (lambda表达式) - 代理对象业务增强部分 .
InvocationHandler.invoke()
方法 (详细可看上面方法说明) InvocationHandler.invoke()
方法中的invoke()
方法 反射调用 真实对象 (TrainStation) 中的sell()
方法
# CGLIB动态代理
CGLIB代理 , 也称子类代理 . 可以看做 JDK动态代理 的升级版
特点 :
- 目标对象无需实现接口也可对外进行代理
- 代理对象是目标对象的子类 , 因此 也继承了方法
- 通过
Enhancer
类 进行创建代理对象 (和Proxy类 类似 , 创建方式有多种) - 依赖外部引入jar包 CGLIB动态代理 (opens new window)
GLIB代理被许多 AOP框架 所使用 , 其底层是通过使用一个小而快的字节码处理框架ASM(Java 字节码操控框架)转换字节码并生成新的类
仓库示例 : 03StructuralPattern.proxy (opens new window)
# 代理模式总结
JDK动态代理 | CGLIB动态代理 | 静态代理 | |
---|---|---|---|
效率 | 较高 | 偏低于JDK | 较高 |
复杂度 | 复杂 | 复杂 | 简单 |
外部依赖 | jdk本身 | 外部jar包 | 无 |
代理方式 | 目标对象实现的接口 | 目标对象 | 目标对象 |
动态代理和静态代理 的区别 :
- 动态代理 目标对象的业务方法都会集中在一个集中的方法处理 ; 静态代理 指定方法进行手写中转处理 , 才能实现代理
- 如果目标对象添加一个方法的情况 ! 动态代理 : 无需重新重写该方法即可代理 静态代理 : 手写添加此方法进行中转处理
优点 :
- 客户端和目标对象 交互是通过代理对象进行交互 , 代理形式能够起到保护目标对象的作用
- 代理对象能够扩展目标对象的功能 , 满足 开闭原则
- 分离 客户端和目标对象 , 在一定程度上降低系统耦合度
缺点 :
- 增加系统的复杂度
使用场景 :
- 运程代理 (RPC通信 , 远程调用方法)
- 防火墙代理 (VPN代理转发)
- 保护代理 (不同用户提供不同权限)
# 适配器模式
适配器模式 是将一个类的接口转换成我们希望的另外一个接口 , 使其兼容运作
适配器模式结构 :
- 目标 (Target) : 当前系统规范的业务接口
- 适配者 (Adaptee) : 访问与现有的适配器组件库中组件接口
- 适配器 (Adapter) : 转换器 , 通过 继承/引用适配者 的对象 , 把适配者接口转换成目标接口
# 类适配器模式
定义一个适配器类来实现当前系统的业务接口 , 同时又继承现有组件库中已经存在的组件
仓库示例 : 03StructuralPattern.adapter (opens new window)
读卡器案例
现有台电脑只能读取SD卡 , 而要读取TF卡的话就需要借助适配器模式 . 创建一个读卡器 , 将TF卡中的内容读取出来
类 | 角色 |
---|---|
SDcardImpl (SC卡类) | 目标 |
TFCardImpl (TF卡) | 适配者 |
SDAdapterTF (SC适配TF类) | 适配器 |
Computer (计算机类) | 应用接口 |
核心代码 (继承已有的组件库)
// 适配器类 (SD兼容TF)
// 实现当前业务接口 SDCard ; 继承了已有组件库 TFCardImpl
public class SDAdapterTF extends TFCardImpl implements SDCard{
@Override
public String readSD() {
System.out.println("适配器 读TF卡");
return readTF();
}
@Override
public void writeSD(String msg) {
System.out.println("适配器 写TF卡");
writeTF(msg);
}
}
# 对象适配器模式
对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中 , 该类同时实现 当前系统的业务接口
仓库示例 : 03StructuralPattern.adapter (opens new window)
复用以上案例 (结构和上面的大体相同)
核心代码 (引入适配器类)
// 适配器类 (SD兼容TF)
public class SDAdapterTF implements SDCard {
// 适配者类
private TFCard tfCard;
public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}
@Override
public String readSD() {
System.out.println("适配器 读TF卡");
return tfCard.readTF();
}
@Override
public void writeSD(String msg) {
System.out.println("适配器 写TF卡");
tfCard.writeTF(msg);
}
}
PS : 接口的适配器 , 如果希望重写所有的方法 , 需要创建抽象类(Adapter) , 来重写这些方法 , 如果需要用的时候再进行具体化他们 . 这一过程需要将实现改为继承形式进行应用
# 适配器模式总结
应用场景 :
- 兼容 新老系统业务接口 的对接问题
- 使用第三方提供的组件 , 组件接口和我们要求的接口不一致 , 可通过适配器调整接口
优点 :
- 客户端通过适配器可直接调用目标接口
- 将目标类和适配者类解耦 , 解决了目标类和适配者类接口不一致的问题
- 符合 开闭原则
缺点 :
- 频繁使用会 增加复杂度&降低可读性
# 装饰者模式
指在不改变现有对象结构的情况下 , 动态给对象增加一些职责(即增加其额外功能)的模式 , 它属于对象结构型模式
装饰者模式结构 :
- 抽象构件角色(Component) : 定义一个抽象接口以规范准备接收附加责任的对象
- 具体构件角色(Concrete Component) : 实现抽象构件 , 通过装饰角色为其添加一些职责
- 抽象装饰角色(Decorator) : 继承/实现 抽象构件 , 其中包含有 具体构建的实例 , 可通过其子类扩展
- 具体装饰(ConcreteDecorator) : 实现抽象装饰的相关方法 , 并给具体构件对象添加附加的责任
仓库示例 : 03StructuralPattern.decorator (opens new window)
快餐店案例
用装饰者模式编写该案例 . 一家快餐店 有炒面(FriedRice)&炒饭(FriedNoodles)等其他快餐 , 这些餐中可以增添 鸡蛋(Egg)/培根(Bacon) 等配菜 , 这些配菜的添加也是要额外加钱
类 | 角色 |
---|---|
FastFood(快餐类) | 抽象构件角色 |
FriedNoodles/FriedRice (炒面/炒饭 类) | 具体构件角色 |
Egg/Bacon (鸡蛋/培根 类) | 具体装饰者 |
Garnish(装饰者类) | 抽象装饰角色 |
优点 :
- 比继承更加灵活的扩展功能 , 可组合装饰者获取来的结果 . 完美遵循了 开闭原则
- 修饰类和被修饰类可独立拓展 , 无耦合 . 是继承的替代模式
缺点 :
- 子类容易增多 , 系统的复杂度也随着提高
应用场景 :
- 不能采用继承对系统扩展 / 继承不理系统扩展和维护时 , 可以采用装饰模式
不能继承的情况 :
- 系统存在大量的独立扩展 , 每种组合将会产生大量子类 , 子类容易以爆炸性增长
- 定义的类不能被继承 (如: final类)
- 不影响其他对象的情况下 , 以 动态/透明 的方式给单个对象添加职责
- 当对象的功能要求可动态添加 , 也可 动态撤销时 (直接将其子类移出即可)
# 桥接模式
将抽象与实现分离 , 使它们可以独立变化 . 它是用组合关系代替继承关系来实现 , 从而降低了抽象和实现这两个可变维度的耦合度
一家公司发行了一款 新品的电饭锅 , 那么该新产品 在代码设计层面上理解 , 只需实现 设定好的规则(实现接口) , 聚合应用即可 (组合应用)
桥接模式结构 :
- 抽象化角色(Abstraction) : 抽象类 , 包含一个对象 实现化角色对象 的引用 , 对象操作的行为
- 扩展抽象化角色(Refined Abstraction) : 抽象化角色的子类 , 实现操作方法 , 并操作 实现化角色对象
- 实现化角色(Implementor) : 定义实现接口 , 提供方法 扩展抽象化角色 引用
- 具体实现化角色(Concrete Implementor) : 具体实现 实现化角色 的方法
优点 :
- 抽象与实现分离 , 扩展能力强 , 符合 开闭原则
- 实现细节对客户透明
缺点 :
- 聚合关系建立在抽象层 , 添加多个独立维度 , 会增加系统的理解与设计难度
应用场景 :
- 一个类存在两个以上的独立变化维度 (可在抽象层建立不同维度的关联关系)
- 当一个系统不希望 多层继承/类数急增 的情况
仓库示例 : 03StructuralPattern.bridging (opens new window)
视频播放器
设计个 跨平台视频播放器 , 不同操作系统(OperatingSystem) , 解析视频文件格式(VideoFile)不一样 , 在加上视频文件格式也是多样化的 , 因此我们根据这两个维度进行分析并且实现(操作系统&文件格式) , 采用桥接模式
类 | 角色 |
---|---|
VideoFile(视频文件类) | 实现化角色 |
AVIFile/RmvbFile(视频文件类) | 具体实现化角色 |
OperatingSystem(操作系统类) | 抽象化角色 |
Windows/Mac(操作系统类) | 扩展抽象化角色 |
# 外观模式
外观模式 是一种通过为多个复杂的子系统提供一个一致的接口 , 外部直接通过接口访问子系统 , 黑盒子模式(无需关心内部细节) , 降低程序应用可行度
请一位管家进行对别墅 , 别墅管理事项有 : 修理花草 , 浇水临花 , 卫生清洁等事项 . 房主只需跟管家说下即可 !
外观模式结构 :
- 外观角色(Facade) : 为多个子系统对外提供一个共同的接口
- 子系统角色(Sub System) : 实现系统的部分功能 , 客户可以通过外观角色访问它
优点 :
- 降低 子系统与客户端 之间的耦合度
- 对客户屏蔽了子系统组件 , 减少了客户处 理的对象数目 , 并使得子系统使用起来更加容易
缺点 :
- 不符合 开闭原则
应用场景 :
- 分层结构的应用 , 可简化子系统依赖关系
- 系统有多个子系统且和客户端有联系时 , 引入外观模式将他们分离 , 使得子系统独立可移植
仓库示例 : 03StructuralPattern.exterior (opens new window)
智能家居案例
随着时代的发展 , 智能家电的普及 , 操作起来也是十分的方便 . 这一过程我们只需和接收信息的终端设备 (智能音响) , 进行统一控制 , 可便一键完成操作
类 | 角色 |
---|---|
IntelligentControlLittleLove(小爱同学类) | 外观角色 |
AirCondition/Light/Tv (家用设备类) | 子系统角色 |
# 组合模式
把一组相似的对象看做一个单一的对象处理 . 该模式依据树型结构来组合对象 , 用来表示部分整个层次 .
在电脑的操作系统中 , 一般有文件系统的管理 , 文件夹和文件我们可以看做单一对象处理 , 虽然文件夹中还会包含 文件/文件夹 , 但我们可以看做一个对象组的树型结构
组合模式结构 :
- 抽象根节点(Component) : 定义系统各层级对象的共有 对象&方法 , 预先定义默认 行为&属性
- 树枝节点(Composite) : 定义树枝节点行为 , 包含有 树枝和叶子 节点 , 从而形成树型结构
- 叶子节点(Leaf) : 叶子节点对象 , 无其他节点分支 , 是系统层次遍历的最小单位
优点 :
- 清晰地定义复杂对象的层次结构 , 使用更为便捷
- 层次的节点添加 , 无需对类库进行修改 , 满足 开闭原则
缺点 :
- 设计复杂 , 层级编辑比较复杂
应用场景 :
- 引用 树型结构 的情况 (如: 文件目录展示 , 多级目录呈现等...
仓库示例 : 03StructuralPattern.combination (opens new window)
系统菜单案例
在系统菜单中 , 我们一般会看到一层一层的菜单结构 , 以树型结构进行呈现出来的信息 , 如图 :
类 | 角色 |
---|---|
MenuComponent(菜单组件类) | 抽象根节点 |
Menu(菜单类) | 树枝节点 |
MenuItem(菜单项类) | 叶子节点 |
# 享元模式
运用共享技术以最大利用率进行对象复用 . 主要通过共享以存在的对象进行缩短对象创建的数量 , 避免大量相似对象的开销 , 从而提高资源利用率
围棋&五子棋和井字棋中的黑白棋子 , 图像中的坐标点或颜色等信息... , 能把它们相同点提取出来共享 , 能节省大量利用资源
享元模式结构 :
- 抽象享元角色(Flyweight) : 所有享元类的父类/实现接口 , 规范化享元类
- 具体享元角色(Concrete Flyweight) : 抽象享元角色规定的接口 (这里可以看做结合单例模式进行设计 , 每个独享提供唯一的享元对象 , 但他们的地址相同
- 非享元角色(Unsharable Flyweight) : 是不可共享的外部状态 , 它以参数形式注入具体享元的相关方法中
- 享元工厂角色(Flyweight Factory) : 负责 创建&管理 享元角色 . 当客户请求享元对象时 , 享元工厂会检测是否存在满足条件的享元对象 , 存在则提供 , 否则创建新的享元对象 (类似单例模式)
享元模式的状态 :
- 内部状态 : 不会随着环境的改变而改变的可共享部分
- 外部状态 : 会随着改变而改变 , 是不可以共享的部分
连接池中的连接对象 , 保存在连接对象中的 用户名&密码&连接URL等信息 , 创建时就已经设好了 , 不会随环境的改变而改变 , 这些为内部状态 ; 而当每个连接要被回收利用时 , 我们需要将它标记为可用状态 , 这些为外部状态
优点 :
- 缓存共享对象 , 降低内存消耗
缺点 :
- 对象可共享 , 但不同共享的状态外部化 , 使得程序复杂性提高
仓库示例 : 03StructuralPattern.flyweight (opens new window)
俄罗斯方块案例
俄罗斯方块游戏中 , 不同类型的方块都是一个实例对象 , 按照往常操作是需要创建很多实例对象 , 因此需要应用享元模式进行实现 ! (包含有 I&J&L&O&Z&T&S 形状)
类 | 角色 |
---|---|
AbstractBox(抽象方块类) | 抽象享元角色 |
IBox/LBox/OBox(类型方块类) | 具体享元角色 |
BoxFactory(方块工厂类) | 享元工厂角色 |
# 行为型模式
# 模块模式
模块模式 定义一个操作的骨架 , 将部分步骤让其子类执行且不影响骨架的特定步骤
去银行办理业务 需要走的流程 : 取号>排队>办理具体业务>服务评价 等流程 , 这些过程当中每个人去银行都是需要走的流程 , 但 办理的业务因人而异
模板结构 :
- 抽象类(Abstract Class) : 方法构件 , 若干个抽象形式的 基本方法&基本方法
- 模板方法 : 定义骨架 , 按自定顺序调用其基本方法
- 基本方法 : 实现各步骤的方法 , 模板方法的组成部分
- 抽象方法(Abstract Method) : 抽象类声明实现其方法 , 并且由子类进行实现 (套娃)
- 具体方法(Concrete Method) : 实现抽象的具体方法
- 钩子方法(Hook Method) : 判断逻辑方法 , 返回布尔类型
- 具体实现子类(Concrete Class) : 实现抽象类中的抽象方法和钩子方法 , 顶端组成步骤
优点 :
- 封装不变部分 , 扩展可变部分(不变的 封装在父类中实现 , 可变的 通过子类实现
- 可变的 部分封装成方法是由子类实现的 , 因此可通过子类扩展功能 , 符合开闭原则
缺点 :
- 每次实现不同功能的子类 , 都会导致类的个数的增加(更为抽象 , 复杂度也就上来了
- 继承关系的缺陷 , 如果父类新添加抽象方法 , 继承的子类都要重写新添加的方法
应用场景 :
- 算法整体步骤固定 , 个别易变时 , 可通过模板方法进行抽象出来进行实现
- 父类抽象方法由子类实现 , 子类执行结果会影响父类的结果 , 导致反向控制结构 , 提高复杂度
仓库示例:04BehaviorPattern.templet (opens new window)
炒菜案例
炒菜步骤一般分别 : 倒油 -> 热油 -> 倒蔬菜 -> 倒调味 -> 翻炒 , 这些步骤已模板形式进行模拟 .
类 | 说明 |
---|---|
AbstractClass | 抽象角色 |
ConcreteClass_CaiXin(炒菜心类) | 具体实现类 |
ConcreteClass_DaoCai(炒包菜类) | 具体实现类 |
# 策略模式
该模式定义一套算法 , 将它们进行封装起来 , 算法之间可相互替换 , 这些算法不会影响到客户预期的结果
旅游出行计划 , 如果在网上找游玩攻略 , 会提供出很多推荐游玩等... (提供接口确定行为执行方式)
模式结构 :
- 抽象策略(Strategy) : 通常由 接口/抽象类 实现 . 给角色提出所有具体决策所需的接口
- 具体策略(Concrete Strategy) : 实现抽象决策类定义的接口 , 提供具体实现的 算法/行为
- 环境(Context) : 策略类的引用 , 最终客户调用的
优点 :
- 策略 算法/行为 可以自由选择
- 容易拓展 (利用了 抽象类/接口 多态特性)
- 避免多重选择语句(if else) 进行判定策略
缺点 :
- 策略类容易多 . 可通过享元模式进行压缩数量
- 策略类是透明的
应用场景 :
- 系统在多个 算法/行为 中选择一种时 , 进行通过角色策略类进行封装
- 一个类定义多种行为 , 而且出现了多个选择语句 , 可通过策略类进行替换选择语句
- 策略 算法/行为 完全独立 , 对客户 策略类实现细节隐藏
仓库示例 : 04BehaviorPattern.tactics (opens new window)
销售案例
销售策略有三种 , 为别为不同假日类型销售 旺季/淡季/平常 价格
类 | 角色 |
---|---|
Strategy(抽象类/接口) | 抽象策略 |
Strategy A/B/C (实现 抽象类/接口) | 具体策略 |
SalesMan(销售员) | 环境角色 |
# 命令模式
命令模式 是将 请求封装为一个对象 , 使请求的 职责和执行 分割出来 , 两者通过命令进行沟通 , 命令对象可进行 存储/传递/调用/增加/管理
餐厅中 一般都会有服务员 , 当客人进入餐厅 , 服务员会招待
模式结构 :
- 抽象命令(Command) : 定义命令的接口 , 声明执行的方法
- 具体命令(Concrete Command) : 实现命令接口 , 通常有 接收者 , 通过接收者的功能完成命令操作
- 接收者/实现者(Receiver) : 真正执行命令的对象 . (任何类都可成为 , 只要完成需求)
- 调用者/请求者(Invoker) : 命令对象执行请求 , 通常有 命令对象(多态形式) , 命令对象可以是集合形式 . 并且包含有命令发送执行的入口(执行方法)
优点 :
- 降低系统耦合度(操作和实现解耦)
- 命令 增删 较快 , 不会影响其他类 , 满足开闭原则
- 可实现宏命令 . 命令模式和组合模式结合实现
- 方便实现 撤销/恢复 功能
缺点 :
- 命令模式可能会导致较多的具体命令类
- 提高系统复杂度
应用场景 :
- 系统需要将请求 接收者/调用者 解耦 , 使得不能直接交互
- 系统需要在不同时间指定请求 , 将他们进行排队执行请求
- 系统需要支持命令 撤销(Undo)和恢复(Redo) 操作
仓库示例 : 04BehaviorPattern.command (opens new window)
点菜订单案例 客户点菜后 将点菜记录至订单中 , 订单会由服务员发给厨师(发请求给实现者)...
类 | 角色 |
---|---|
Command | 抽象命令 |
OrderCommand | 具体命令 |
SeniorChef(厨师类) | 接收者/实现者 |
Waitor(服务员类) | 调用者/请求者 |
Order(订单类) | 实体对象 |
# 职责链模式
职责链模式 是将 请求发送者 和 请求处理者 进行解耦 , 通信是通过记住链的下一个对象的引用而形成的一条链 . 当请求发生时 , 请求会沿着这条链进行传递 , 直到 满足对象条件/到达链的终点 为止 (可能 到达终点也可能没有得到处理)
在学校请求中 , 如果请假超过3天 , 就不是班主任能决定的事了 , 因此需要班主任的上级可许才能请假 .
模式结构 :
- 抽象处理者(Handler) : 处理请求抽象类 , 包含 请求处理方法 和 后继连接方法
- 具体处理者(Concrete Handler) : 请求处理方法的具体实现 , 判断满足条件 , 如果满足则处理 , 否则 请求转让后继连接
- 客户类(Client) : 创建请求处理链 , 并向链头的具体处理者对象提交请求 , 不用关心 处理细节/传递过程
优点 :
- 降低了 请求发送者 和 请求处理者 耦合度
- 增强了系统的可扩展性 (按需求添加请去处理类)
- 增强了 流程链 的灵活度 (链的顺序只需指定下一个即可)
- 责任分担 , 每个具体处理者 都有自己的处理工作 , 不能处理的传递给下一个 , 明确职责范围 , 符合单一原则
缺点 :
- 不能保证请求一定被接收处理 , 有可能 到达终点也可能没有得到处理
- 较长的职责链会影响系统处理 , 也会影响 代码调试
仓库示例 : 04BehaviorPattern.chainOfResponsibility (opens new window)
请假案例
公司请求条件 : 请假一天以下的假只需要小组长同意即可 ; 请假1天到3天的假还需 要部门经理同意 ; 请求3天到7天还需要总经理同意才行
类 | 角色 |
---|---|
Handler | 抽象处理者 |
GroupLeader(组长) / Manager(经理) / GeneralManager(总经理) | 具体处理者 |
Main | 客户类 |
LeaveRequest(请假条) | 实体对象 |
# 状态模式
状态模式 中 包含有很多不同的状态 , 不同的状态有不同的行为 , 这些状态会随着状态对象进行改变而改变的context对象
模式结构 :
- 环境(Context) : 也称上下文 , 定义了各状态程序的对象 , 也维护了状态对象且包含有当前状态的处理
- 抽象状态(State) : 定义 接口/抽象类 , 里面包含有 状态的所有行为 , 环境对象以及状态的变化方法
- 具体状态(Concrete State) : 实现抽象状态所对应的行为
优点 :
- 封装转化状态安全
- 所有状态会封装到一个类中 , 可方便添加新状态 , 且只改变对象状态即可改变行为
- 状态转换逻辑与状态对象合为一体 , 并非较大的条件语句
缺点 :
- 类数量容易多
- 模式的结构和实现较为复杂
- 对 开闭原则 的支持不友好
应用场景 :
- 对象行为取决于状态时 , 并且是在运行时根据状态改变的行为的情况
- 庞大分支结构
仓库示例 : 04BehaviorPattern.state (opens new window)
电梯案例
电梯在一般情况下包含有 开门/关门/运行/停止 状态 , 并且每个状态都有自己独有的执行行为
类 | 角色 |
---|---|
Context | 环境 |
LiftState | 抽象状态 |
ClosingState(关闭) / OpenningState(打开) / RunningState(运行) / StoppingState(停止) | 具体状态 |
# 观察者模式
观察者模式 定义了一种一对多关系的依赖关系 , 让多个观察者对象同时监听某一个主题对象 , 主题一旦发生变化会通知所有观察者对象
这一模式类似于 微信公众号 , 它有 关注-推送 功能 , 只要关注某一公众号后 , 今后推送的内容都会 收到响应推送消息
模式结构 :
- 抽象主题(Subject) : 主题将所有观察者对象以集合形式保存 , 接口提供了 增/删 观察者 和 推送 功能
- 具体主题(ConcreteSubject) : 实现抽象主题功能 , 对集合中的观察者对象 增/删 的功能 , 和推送关注集合内的观察者对象
- 抽象观察者(Observer) : 定义了更新接口 , 会随着主题通知时更新自己
- 具体观察者(ConcreteObserver) : 实现更新功能 , 以便更新自己状态
优点 :
- 降低 主题 - 观察者 耦合关系 , 两者之间有耦合关系
- 实现广播机制 , 一对多推送通知
缺点 :
- 观察者较多 , 那么发送较为耗时
应用场景 :
- 对象之间存在一对多关系 , 那么一个对象会影响其他对象的改变的情况下
仓库示例 : 04BehaviorPattern.observer (opens new window)
微信公众号案例
当你关注某一公众号后 , 该公众号推送消息给关注公众号的微信用户端 . (DDDD
类 | 角色 |
---|---|
Subject | 抽象主题 |
SubscriptionSubject(公众号) | 具体主题 |
Observer | 抽象观察者 |
WeixinUser(微信用户) | 具体观察者 |
# 中介者模式
定义了一个 中介者角色 进行封装与其他对象之间的交互 , 使原有对象耦合松散 , 且可以独立改变他们之间交互
一个公司中 , 同事与同事之间的信息交互 , 可通过 钉钉 直接联系到对方 , 那么这个 钉钉 就可看做为中介对象
模式结构 :
- 抽象中介者(Mediator) : 提供同事对象通信的抽象方法
- 具体中介者(ConcreteMediator) : 实现通信方法 , 定义同事集合 , 且可添加通信(同事角色的依赖
- 抽象同事类(Colleague) : 定义有 中介对象 , 同事名称 , 以及 通信所用的方法
- 具体同事类(Concrete Colleague) : 实现接口对象 , 通信需要通过中介进行交互
优点 :
- 对象之间耦合松散
- 集中控制交互 , 交互只需通过中介即可
- 符合 迪米特原则
缺点 :
- 中介者类容易庞大 , 难以维护
应用场景 :
- 系统存在多个对象之间的通信 , 且结构难以维护的情况
仓库示例 : 04BehaviorPattern.mediator (opens new window)
中介租房案例
出租房 一般情况是通过中介进行介绍来 , 和客户进行沟通购买的 , 因此中介的作用 能联系到房主
类 | 角色 |
---|---|
Mediator | 抽象中介者 |
MediatorStructure(通信中介) | 具体中介者 |
Person | 抽象同事类 |
Tenant(租房者) / HouseOwner(房主) | 具体同事类 |
# 迭代器模式
提供一个对象 , 按顺序访问集合的对象 , 无需知道底层执行方式来实现
模式结构 :
- 抽象聚合(Aggregate) : 定义 增加/删除 聚合元素以及创建迭代器接口
- 具体聚合(ConcreteAggregate) : 实现 对象的存储 , 以及 抽象聚合方法 的实例
- 抽象迭代器(Iterator) : 定义 可访问和遍历元素的接口
- 具体迭代器(Concretelterator) : 实现 对象的存储 , 以及 抽象迭代器方法 的实例
优点 :
- 可通过自定义迭代器改变迭代算法
- 扩展性高 , 可在原有代码进行增强 , 满足 开闭原则
缺点 :
- 增加类的个数 , 也会增加系统的复杂度性
应用场景 :
- 需要多种遍历方式时
- 需要不同聚合结构提供统一的接口时
- 访问聚合对象的内容无需暴露细节时
仓库示例 : 04BehaviorPattern.iterator (opens new window)
模拟对学生对象迭代器存储实现功能
类 | 角色 |
---|---|
StudentAggregate(学生聚合) | 抽象聚合 |
StudentAggregateImpl | 具体聚合 |
StudentIterator(学生迭代器) | 抽象迭代器 |
StudentIteratorImpl | 具体迭代器 |
Student(学生) | 实体对象 |
# 访问者模式
作用于某些数据结构对各个元素操作 , 且可不改变结构的前提作用这些元素对象的操作
模式结构 :
- 抽象访问者(Visitor) : 定义 固定访问的行为 , 在创建的时候就确定好需要访问的具体元素对象
- 具体访问者(ConcreteVisitor) : 实现具体访问的行为
- 抽象元素(Element) : 定义 接受访问方法 , 指定每个元素都有访问的方法
- 具体元素(ConcreteElement) : 提供 接受方法的具体实现 , 接受后可使用访问者的方法
- 对象结构(Object Structure) : 定义 具体元素的集合 和 添加具体元素的方法 , 分别将他们理解为容器进行提供访问者进行访问
优点 :
- 扩展性好 , 不修改结构进行添加新功能
- 复用性好
- 满足 单一原则 , 行为和访问分离
缺点 :
- 具体元素的细节是公开的 , 违背 迪米特原则
- 对象结构变化困难 , 添加新元素 , 所有的具体元素都需要添加具体操作 , 违背 开闭原则
应用场景 :
- 对象结构相对稳定的情况
- 对象结构中的对象需要多种不同不相关的操作的情况
仓库示例 : 04BehaviorPattern.visitor (opens new window)
宠物店喂宠物案例
宠物店多种宠物(具体元素) , 在开店的时候会有不同的客人(访问者)进来 参观访问这些宠物
类 | 角色 |
---|---|
Person(人) | 抽象访问者 |
Owner / Someone | 具体访问者 |
Animal(宠物) | 抽象元素 |
Cat / Dog | 具体元素 |
Home | 对象结构 |
# 备忘录模式
该模式提供了一套 状态恢复机制 , 使得方便返回到特定的历史步骤 , 如果出现问题 , 可返回之前的状态进行操作
模式结构 :
- 发起人(Originator) : 提供 记录当前状态/备份 等其他拓展实现
- 备忘录(Memento) : 负责 存储发起人状态 , 在需要的时候进行恢复状态
- 管理者(Caretaker) : 对备忘录进行管理 , 提供 保存/获取 备忘录功能 , 不能对其内容进行 访问/修改
宽窄接口
窄接口 : 只能获取备忘录对象 , 不能对备忘录里面的数据进行 访问/修改 . 除了 发起人 , 其他访问的都是 窄接口
宽接口 : 与窄接口相反 , 所有人均可访问都是 窄接口
优点 :
- 提供恢复状态机制 , 可恢复某个历史的状态
- 实现内部状态的封装 , 黑盒备忘录
- 简化发起人 , 并由管理者进行管理 , 符合 单一原则
缺点 :
- 资源消耗大
应用场景 :
- 需要 保存/恢复 的场景
- 需要 提供可回滚的场景
仓库示例 : 04BehaviorPattern.memento (opens new window)
游戏战斗案例
模拟游戏战斗存档场景 , 游戏存档状态作为数据 , 在战斗前和战斗后 状态都是不一样的 , 因此我们可以根据这些状态进行恢复存档之类的
白盒备忘录
备忘录角色 对所有需要对象 提供 宽接口
类 | 角色 |
---|---|
GameRole(游戏角色) | 发起人 |
RoleStateMemento(角色状态 备忘) | 备忘录 |
RoleStateCaretaker(角色状态 管理) | 管理者 |
黑盒备忘录
备忘录角色 对发起人对象 提供 宽接口 , 而其他对象提供窄接口
将 RoleStateMemento管理员 设为私有内部类 , 将其 实现一个 空Memento接口 作为标识应用 , 这样就不会暴露内部操作
类 | 角色 |
---|---|
GameRole | 发起人 |
Memento(窄接口) | 管理者 |
RoleStateCaretaker | 备忘录 |
# 解释器模式
该模式 实现了一个表达式接口 , 接口解释一个特定的上下文 . 例如 : 日常使用的计算器/SQL语句的解析/...
按照特定的规则去抽象化定义
模式结构 :
- 抽象表达式(Abstract Expression) : 定义解释器接口 , 约定解释器的解释操作 , 主要 interpret()方法
- 终结表达式(Terminal Expression) : 实现抽象表达式 , 解释 interpret()方法 解决运算符相关的操作 , 有结果
- 非终结表达式(Nonterminal Expression) : 实现抽象表达式 , 解释 interpret()方法 解决运算符相关的操作 , 无结果
- 环境(Context) : 定义 存储/添加/获取 数据的功能 , 这些数据是等待进行解释的 , 一般情况是公开的
优点 :
- 灵活 , 扩展高
- 添加新的表达式规则简单
缺点 :
- 应用场景少
- 结构难以维护
- 执行效率低(大量递归)
应用场景 :
- 规则简单 , 执行效率不是问题的情况
- 问题重复出现且用的是简单语言进行表示时
- 语言句子中以树的形式表示的时候
仓库示例 : 04BehaviorPattern.interpreter (opens new window)
类 | 角色 |
---|---|
AbstractExpression | 抽象表达式 |
Variable(变量运算符) | 终结表达式 |
Minus(减法运算符) / Plus(加法运算符) | 非终结表达式 |
Context | 环境 |
# 自定义Spring
# Ioc 控制反转
仓库代码 : 链接 (opens new window)
**涉及设计模式 : **
- 工厂模式 : BeanFactory工厂
- 单例模式 : 每个Bean对象都是单例
- 模板模式 : AbstractApplicationContext类的refresh() 方法 顺序固有了
- 迭代器模式 : MutablePropertyValues类管理propertyValueList集合 , 使用了迭代器模式