架构师-GOF设计模式
GOF,Gang of Four 设计大师四人组,90年代出版了设计模式的书籍《设计模式:可复用面向对象软件的基础》
设计模式是对软件设计反复出现的各种问题提出的解决方案,如问题反复出现三次就应该重构了!主要解决可扩展性!
设计原则:
七大原则是日常开发应遵守的原则,也是设计模式研究的依据
- 单一职责,一个类只处理一种职责,一个方法也是。
- 接口隔离,类的依赖应该建立在最小的接口上,无关的设计不应该出现,可设置多个接口
- 依赖倒转,面向接口编程,高层抽象不依赖底层实现
- 里氏替换原则,子类不应该破坏父类的实现代码,而能做到无缝替换
- 开闭原则,扩展开放,修改关闭,也就是做到功能或需求修改时,只修改供方、拓展供方即可(核心,也是接口隔离的一种表现)
- 迪米特原则,最少原则暴露,高内聚,类中不应该出现陌生临时对象!
- 合成复用原则,尽量能用合成、聚合的地方就不要用继承。
1、单一原则
一个类只处理一种职责,这样易于扩展、阅读,后续修改也不会连锁反应。
- 代码量很少的情况用方法1
- 方法很少的情况下允许用方法3
2、接口隔离原则
类的依赖应该建立在最小的接口上,如果接口较多,应该拆解。
注意:下方Class1和2的形参从原来的1变成了2、3
3、依赖倒转
核心思想是面向接口编程,高层双方以抽象方式(接口)交互,底层依赖高层,制定出了规范,有了缓冲。
实现方式,高层双方接口调用,接口的传递可以用方法、构造器、set方法,三种方式。
4、里氏替换原则
对继承的规范和契约,要求子类不对父类已实现的方法进行修改破坏,如果确实需要修改,建议做新的方法!这样父类修改可以不考虑子类的影响面!
简单地说,引用基类的地方必须能透明的使用子类的对象。
5、开闭原则
编程中最基础、最重要的原则,对提供方的扩展(增加方法)开放,对使用方的修改()关闭
当软件发生变化时,通过扩展而不是修改来达到目的,也就是说仅修改供方一边的代码即可完成功能!也是从依赖倒转的另一个角度来解读。
6、迪米特原则
最少知道原则,使用方尽量对依赖类知道的越少越好,不要出现陌生对象,仅与直接朋友有关系就好!
直接朋友指类中的成员变量,方法的入参、出参。
7、合成复用原则
尽量使用依赖或聚合、合成的方式,而不是使用继承
设计原则的核心思想:
- 找出应用中变化的部分,独立出来,跟鲜少变动的代码混合在一起
- 针对接口编程而不是面向实现编程
- 为了交互对象的松耦合而努力!
设计模式
序号 | 分类 | 模式 | 解决问题 | 设计方案(实现方法) | 应用场景 | 扩展延伸 |
---|---|---|---|---|---|---|
1 | 创建型1 | 单例模式 | 只能存在一个对象实例 |
构造函数私有化,对外提供静态实例 | 项目中只需要一个实例,如hibernate的sessionfactory作为数据存储源的代理 | 饿汉式(编译生成)、懒汉式(首次使用初始化)、双重检查(并发优化)、枚举(新特性) |
2 | 创建型2 | 简单工厂 | 大量创造 某种对象 |
定义一个创建对象的类(工厂),由它来实例化对象 | 单维度重复生成、封装实现细节 | 静态简单工厂、工厂模式 |
3 | 创建型2 | 工厂模式 | 大量创造多维 需求对象 |
定义产品接口,由多个不同的子类决定生产什么产品(更多工厂) | 多维度重复生成对象 | 抽象工厂 |
4 | 创建型3 | 抽象工厂模式 | 多类多维 产品族生成代码爆炸 |
定义抽象类抽象方法,由子类决定生产什么产品(组合工厂) | 多类产品族生成、简化工厂类 | |
5 | 创建型4 | 原型模式 | 创建新对象比较复杂,需要实时状态,克隆羊 |
重写并调用Object.clone或反序列化+序列化来实现 | 深度拷贝场景,如spring中对bean生命周期的判断 | |
6 | 创建型5 | 建造者模式 | 产品和产品建造过程解耦,指定产品类型和内容就可创建复杂对象 |
由产品、抽象建造者(流程)、具体建造者、指挥者builder组成 | 复杂对象创建,按蓝图设计,如stringbuilder及其父类的组合使用 | |
7 | 结构型1 | 适配器模式 | 类型转换 |
通过中间类进行接口的调用转发、扩展 | SpringMVC中DispatchServlet接收请求返回modelAndView时用适配器去找到最应的controller | 类适配、对象适配(继承换成聚合)、接口适配 |
8 | 结构型2 | 桥接模式 | 多维 度关系带来类爆炸 、违反OCP,如手机款式、品牌的指数级组合 |
多的维度抽象为接口被原有类实现并通过构造器组合,完成解耦 | 如DriverManager中聚合了Connection,通过注册、获取来完成业务解耦 | |
9 | 结构型3 | 装饰模式 | 单维、多维组合 导致类爆炸,如咖啡单点或咖啡+奶精的组合 |
装饰者和主体都继承同一个父类,装饰器组合主体完成套娃组合 | 如FilterInputStream做各种流数据转换 | 套娃解决了代码爆炸,但不易理解,用对象数组组合,如桥接或再抽象一层会易于理解和维护 |
10 | 结构型4 | 组合模式 | 单维、多维爆炸又没有实际继承关系 |
管理关系统一继承,层级关系用组合达成树形结构 | 如HashMap可以添加节点或叶子 | |
11 | 结构型5 | 外观模式 | 家庭影院、摇滚等成套场景 需要 |
高层接口提供一致化操作,子类实现并被聚合到操作类中 | 如Mybatis中的Configuration中操作MetaObject,做不同类型判断再聚合 | |
12 | 结构型6 | 享元模式 | 对象复用的池技术 |
通过操作静态成员来完成池的建立和维护 | 如Integer对-127~128之间的数值做了缓存 | |
13 | 结构型7 | 代理模式 | 提供增强版 的代理对象,如增加权限判断、日志审计等 |
静态代理(新建代理类)、动态代理/jdk代理(Proxy反射,需实现接口)、cglib代理(第三方jar,通过asm反编译,跳过接口) | 外网代理、缓存代理、远程代理、同步代理 | |
14 | 行为型1 | 模板方法 | 完成某个过程要一系列步骤 ,几乎都相同,仅个别过程有变化 ,或如架构师和同事的分工协作,做好算法,留钩子 |
抽象类定义执行方法模板,子类实现具体各个方法。 | Spring ioc 容器初始化 | 跟建造者模式很像,但是在一组父子类中完成 |
15 | 行为型2 | 命令模式 | 特定场景需求,如遥控器、智能家居app,对批量发送类似指令 |
定义调用者、接受者、命令对象,其中命令对象是桥梁,起到解耦的作用 | JDBCTemplate中execute对CRUD的命令设计,方便任务边界划分,工作量化 | 设计空命令可以省去很多判断。 还可以做成多线程处理队列 |
16 | 行为型3 | 访问者模式 | 特殊场景,数据结构稳定,操作易变且无关联性,这时对数据结构和操作进行解耦 | 被访问类内部提供一个对外接待的接口,双分派,把自己做参数,互相传递两次! | 符合单一职责、便于扩展,常用于UI、报表、拦截器 、过滤器等设计 |
破坏了ocp、迪米特法则、依赖倒转等规则,但特定场景使用 |
17 | 行为型4 | 迭代器模式 | 集合元素种类不同,遍历需要多种方式,还暴露元素内部结构 | 实现迭代器 接口 |
ArrayList等集合类 | |
18 | 行为型5 | 观察者模式 | 解决消息通知,类似MQ 场景的需求 |
通过注册中心、用户两级,以聚合、继承等形式实现 | JDK中的Observable | |
19 | 行为型6 | 中介者模式 | 将网状 结构梳理成星型 结构 |
网状改三层,提供中间层(中介)进行解耦 | SpringMVC 中的Controller |
|
20 | 行为型7 | 备忘录模式 | 需要多快照 、回滚 的场景 |
增加守护者角色,通过集合、map提供对象缓存(很常规的做法) | 事务回滚,回档、重做等操作 | 跟原型模式组合使用,简单、节省内存 |
21 | 行为型8 | 解释器模式 | 计算器、解释器、正则表达式 等特殊场景 |
通过分词器、语法分析器构建语法分析树,过程是比较复杂的。 | Spring中的SpelExpressionPasser | |
22 | 行为型9 | 状态模式 | if/else因状态判断多而爆炸,怎么优雅的扩展;事件有很多状态 ,状态间可以互转 |
定义接口,用抽象类异常实现,在多子类中定义状态,然后组装。 | 缺点:状态类会有很多 | |
23 | 行为型10 | 策略模式 | if/else因策略判断多 而爆炸,怎么优雅的扩展; |
定义策略接口,策略子类作为参数传递,实现效果,解耦 | JDK的Arrays.sort就可以传递策略对象 | |
24 | 行为型11 | 职责链模式 | 多级请求 、审批流、字符流处理器等场景,减少不必要的代码爆炸 |
组成对象无环图DUG,类似链表的形式进行挨个处理,节点不能太多,不然影响性能 | SpringMvc中的HandlerExecutionChain |
设计模式是问题总结出的经验,不是代码,而是某类问题的通用解决方案,在代码重构中特别有用!
设计模式的本质是提高软件的维护性、通用性、扩展性,并降低软件的复杂度
。
《设计模式——可复用面向对象软件的基础》有本经典书,由Gof四人组编写。
设计模式不局限于某种语言,通常面向对象的语言都适用。
设计模式分三种类型:
- 创建型,解决类的创建问题
- 单例模式,仅生成1个实例
- 抽象工厂模式,提供创建产品的接口,子类来生产一系列产品
- 原型模式,深度拷贝原型
- 建造者模式
- 工厂模式,定义创建产品接口,子类决定生产什么产品
- 结构型,解决类的爆炸性问题
- 适配器模式
- 桥接模式
- 装饰模式
- 组合模式
- 外观模式
- 享元模式
- 代理模式
- 行为型,解决类的各种行为问题
- 模板方法
- 命令模式
- 访问者模式
- 迭代器模式
- 观察者模式
- 中介者模式
- 备忘录模式
- 解释器模式
- 状态模式
- 策略模式
- 职责链模式
创建型模式(5)
创建型解决了类的创建问题,主要有工厂和建造者模式,单例、原型解决特殊场景需要
- 工厂系列
- 简单工厂,就是有个代理人(工厂)来统一对接后端工坊的创建。
- 工厂模式,就是有代理商直接负责工坊的创建,是一种高效的过渡模式。
- 抽象工厂,由区域代理调度不同的工厂,解决
产品族
的创建问题。
- 建造者模式,就是用蓝图,按顺序去创建,如stringbuilder里面的多个方法顺序执行。
- 特殊场景
- 单例模式,通过关闭构造函数,提供静态实现的方法,其中有几种变种。
- 饿汉模式,直接jvm静态初始化、提供,占用内存,但没有并发风险。
- 懒汉模式,首次使用时初始化,因有了判断所以并发存在风险,而采用不同锁技术来控制。
- 还有通过匿名内部类、枚举等新鲜玩法。
- 原型模式,解决深度克隆的问题,用的object.clone或反序列、序列两种技术。
- 单例模式,通过关闭构造函数,提供静态实现的方法,其中有几种变种。
1、单例模式
限定某个类只能有一个实例,并提供一个获取的静态方法,如Hibernate的SessionFactory,有八种方式:
推荐四种使用:枚举、静态内部类、双重检查、饿汉式(稍微有点内存浪费,无所谓)
使用场景:创建需耗费大量时间、大量资源,且高频重复使用,如数据源、sessionFactory等文件、数据库资源。
饿汉式, JDK1.0的runtime就是用这个了。
- 静态常量,构造器私有化,类设置静态常量并实例,提供public的静态方法供使用,可用
- 静态代码块,同上,把new的过程放在静态代码块中,可用
- 优点:装载就完成实例化,没有线程同步问题。
- 缺点:没有懒加载效果,如果没有使用,那就内存浪费
懒汉式
- 线程不安全,同饿汉式,但只在提供时判断是否实例化,仅适用于单线程,不推荐。
- 线程安全,同步方法,同上,在提供的方法上加上
synchronized
关键字,可用,但锁+每次判断,导致效率低 - 线程安全,同步代码块,比较诡异的写法,在线程不安全写法之上,在if代码块中对new的过程做同步代码块,如果并发都进入if代码块,这里就没有意义了,**不推荐
双重检查
- 用volatile+synchronized,并套两层if,对并发所有情况进行控制,推荐!
- 优点:解决线程安全问题,解决懒加载问题,并解决效率问题!
- 缺点:编写复杂,首次不易理解
静态内部类
- 效果同双重检查,依赖的是静态内部类延迟加载且由锁的特性,推荐!但不同版本的jvm未验证过。
枚举
- 依赖jdk1.5的特性实现,效果还行,Effective Java作者推荐!据说还可防止反序列化再重新创建。
2、简单工厂模式(静态工厂,如果加static修饰)
面对原始需求,我们用面向对象做了三层封装,并对商品做了抽象
但增加商品、供方,或同时增加都会成倍增加复杂度
改进: 用简单工厂对商品和供方进行解耦,无论增加多少商品或供方,均是最小耦合性
小结:商品(对象)的暴增用简单工厂解耦,如果要增加商品系列,则需要对工厂进行抽象
3、工厂方法模式
增加产品系列,对供方进行抽象,让多个供方实例做工厂,也就是供应商兼工厂!这是种过渡方式。
4、抽象工厂模式
按产品系列进行工厂的抽象、解耦,适合大型复杂环境。
5、原型模式
解决克隆羊的需求,只要实现cloneable接口、实现clone方法即可。
其中clone有两种方式:
- 调用object的clone方法,不过类中如果有其他对象需要挨个clone,后续还需要持续修改。
- 通过序列化、反序列化可一次完成工作,没有后续烦恼。
//通过Object的拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
FactoryTest clone = (FactoryTest) super.clone();
clone.friend = (FactoryTest) this.friend.clone();
return clone;
}
/*通过序列化、反序列化来获取*/
@Override
protected Object clone() throws CloneNotSupportedException {
ByteArrayOutputStream byteArrayOutputStream = null;
ObjectInputStream objectInputStream = null;
ByteArrayInputStream byteArrayInputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
try {
byteArrayOutputStream.close();
objectInputStream.close();
byteArrayInputStream.close();
objectOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
6、建造者模式
参考对象:StringBuilder
把产品、零件生产、组装进行解耦,比如盖房子,需要打地基、筑墙、盖屋顶等步骤,按一定蓝图来建造,所以有三个主要角色:
- 产品
- (抽象)建造者,如果有多个不同的建造者
- 建造者,完成不同的建造过程
- 指挥者,组装(蓝图)
StringBuilder是个例子,实际情况就是指挥者也可能充当一部分建造者职能,甚至有其他模式混用。
建造者模式和抽象工厂模式的差异:
抽象工厂解决产品族的创建
建造者模式按蓝图生产零件,然后组装的过程
结构型模式(7)
1、适配器模式
参考对象:SpringMVC的HandlerMappper下研究Dispatcher,根据不同的controller获取对应adapter,然后adapter再处理controller
解决类型转换的问题,通过一个适配器来做中间转换,适配器有三种形式
- 类适配器
- 对象适配器,将上图做改进,将继承换成聚合。
- 接口适配器,用抽象类先全空的形式实现接口,在使用时才针对部分刚需做实际的实现
2、桥接模式
通过桥接类和行为接口,解决因为两维度导致的类爆炸
问题,如手机的款式和品牌,导致新增一个系列而爆炸。
主要案例:spring sql驱动器
从上面的图可以看到,品牌被抽离后聚合,两个维度被进行了拆分,任意添加都互不影响。
3、装饰模式
装饰器采用继承+组合的方式,完成套娃!解决单品、多次装饰的类爆炸
问题!
主要案例:FilterInputStream
装饰器就像是桥接模式又多了个继承!
4、组合模式
组合模式解决看似继承关系,实质为树形结构的类爆炸问题,通过继承+组合的形式。
技巧:叶子节点不需要的功能在抽象类中提前实现,如抛出个异常。
实例:hashmap
5、外观模式
类似建造者模式的1个指挥者+1个建造者,这里是1个外观搭配多个工具,封装调用细节!
比如新系统调用旧系统的可以用外观模式做一个封装,简化维护!
应用场景:mybatis中configuration中对metaobject
6、享元模式
享元模式,共享对象模式
,就是池技术,解决重复对象的内存浪费技术。
应用场景:数据库连接池、字符串池等等
源码:IntegerCache
7、代理模式
代理模式,解决对象增强的需求,如增加安全审计,在aop编程中常见,有三种形式
- 静态代理,通过代码继承实现,缺点是比较僵硬
- 动态代理,通过jdk的反射调用做增强
- cglib代理,第三方jar,通过asm反编译,相比jdk代理不需要实现接口!更灵活,是spring的主要形式。
行为型模式(11)
1、模板方法
类似建造者模式,这里主要关注行为,特别是模板算法流程套装
,所以,stringbuilder也是不错的分析例子
通过模板规定算法大致流程,具体实现在子类中去实现
这是很好的架构师工具
2、命令模式
价值:把发起请求和执行请求的对象解耦,发起请求者不需要知道具体执行者是谁
案例:Spring JDBCTemplate的query方法,调用的内部类
这应该也是架构师常用分解工作的好模式,方便定义任务便捷,类也不会爆炸
3、访问者模式
特定场景使用:数据结构不变,甚至不允许做任何修改,但又有很多操作,并且需要被解耦。
举例:UI、报表、拦截器、过滤器等。
注:这里有个特殊名词双分派
,就是a调用b,将c作为参数传给b,然后b再将自己传给c。
4、迭代器模式
集合元素种类不同,遍历需要多种方式,还暴露元素内部结构。
通过实现迭代器接口,如ArrayList家族
5、观察者模式
观察者模式目前比较类似的是MQ应用,SpringCloud注册中心的职责,通过提前注册,有消息时及时提醒。
标准是采用接口聚合的形式,jdk的源码中Observable是采用继承的形式完成消息的传递
6、中介者模式
如springmvc中的控制层,对网状结构拆成三层,提供中间层,形成星型结构,达到解耦的效果!
7、备忘录模式
对一个或多个对象,提供多个快照,并支持任意回滚、重做。
如事务回滚,ctrl+z/Y的回滚重做需要。
8、解释器模式
特殊场景使用,如计算器、正则表达式等等,spring中对于注解写法有特有的解释器,如SpelExpressionParser
9、状态模式
当状态特别多、状态还可以互换时,如果用传统if/else来判断,代码肯定爆炸,非常难维护。
通过抽取状态类来简化维护难度,缺点是状态类会很多。
优雅的设计:定义接口,用抽象类异常实现,在多子类中定义状态,然后组装。
10、策略模式
if/else因策略判断多而爆炸,怎么优雅的扩展;事件有很多状态,状态间可以互转
定义策略接口,策略子类作为参数传递,实现效果,解耦
11、职责链模式
多级请求、审批流、字符流处理器等场景,减少不必要的代码爆炸
组成对象无环图DUG,类似链表的形式进行挨个处理,节点不能太多,不然影响性能
小结
设计模式有标准,实际使用可以灵活,往往很多优秀的代码有灵魂但不是完全照搬标准模式!
设计模式难学的原因:
- 这是偏理论、哲学层米的设计思想,确实门槛比较高
- 没有具体实操,面向所有语言,所以难有统一的java实现样例
- 翻译的问题,中文很容易歧义
评论系统未开启,无法评论!