23种设计模式笔记
设计模式概述
代码的评价标准
- 可维护性 : 在不破坏远有的代码情况下,原有代码设计以及不引入新的bug前提下,能够快手的修改或者新增代码;
- 灵活性: 在原有的代码添加新的代码,已有的代码不受影响,不产生冲突,不出现排斥,保证自身代码不被破坏的情况下接纳新的代码,预留扩展点;
- 简洁性 : 代码的写法,应该是别人理解他需要时间的最小化;
- 可复用性:尽量减少重复代码的编写,复用已有的代码;
- 可测试性:在进行单元测试的时候,代码是容易测试的;
- 可扩展性 : 在不修改或者少量修改代码的情况下,通过扩展的方式添加新的功能代码;
- 可读性 : 让别人能够快速的看懂你写的代码;容易理解的代码;检查代码的风格和变成规范判断代码的可读性;
编程的方法论
面相对象
面相对象是一种编程思想,也是一种编程范式,现在比较流行的编程语言大部分是面向对象编程语言,而大部分项目也是基于面相对象编程风格进行开发的;
设计原则
设计原则是指导我们代码设计的一些经验总结。在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的扩展性和灵活性,程序员要尽量根据设计原则开发程序,从而提高开发的效率,节约软件开发成本和维护成本。
1.单一职责原则
2.开闭原则
3.里氏替换原则
4.依赖倒转原则
5.接口隔离原则
6.迪米特法则
设计模式
设计模式是针对软件开发中经常遇到的一些设计问题,总结出来一套解决方案,或者设计思路。大部分设计模式要解决的都是代码的可扩展性问题。
编程规范
编程规范主要解决代码的可读性问题,编码规范相对设计原则,设计模式,更加具体,更加偏重代码细节。
重构
维基百科: 在软件工程学里,重构代码一词通常是指在不该变代码的外部行为情况下,修改源码,有时候非正式的称为,"清理干净"。 在极限编程或者其他明捷方法学中,重构常常是团建开发循环的一部分: 开发者轮流增加性的测试和功能,并重构代码来增进内部的清晰和一致性;
提高代码质量的方法论总结
1,面向对象的思想
2,设计原则(指导方针)
3,设计模式(是设计原则的具体实现)
4,变成规范 (提高代码的可读性)
5,重构
设计模式概述
设计模式 是一套被反复使用,多数人知晓的,经过分类编目的,代码设计经验的总结。
-
世界上本没有路,走的人多了变成了一条路。在程序员的世界中,本没有设计模式,写代码的人多了,他们便总结出了一套能够提高开发和维护效率的套路,这就是设计模式。
提高复杂代码的设计和开发能力。
有助于我们读懂源码,学习框架更加事半功倍。
设计模式分类
GoF 设计模式有23个,它们各具特殊,每个设计模式都是为了一个可重复的设计问题提供了一套解决方案。根据他们的用途,设计模式分为:
- 创建型 (Creational)
- 结构型(Structural)
- 行为型(Behavioral)
就相当于交通工具,根据不同的场景使用不同的交通工具;
创建型模式(5种): 提供创建对象机制,提升已有代码的灵活性和可复用性
常用: 单例模式,工厂模式(工厂方法和抽象工厂),建造者模式
不常用: 原型模式
结构型模式(7种): 介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活性和高效;
常用: 代理模式,桥接模式,装饰者模式,适配器模式。
不常用: 外观模式,组合模式,享元模式。
行为型模式(11种): 负责对象间的高效沟通和职责传递委派。
常用: 观察者模式,模版模式,策略模式,责任链模式,迭代器模式,状态模式。
不常用:访问者模式,备忘录模式,命令模式,解释器模式,中介模式。
UML图概述
统一建模语言 :是用来设计软件的可视化建模语言。它的特点是简单,统一,图形化,能表达软件设计种的动态和静态信息
UML分类
静态结构图:类图,对象图,组件图,部署图
动态行为图: 状态图,活动图,时序图,协作图,构件图等
六大设计原则
1.单一职责原则
官方定义: 单一职责,英文缩写SRP, 在《架构整洁之道》 中,关于这个原则是这样描述的:一个类或者模块只负责完成一个职责(或者功能)
通俗解释: 单一职责原则的定义描述非常简单,也不难理解。一个类只负责完成一个职责或者功能。
也就是会所在类的设计中,我们不要设计大而全的类,而是设计粒度小,功能单一的类。
2, 开放封闭原则
官方定义: 一般认为最早提出开闭原则的是伯特兰·迈耶 。他在1988年发表的《面向对象软件构造》中给出的。
在面向对象编程领域中,开闭原则规定软件中的对象,类,模块和函数对扩展应该是开放的,但对于修改是封闭的。这以为这应该用抽象定义结构,用具体实现扩展细节,以此确保软件系统开发和维护过程中的可靠性。
通俗解释:
定义:对扩展开放,对修改关闭。
优点:
- 新老逻辑解耦,需求发生改变不会影响老业务的逻辑。
- 改动成本小,只需要追加新逻辑,不需要修改老的逻辑
- 提供代码的稳定性和可扩展性;
开闭原则是所有设计模式的最核心目标,顶层设计思维:
- 抽象意识
- 封装意识
- 扩展意识
3,里氏替换原则
官方定义: 里氏替换原则 是有麻省理工学院计算机系教授芭芭拉·利斯科夫 1987年在"免息那个对象技术的高峰会议" (00PSLA) 上发表的一篇论文《数据抽象和层次》 里提出的。
他在论文中提到:如果S是T的子类型,对于S类型的任意对象,如果将他们看做T类型的对象,则对象的行为也理应与期望的行为一致。
通俗解释:
什么事替换?
替换的前提是面向对象语言所支持的多态性,同一个形式具有不同表现形式或者形态的能力。
简单说就是当我的一个方法的参数是一个接口的类型时,这个方法可以接收所有实现过这个接口的实现类;
什么事与期望一致的替换?
在不了派生类的情况下,仅通过接口或者积累的方法,即可清楚的之道方法的行为,而不管那种派生类的实现,都与接口或积累方法的期望行为一致。
(只需要阅读接口,就知道接口是想要做什么,不需要具体的实现方式,就知道他给我返回什么。)
4,接口分离原则
《架构整洁之道》 中的隔离原则的定义: 客户端不应该被迫依赖于他不使用的方法,
该原则还有另一个定义:一个类对另一个类的依赖应该建立在最小接口上。
通俗的解释:
上面两个定义的含义使用一句话概括就是: 要为各个类建立他们需要的专用接口,而不要视图去建立一个很庞大的接口供所有依赖他的类去调用。
删除接口,直接是代码隔离,不需要的业务系统不加这个实现的代码。需要删除接口的业务系统添加这个删除的代码打包。
遵循接口隔离原则的优势
- 将胖接口分解成多个粒度的小接口,可以提高系统的灵活性和可维护性。
- 使用对个专门的接口,还能够体现出对象的层次。
- 能够减少项目工程中的冗余代码;
5,依赖倒置原则
官方定义: 依赖倒置原则是罗伯特 在C++Repor上发表的文章中提出的
依赖倒置原则 是指在设计代码架构时,高层模块不应该依赖于底层模块,二者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象。
通俗解释:
依赖倒置原则想要表达的是:由于在软件设计中,细节具有多变性,二抽象层则相对稳定,因此抽象为基础搭建起来的架构要比已细节为基础搭建起来的架构要稳定得多。
指导框架设计
控制反转
他是一种框架谁常用的模式,但是不是具体方法;
本来控制权在程序员手上的,现在需要将控制权放在框架上,就是控制反转。
依赖注入
依赖注入是实现控制反转的手段,他是一种具体的编码技巧;
6,迪米特法则 (最少知识原则)
迪米特法则: 又叫做最少知识原则,指的是一个类/模块对其他类/模块有越少的了解越好;
通俗解释:
简单来说迪米特法则想要表达的思想是: 不应该有直接依赖关系的类之间,不要有依赖,有依赖关系的类之间,尽量指依赖必要的接口。
迪米特法则强调的是:多使用中间人这个角色。
设计模式
创建者模式(5种)
创建型模式(5种): 提供创建对象机制,提升已有代码的灵活性和可复用性
常用: 单例模式,工厂模式(工厂方法和抽象工厂),建造者模式
不常用: 原型模式
单例模式
定义: 单例模式 是java中最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务,而则类被称为单例模式。
使用单例模式要做两件事情
- 保证一个类只有一个实例。
- 为该类提供一个全局的访问点;
饿汉式-单例模式
// 饿汉式模式
// 特点:不支持延时加载,获取实例对象速度快,但是对象比较大,而且一直没有使用,会有内存浪费;
public class Singleton_01 {
// 1私有构造方法
private Singleton_01(){
}
//2. 在本类中创建私有静态的全局对象
private static Singleton_01 instance =new Singleton_01();
//3. 提供全局的访问点,外部获取单例对象
public static Singleton_01 getInstance(){
return instance;
}
}
懒汉式
// 懒汉式 线程安全 使用synchronized 锁住创建单例对象, 缺点:并发度低
public class Singleton_03 {
// 1私有构造方法
private Singleton_03(){
}
//2. 在本类中创建私有静态的全局对象
private static Singleton_03 instance ;
//3. 通过添加synchronized 保证多线程模式下单例的唯一性
public static synchronized Singleton_03 getInstance(){
if(instance==null){
instance= new Singleton_03();
}
return instance;
}
}
双重检查-单例模式
// 单例模式 双重校验
public class Singleton_04 {
// 1私有构造方法
private Singleton_04(){
}
//2. 在本类中创建私有静态的全局对象
// volatile 关键字 保证变量的可见性,屏蔽指令重排序。
private static volatile Singleton_04 instance ;
//3.获取单例对象的静态方法
public static Singleton_04 getInstance(){
if(instance==null){
synchronized (Singleton_04.class){
if(instance==null){
instance= new Singleton_04();
/**
* 创建对象的代码,在jvm 被分为三步
* 1,分配内存
* 2,初始化对象
* 3,将instance 指向分配好的内存空间;
*/
}
}
}
return instance;
}
}
内部静态类-单例模式
// 静态内部类 -单例模式(推荐)
public class Singleton_05 {
// 1 构造方法私有化
private Singleton_05() {
// 解决反射对单例模式的破快
if(SingletonHandler.instance!=null){
throw new RuntimeException("不允许非法访问");
}
}
// 创建静态内部类
private static class SingletonHandler {
// 静态内部类中穿件,在装载内部类的时候,才会被创建单例对象(静态内部类的特性,只会被加载一次)
private static Singleton_05 instance = new Singleton_05();
}
public static Singleton_05 getInstance() {
return SingletonHandler.instance;
}
}
枚举单例模式
// * 单例模式 枚举方式
public enum Singleton_06 {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Singleton_06 getInstance(){
return INSTANCE;
}
}
单例模式总结:
-
饿汉式(Eager Initialization):
- 适用于单例实例在应用程序启动时就需要被创建,并且创建实例的代价较小的情况。
-
懒汉式(Lazy Initialization):
- 适用于单例实例在应用程序启动时不需要被创建,而是在第一次使用时创建的情况。
- 需要考虑多线程环境下的线程安全性,可以使用
synchronized
关键字或双重检查锁定等方式。
-
双重检查锁定(Double-Checked Locking):
- 在需要懒加载的情况下,且希望提高性能时使用。
- 注意要在JDK5及以上版本使用,并且要将实例变量声明为
volatile
。
-
静态内部类(Static Inner Class):
- 适用于懒加载的情况,天生线程安全。
- 代码简洁,没有使用
synchronized
,性能较好。
-
枚举(Enum):
- 适用于天然单例的情况,防止反射和序列化攻击。
- 代码简洁,实现简单,且线程安全。
综合考虑,如果不需要懒加载,并且在应用程序启动时就需要创建单例实例,饿汉式是一个简单而可靠的选择。如果需要懒加载,可以考虑使用静态内部类或者双重检查锁定,具体取决于项目的需求和性能要求。如果天然单例,并且使用枚举是一个不错的选择。
总的来说,根据具体的需求选择适当的单例实现方式是最重要的。枚举单例-天然防止多重实例创建,线程安全,解决序列化问题,解决反射问题,缺点限制扩展性,不够灵活,业务复杂的情况下,扩展性不满足业务要求。
工厂模式
工厂模式 是java中最常用的设计模式之一,这种类型的设计模式属于创建模式,它提供了创建对象的最佳方式。
在工厂模式中,我们不会创建对象时暴露创建逻辑,并且通过使用一个共同接口来执行创建的对象
《设计模式》一书中,工厂模式被分为了三种: 简单工厂(不属于23中设计模式),工厂方法和抽象工厂,(不过在书中作者将简单工厂模式看作是工厂方法模式的一种特例)
简单工厂模式
简单工厂模式不是一种设计模式,反而比较像是一种编程习惯。简单工厂模式有叫做静态工厂模式,它是一种使用静态方法接受不同的参数来返回不同的实例对象
实现方式
定义一个类, 根据传入的参数返回不同的实例,被创建的实例具有共同的父类或接口。
适用场景
- 需要创建的对象较少。
- 客户端不关心对象的创建过程。
简单工厂结构
-
抽象产品: 定义了产品的规范,描述产品的主要特性和功能。
-
具体产品:实现或者集成抽象产品的子类
-
具体工厂:提供了创建产品的方法,调用这通过该方法来获取产品。
/**
-
具体工厂: 生成免费商品
-
@author mengzhenxin@paraview.cn
-
@create_date 2024/2/25 0025
-
@update_date 2024/2/25 0025
-
@Message
-
创建免费商品的三个对象。
*/
public class FreeGoodsFactory {public static IFreeGoods getInstance(Integer awardsType){
IFreeGoods iFreeGoods=null; if(awardsType==1){ // 打折券 iFreeGoods=new DiscountFreeFGoodsService(); } else if (awardsType==2) {// 优酷会员 iFreeGoods=new YouKuMemnerService(); } else if (awardsType==3) {// 小礼品 iFreeGoods=new SmallGiftService(); } return iFreeGoods;
}
}
/**
-
发放奖品的接口
-
@author mengzhenxin@paraview.cn
-
@create_date 2024/2/25 0025
-
@update_date 2024/2/25 0025
-
@Message
*/
public class DeliverController {public ResponseResult awardToUser(AwardInfo awardInfo){
try {
IFreeGoods instance = FreeGoodsFactory.getInstance(awardInfo.getAwardType());
ResponseResult responseResult = instance.sendFreeGoods(awardInfo);
return responseResult;
}catch (Exception e){
e.printStackTrace();
return new ResponseResult("201","奖品发放失败");
}}
}
-
工厂方法
工厂方法介绍
工厂方法模式属于创建型模式,
概念: 定义一个用于创建对象的接口,让子类决定实例化那个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
工厂方法模式的主要角色
- 抽象工厂: 提供了创建产品的接口,调用这通过它访问具体的工厂的工厂方法来创建产品。
- 具体工厂:主要是实现抽象工厂方法中的抽象方法,完成具体的产品创建。
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来穿件,它同具体工厂之间一一对应。
抽象工厂模式
抽象工厂模式比工厂方法模式的抽象程度更高,在工厂方法模式中每一个具体工厂只需要生产一种具体的产品,但是在抽象工厂模式中,一个具体的工厂可以生产一组相关的具体产品,这样一组产品被称为产品族,产品族中的每一个产品都是属于某个产品的继承等级结构。
产品等级结构和产品族
为了更好的理解抽象工厂,我们这里先引入两个概念:
产品等级结构: 产品等级结构即产品的继承结构,如果一个抽象类是电视机,其子类还有海尔电视机,海信电视机,TCL电视机,则抽象电视机的具体品牌之间构成了一个产品等级结构,抽象电视机是父类,而具体的品牌的电视机是其子类。
产品族: 在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中一组产品,例如海尔电视机工厂生产的海尔电视机,海尔店冰箱,海尔电视机位于产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。
抽象工厂模式概述
抽象工厂模式 原始定义: 提供一个创建一系列相关或依赖对象的结构,而无需指定他们具体的类;
抽象工厂模式为创建一组对象提供了解决方案,与工厂模式相比,抽象工厂模式中的具体工厂不只是穿件一种产品,而是负责创建一个产品族
、
抽象工厂客户端不需要知道具体是哪个工厂给她实现的 ,
抽象工厂 想要改变工厂的话只需要改变工厂的一个具体实例就可以了这样就有了一个扩展性;
@Data
public class Client {
private AbstractTV tv;
private AbstractFreezer freezer;
public Client(AppliansesFactory factory) {
/**
* 使用抽象工厂生产家电,在客户端看来就是还是用抽象工厂
*/
this.tv = factory.createTV();
this.freezer = factory.createFreezer();
}
public static void main(String[] args) {
Client client = new Client(new HisenseFactory());
System.out.println(client.getTv());
System.out.println(client.getFreezer());
}
}
抽象工厂方法总结
从代码层面中我们可以看出,抽象工厂模式向使用(客户)方隐藏了一下变化:
- 程序所支持的实例集合(具体工厂)的数目;
- 当前是使用的实例集合中的那一个实例;
- 在任意时刻给定被实例化的具体类型;
所以说,在理解抽象工厂模式时,你一定要牢牢记住,如何找到某一类产品的正确共性功能 这个重点
软件使用者: 更关心共性的功能
软件创建者:需要找到共性的功能,并且隐藏信息
抽象工厂模式优点
- 对于不同产品系列有比较多共性特征是,可以使用抽象工厂模式,有助于提升组件的复用性;
- 当需要提升代码的扩展性和降低维护成本时,把对象的创建和使用过程分开,能有效的将代码统一到一个级别上
- 解决跨平台带来的兼容性问题;
抽象工厂模式的缺点
新增的产品等级结构麻烦,需要对原有结构进行较大的修改,甚至需要修改抽象层代码,这显然带来较大变化,违背了开闭原则;
建造者模式
建造者模式介绍
建造者模式也被称为 生成器模式,一种创建型设计模式;
定义: 将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示;
建造者模式要解决的问题?
建造者模式可以就将部分和其组装过程分开,一步一步创建一个复杂的对象, 用户只需要指定复杂的对象类型就可以得到该对象,而无需知道内部的具体构造细节。
比如:一辆汽车式有多个零部件组成,包括车轮,方向盘,发动机等等,对于大多数用户而言,并不需要知道这些零部件细节,并且几乎不会使用单独某个零部件,而是使用完整一辆汽车,而建造者模式就是负责将这些零部件进行组装然后将完整的汽车返回给用户;
建造者模式的主要4个角色
建造者实现
创建共享单车
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等
材质。对于自行车的生产就可以使用建造者模式。
这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和HelloBuilder是具体的建造者;Director是指
挥者。类图如下
public class BuilderClient {
public static void main(String[] args) {
// 1 创建指挥长
Director director = new Director(new MobikeBuilder());
// 2,获取自行车
Bike bike = director.constructBike();
System.out.println(bike.getFrame());
System.out.println(bike.getSeat());
}
}
建造者模式方式2
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造需要传入很多参数时,如果穿件这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。
构造方法穿管复杂对象的问题
-
构造方法如果参数过多,代码的可读性和易用性都会变差,在使用构造函数时,很容易搞错参数的顺序,传递进去错误的参数值,导致有很隐蔽的BUG出现。
package com.paraview.infini.dfrcb.apim.buider_pattern.example3;
/**
- 建造者模式
-
1. 目标类的构造方法需要传入一个Builder对象
-
2.builder 位于目标类的内部,并且使用static修饰
-
3.builder 类位于提供内置各种set方法,注意:set 方法的返回值本身是builder 本身
-
4.builder类提供一个build() 方方达,实现目标类的创建;
*/
public class RabbitMQClient3 {
//1. 目标类的构造方法需要传入一个Builder对象
private RabbitMQClient3(Builder builder){} // 2.builder 位于目标类的内部,并且使用static修饰 public static class Builder{ // 保证属性的密闭性 private String host; private int port; private int mode; private String exchange; private String queue; private Boolean hasDurable; int connectionTimeout=10000; public Builder setHost(String host) { this.host = host; return this; } public Builder setPort(int port) { this.port = port; return this; } public Builder setMode(int mode) { this.mode = mode; return this; } public Builder setExchange(String exchange) { this.exchange = exchange; return this; } public Builder setQueue(String queue) { this.queue = queue; return this; } public Builder setHasDurable(Boolean hasDurable) { this.hasDurable = hasDurable; return this; } public Builder setConnectionTimeout(int connectionTimeout) { this.connectionTimeout = connectionTimeout; return this; } public RabbitMQClient3 build(){ // 校验通过之后再返回 if(mode==1){ if(exchange !=null){ throw new RuntimeException("工作队列模式无需交换机"); } if(queue ==null || queue.trim().equals("")){ throw new RuntimeException("工作队列模式名称不能为空"); } if (hasDurable==false){ throw new RuntimeException("工作队列模式必须开启持久化"); } }else if(mode==2){ if(exchange ==null){ throw new RuntimeException("路由模式下必须设置交换机"); } if(queue !=null || queue.trim().equals("")){ throw new RuntimeException("路由模式无需设计队列名称"); } } return new RabbitMQClient3(this); } } public void sendMessage(String msg){ System.out.println("发送消息....."); }
}
public class App {
public static void main(String[] args) {
// 获取连接对象
RabbitMQClient3 build = new RabbitMQClient3.Builder()
.setConnectionTimeout(10000)
.setHost("127.0.0.1")
.setPort(1234)
.setQueue("aw")
.setMode(1)
.setHasDurable(true)
.build();
build.sendMessage("Hello World");
}
}
补充我们在使用lombok 工具的时候
使用@Builder 就是建造者模式,这个类就可以使用建造者模式
建造者模式总结
- 工厂模式是用来创建不同但是相关类型的对象,(集成同一父类或者接口的一组子类) ,由给定的参数来决定创建那种类型的对象。
- 建造者模式是用来穿件一种类型的复杂对象,通过设置不同的可选参数,"定制化"地穿件不同的对象。
建造者优缺点
-
优点
- 建造者模式的封装性很好,使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装性在指挥者类中对整体而言可以取得更好的稳定性。
- 在建造者模式中,客户端不必知道内部组成细节,将产品本身的创建过程结构,是的相同的创建过程可以不同的对象产品。
- 可以更好精细的控制产品的创建过程,将复杂的创建步骤分解在不同的方法中,是的创建过程更加清晰,也方便使用程序来控制创建过程。
- 创建者模式很容易进行扩展,如果有新的需求,可以通一个姓的建造者类就可以完成,基本上不用修改之前测试通过的代码,因此也就可以不回对原来功能有引入风险。符合开闭原则
-
缺点
- 建造者模式所创建的产品一般有比较多的共同点,其组成部分相识,如果产品之间查一下很大,则不适合使用建造者模式,因此使用范围受到一定限制。
原型模式
原型模式介绍
定义: 原型模式用一个已经创建的实例作为原型,通过复制改原型对象来穿件一个和源对象相同的新对象;
新对象:
西游记中的孙悟空,拔毛变小猴,孙悟空这种根据自己的形状复制出很多个身外化身的技巧,在面向对软件设计领域被称为原型模式,孙悟空就是原型对象。
前端程序员,对原型模式是非常的了解,因为前端创建对象通常是使用复制的方式,后端使用的不多。
原型模式解决的问题
如果创建对象的成本比较大,比如对象中的数据经过复杂的计算才得到,或者需要从RPC 接口或者数据库等比较慢的IO中获取;
这种情况我们可以通过使用原型模式,从其他已有对象中进行拷贝,而不是每次都创建新的对象,进行一次耗时操作。
原型模式原理
抽象原型类: 有一个克隆的方法,满足原型对象,有两个子类,具体的原型类;
两个原型类实现了克隆方法,
客户端呢直接使用 prototype.clone 克隆自己想要的原型对象
浅克隆:
克隆对象中的所有变量的值域原型对象完全一致(引用数据类型中存储的地址是完全一致的)
深克隆
在原型设计模式中有一个原型 抽象接口,或者抽象类;s
在java中我么不需要定义这个样的一个原型的抽象类,因为在java中的Object 类中有一个clone 方法
那么我们在原型类中只需要重写clone 方法即可,然后继承Cloneable,标识这个类是可以被复制的
class Prototype implements Cloneable {
// 添加需要复制的属性
private String name;
private int age;
private SomeClass someField; // 假设这是一个自定义类
public Prototype(String name, int age, SomeClass someField) {
this.name = name;
this.age = age;
this.someField = someField;
}
// 重写clone方法,实现深克隆
@Override
public Object clone() throws CloneNotSupportedException {
Prototype cloned = (Prototype) super.clone();
// 对引用对象进行深度复制
cloned.someField = (SomeClass) someField.clone();
return cloned;
}
// Getters and setters
}
class SomeClass implements Cloneable {
// 添加需要复制的属性
private String data;
public SomeClass(String data) {
this.data = data;
}
// 重写clone方法,实现深克隆
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
// Getters and setters
}
public class PrototypePatternExample {
public static void main(String[] args) {
SomeClass someField = new SomeClass("some data");
Prototype original = new Prototype("John", 30, someField);
try {
// 深克隆
Prototype cloned = (Prototype) original.clone();
System.out.println("Original: " + original.getName() + ", " + original.getAge() + ", " + original.getSomeField().getData());
System.out.println("Cloned: " + cloned.getName() + ", " + cloned.getAge() + ", " + cloned.getSomeField().getData());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
使用场景
原型模式常见的使用场景有一下六种。
- 资源化场景,也就是当进行对象初始化需要使用很多外部资源时,比如: IO 资源,数据文件,CPU,网络和内存等。
- 复杂的依赖场景,比如 F 对象创建的依赖A,A又依赖B,B又依赖C,... 于是创建过程是一串对象的get 和set。
- 性能和安全要求的场景,比如: 同一个用户在会话周期里,可能会反复登录平台或者使用某些受限的功能,每每一次访问请求都会访问授权服务器又进行授权,但如果每次都通过new 产生一个对象会非常的繁琐,这时可以使用原型模式.
- 同一个对象可能被多个修改者使用的场景,比如:一个商品对象需要提供给物流,会员,订单,等多个服务访问,而且各个调用者可能都需要修改其值时,就可以使用原型模式。
- 需要保存原始对象状态的场景,比如,历史激励操作的场景,就可以通过原型模式快速保存记录。
原型模式总结
原型模式的优点:
- 当创新建的对象实例较为复杂时,使用原型模式可以简单化对象的创建过程,通过复制一个已有的实例可以提高新实例的创建效率,
比如: 在AI系统中,我们经常需要频繁的使用大量不同分类的数据文件,在对这一类建立对象模型时,不仅会长时间占用IO读写资源,还会消耗大量的CPU运算资源,如果频繁创建模型对象,就会很容易造成服务器CPU被打满而导致系统宕机,通过原型模式我们可以很容易的解决这个问题,当我们完成对对象的第一次初始化后,新创建的对象和使用对象拷贝,(在内存中进行二进制流的拷贝),虽然拷贝会消耗一定的资源,但是相比初始化的外部读写和运算来说,内存拷贝消耗会小很多,而且速度很快;
2,原型模式提供了简化的创建结构,工厂方法模式常常需要一个与产品等级结构相同的工厂等级结构,(具体工厂对应具体产品),而原型模式就不需要这样,原型模式的产品复制是通过封装在原型类中的克隆方法实现的,无需专门的工厂类来创建产品。
3,可以使用深克隆的方法保存对象状态,使用原型模式复制一份并将其状态保存起来,以便在需要的时候使用,比如回复到某一历史状态,可以辅助实现撤销操作;
在某写需要保存历史状态的场景中,比如: 聊天消息,上线发布流程,需要撤销操作的程序等,原型模式能快速的复制现有对象的状态并保存副本,方便快捷快速回滚到上一次保存或者最初始的状态,避免因网络延迟,误操作等原因造成数据的不可恢复。
原型模式缺点
需要为每个类配备一个克隆方法,而改克隆方法位于一个类的内部,当对已有的类进行改造时需要修改源码,违背了开闭原则。
总结:
单例模式用来创建全局唯一对象。
工厂模式用来穿件不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定参数爱决定穿件那种类型的对象;
建造者模是用来穿件复杂类型对象,可以通过设置不同的可选参数,定制化地创建不同对象。
原型模式针对创建成本比较大的对对象,利用已有对象进行复制的方法进行复制,已达到节省创建的目的。
结构型模式(7种)
结构性模式包括种:
代理模式,桥接模式,装饰模式,适配器模式,外观模式,组合模式,享元模式
代理模式:
在软件开发中,由于一些原因,客户端不想或者直接访问一个对象,此时可以通过一个成为,代理 的第三者来实现间接访问,该方案对应设计模式被称为代理模式。
代理模式原理
真实业务类,和代理类,代理类中必须引用真实主题类,也就是说,调用代理类时,其实还是真实业务类完成实际的业务。
静态代理实现
@AllArgsConstructor
@NoArgsConstructor
public class UserDaoProxy implements IUserDao {
private IUserDao taget;
@Override
public void saveUser() {
System.out.println("开启事务");
taget.saveUser();
System.out.println("提交事务");
}
}
public class TestProxy
{
@Test
public void testStaticProxy(){
//目标类
IUserDao dao=new UserDaoimpl();
UserDaoProxy userDaoProxy = new UserDaoProxy(dao);
userDaoProxy.saveUser();
}
}
静态代理优点:
可以不修改目标类的情况下,可以扩展增强目标类;
缺点: 代码冗余,不易维护,一旦接口新增方法,目标类和代理对象都要修改;
JDK动态代理
动态代理利用了 JDK API,动态的在内存中构建代理对象,从而实现对目标的代理工功能,动态代理又被称为JDK代理或者接口代理.
静态代理与动态代理的区别:
- 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
- 动态代理是在运行时动态生成的,即编译完成之后没有实际的class文件,而是运行在动态生成类字节码,加载到JVM中。
/**
* 代理工厂-动态的生成代理对象
*/
@AllArgsConstructor
public class ProxyFactory {
private Object taget;//维护目标对象
// 为目标对象生成代理对象
public Object getProxyInstance(){
/**
* 3三个参数
* 1.目标类使用的类加载器
* 2,目标对象实现的几口类型
* 3,事件处理器
*/
return Proxy.newProxyInstance(
taget.getClass().getClassLoader(),
taget.getClass().getInterfaces(),
new InvocationHandler() {
/**事件处理器
* @param proxy 代理对象
* @param method 对应代理类的接口方法实例
*
* @param args 对应代理对象在调用接口时传递实际的参数
* 返回目标对象的返回值
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理开启事务");
// 执行目标的方案
method.invoke(taget,args);
System.out.println("动态代理提交事务");
return null;
}
}
);
}
}
public static void main(String[] args) {
//第一步创建目标类
IUserDao userdao=new UserDaoimpl();
System.out.println(userdao);// 目标对象的信息
// 第二步获取代理对象
IUserDao proxy = (IUserDao)new ProxyFactory(userdao).getProxyInstance();
System.out.println(proxy.getClass());
System.out.println("===================");
// 第三步代理方法
proxy.saveUser();
}
类是如何动态生成的
java 虚拟机类加载过程主要分为5个阶段,加载,验证,准备,解析,初始化,其中加载阶段需要完成3件事:
- 通过一个类的全限定名来获取定义此类的二进制字节流;
- 将这个字节流所代表的静态存储结构转化为方法区的运行是数据结构;
- 在内存中生成一个代表这个类的java.lang.Class 对象,作为方法区这个类的各种数据访问入口
由于虚拟机规范对这3点要求并不具体,所以实际的视线非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:
- 本地获取 (普通静态的类)
- 从网络中获取
- 运行时计算生成,这种场景使用最多的就是动态代理技术,在java.langreflect.Proxy类中,就是用了ProxyGenerateProxyClass来为特定接口生成形式为: *$Proxy 的代理类的二进制字节流
cglib动态代理
CGlib 动态代理实现
chlib 是一个第三方代码生成库,运行时在内存中生成一个子类对象,从而实现对目标类功能的扩展,cglib为没有实现接口的类提供代理,为JDK 的动态代理提供了更好的补充;
- 最底层是字节码
- ASM是操作字节码的工具
- cglib 基于ASM字节码工具操作字节码(即动态代理生成,使方法进行增强)
- SpringAOP基于cglib进行封装,实现cglib动态代理
使用cglib需要引入cglib的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib
-
cglib的Maven坐标
/**
-
cglib 动态代理类
*/
public class UserLogProxy implements MethodInterceptor {/**
- 生成cglib动态代理类
- target 需要背代理的目标类
- @param target
- @return
*/
public Object getLogProxy(Object target) {
// 需要增强器类,用来创建动态代理类
Enhancer enhancer = new Enhancer();
// 设置代理类的父类字节吗对象
enhancer.setSuperclass(target.getClass());
// 设置回调
enhancer.setCallback(this);
// 创建代动态 理对象并返回
return enhancer.create();
}
/**
*-
@param o 代理对象
-
@param method 目标对象中的方法的method实例
-
@param args 实际参数
-
@param methodProxy 代理类对象中的方法的method实例
-
@return
-
@throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Calendar calendar = Calendar.getInstance();
String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime());
System.out.println(format +"["+method.getName()+"]查询用户信息");
Object result = methodProxy.invokeSuper(o, args);return result;
}
}
@Test
public void testCgLibProxy(){
//目标类
UserServiceImpl userService=new UserServiceImpl();
System.out.println(userService);
// 代理对象
UserServiceImpl userServiceProxy = (UserServiceImpl)new UserLogProxy().getLogProxy(userService);
System.out.println(userServiceProxy.getClass());List<User> userList = userServiceProxy.findUserList(); System.out.println(userList);
}
-
代理模式 原始定义: 让你能够提供对象的代替品或占位符,代理控制着对于源对象的访问,并允许将请求提交对象前后进行一些处理。
cglib执行流程
代理模式总结
三种代理模式的实现方式的对比
- jdk代理和CGLIB代理
- 使用CGLIB实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用java反射效率要高,唯一主要注意的是,CGlib不能对声明final的类或者方法进行代理,因为CGLib 原理是动态生成被代理的子类。
在JDK1.6 ,JDK1.7,JDK1.8 逐步对JDK动态代理优化之后,在调用次数比较少的情况下,JDK代理效率高于CGlib代理效率,只有当进行大量调用的时候,JDK1.6 和JDK1.7 比CG礼包代理效率低一点,但是到JDK1.8的时候JDK代理效率高于CGLib代理。
-
动态代理和静态代理
动态代理和静态代理相比较,最大的好处是接口中什么的所有方法都被庄毅到调用处理器一个集中的方法中处理,这样子在接口方法数量比较多的时候我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口新增一个方法,静态代理模式除了实现需要实现的这个方法外,所有的代理类也需要实现此方法,增加了代码维护的复杂度,而动态代理不会出现该问题。
代理模式的优缺点
优点:
- 代理模式子啊客户端与目标对象之间起到一个中介和保护作用
- 代理对象可以扩展目标对象的功能
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。
缺点:
- 增加了系统的复杂度;
代理模式的使用场景
-
功能增强
当需要对一个对象的访问提供额外的操作时,可以使用代理模式
-
远程代理
实际上,RPC框架也可以看做一种代理模式,GoF的《设计模式》 一书中把它称作远程代理。通过远程代理,将网络通信,数据编解码等细节隐藏起来,客户端在使用RPC服务的时候,就像使用本地函数一样,无需了解服务器交互的细节,除此之外RPC服务的开发者也只是需要开发业务逻辑,就像本地开发函数一样,不需要关注跟客户端的交互细节
-
防火墙代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求给胡亮网;当互联网返回响应书,代理服务器在把它转给你的浏览器;
-
保护代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限
桥接模式
桥接模式介绍
桥接模式的定义是: 将抽象部分与它的实现分离,使他们可以独立的变化。
桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联来取代传统的多层继承,将类之间的静态类关系转变为动态的组合关系,是的关系更加的灵活,并易于扩展,有效的控制了系统中类的个数,(避免了继承层次的指数级爆炸)
少用继承,多用组合的方式
桥接模式就是还是用两个独立的维度,组合成一个完整的产品
桥接模式的原理
桥接就是将事物做了一个分类,分离,将实体和行为做了一个分离,达到动态绑定的效果
桥接模式的应用示例
模拟不同的支付工具对应不同的支付模式,比如微信和支付宝都可以完成支付操作,而支付操作又可以有扫码,密码支付,人脸支付,指纹,等那么关于支付方式有两个维度,包括,支付聚到和支付方式。
public class PayController {
/**
*
* @param uId 用户id
* @param tradeId 交易id
* @param amount 交易金额
* @param chanelType 交易聚到 1,支付宝,2,微信
* @param modeType 支付类型 1,密码。2人脸,3指纹
* @return
*/
public boolean doPay(String uId, String tradeId, BigDecimal amount,int chanelType,int modeType){
if (chanelType == 1) {
System.out.println("微信聚到支付开始。。。。。。。。。。");
if(modeType == 1){
System.out.println("微信密码支付");
} else if (modeType==2) {
System.out.println("微信人脸支付");
} else if (modeType ==3) {
System.out.println("微信指纹支付");
}
}else if (chanelType == 2) {
System.out.println("支付宝聚到支付开始。。。。。。。。。。");
if(modeType == 1){
System.out.println("支付宝密码支付");
} else if (modeType==2) {
System.out.println("支付宝人脸支付");
} else if (modeType ==3) {
System.out.println("支付宝指纹支付");
}
}
return true;
}
}
桥接模式重构代码
重构类图
桥接模式原理的核心是: 首先识别出一个类所具有连个独立话的维度,将他们设计为两个独立的继承等级结构,为两个维度都是提供抽象层,并建立抽象耦合
- Pay 抽象类
- 支付聚到子类: 微信支付
- 支付聚到子类:支付宝支付
- IPayMonde接口
- 支付模式实现:刷脸支付
- 支付模式实现:指纹支付
- 支付聚到*支付模式组合=相对应的组合;
public abstract class Pay {
// 桥接对象
protected IPayMode paymode;
public Pay(IPayMode paymode) {
this.paymode = paymode;
}
// 划账方法
public abstract String transfer(String uId,String tradeId,String amount);
}
/**
* 支付聚到-微信支付
*/
public class WxPay extends Pay{
public WxPay(IPayMode paymode) {
super(paymode);
}
@Override
public String transfer(String uId, String tradeId, String amount) {
System.out.println("微信聚到划账开始。。。");
//风控校验
boolean security = paymode.security(uId);
System.out.println("微信聚到风险校验" + uId + "," + tradeId + "," + security);
if (!security){
System.out.println("微信聚到划账失败!");
return "500";
}
System.out.println("微信聚到划账成功! 金额:" + amount);
return "200";
}
}
@Test
public void test02(){
System.out.println("测试: 微信支付,人脸支付");
WxPay wxPay = new WxPay(new PayFaceMode());
String wxOooo12 = wxPay.transfer("wx_oooo12", "101230123","1000" );
System.out.println("支付宝测试: 微信支付,人脸支付");
ZfbPay zfbPay = new ZfbPay(new PayFaceMode());
String zhifub = zfbPay.transfer("zhifu_oooo12", "101230123","1000" );
}
桥接模式就是使用 一个抽象类,然后子类实现,不同的功能,然后将不同子类组合完成一个业务
桥接模式优点
- 分离抽象接口及其实现部分,桥接模式使用对象间的关联关系,解耦抽象和实现之间固有的绑定关系,使得抽象和实现可以各自实现各自维度来变化
- 在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了单一原则职责,复用性差,类的个数多,桥接模式很好的解决了这些问题。
- 桥接模式提高了系统的扩展性,在两个变化维度中任意扩展一个维度都不需要改变原有系统,符合开闭原则
桥接模式缺点
- 桥接模还的使用会增加系统的理解和设计难度,由于关联建立在抽象层,要求开发者一开始就要对抽象层进行设计和编程
- 桥接模式要求正确识别出系统的两个独立变化的维度,因此具有一定的局限性,并且如果正确的进行维度的划分,也需要相当丰富的经验。
桥接模式使用场景
- 需要提供平台独立性的应用程序时,比如,不同数据库的JDBC驱动程序,硬盘驱动程序等
- 需要再某一种协议下增加更多组件时,比如:在支付宝场景中,我们期望微信支付,支付宝支付,各银行的支付等组件,这里的统一协议是收款,付款,扣款,而组件就是微信,支付宝等。
- 基于消息驱动的场景,虽然消息的行为比较单一,主要包括发送,接收处理和回执,但其实具体客户端的视线通常却各不相同,比如 手机短信,邮件消息,qq消息,微信消息等
- 拆分复杂的类对象,当一个类中包含大量的方法时,即不方便阅读,也不方便修改
- 希望从多个独立维度上扩展时,比如,系统功能性各非功能性角度,业务或技术角度等。
装饰器模式
装饰器介绍
装饰器模式的原始定义是: 动态的给一个对象添加额外的职责,就是扩展功能而言,装饰者提供了一种比使用子类更加灵活的代替方案。
假设现在有一块蛋糕,如果只在上面涂奶油就是普通的奶油蛋糕,这时如果我们添加上蓝莓,那就是蓝莓蛋糕,如果我们在那一块巧克力然后写上名字,插上代表年龄的蜡烛,这就是一个生日蛋糕;
在软件设计中,装饰者模式是一种用于代替继承的技术,它通过一种无需定义子类的方式给对象动态新增职责,使用对象之间的关联关系取代类之间的继承关系。
装饰器模式原理
/**
* 抽象构建类
*/
public abstract class Component {
// 抽象方法
public abstract void operation();
}
/**
* 具体的构建类
*/
public class ConcreteComponent extends Component {
@Override
public void operation() {
// 基础的实现,复杂功能通过装饰类实现
}
}
/**
* 抽象装饰者-装饰者模式核心
*/
public class Decorate extends Component{
/**
* 第1,维持一个对象构建对象的引用
*/
private Component component;
public Decorate(Component component) {
this.component = component;
}
@Override
public void operation() {
/**
* 在装饰者类中,原有的业务方法并没有 真正的装饰,而是提供一个统一的接口,将装饰的过程个子类完成
*
*/
component.operation();
}
}
/**
* 具体的装饰者类
*/
public class ConcreteDecorator extends Decorate{
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
add(); // 调用完原有的方法之后,调用新增的方法
/**
* 在里面对具体装饰,无果我想扩展
* 这样的话就对具体类进行了一个装饰
*/
}
public void add(){
}
}
读取文件示例:
// 抽象组件
interface Component {
void operate();
}
// 具体组件
class ConcreteComponent implements Component {
@Override
public void operate() {
System.out.println("具体组件的操作");
}
}
// 抽象装饰者
abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operate() {
component.operate();
}
}
// 具体装饰者A
class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operate() {
super.operate();
// 添加额外的功能
System.out.println("为具体组件添加的额外功能A");
}
}
// 具体装饰者B
class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operate() {
super.operate();
// 添加额外的功能
System.out.println("为具体组件添加的额外功能B");
}
}
public class DecoratorPatternDemo {
public static void main(String[] args) {
Component component = new ConcreteComponent();
component.operate();
System.out.println("--- 装饰后 ---");
Component decoratedA = new ConcreteDecoratorA(component);
decoratedA.operate();
System.out.println("--- 再装饰 ---");
Component decoratedAB = new ConcreteDecoratorB(decoratedA);
decoratedAB.operate();
}
}
装饰者模式总结
装饰者模式优点
- 对于扩展一个对象的功能,装饰者模式比继承更加灵活,不会导致类的个数急剧增加
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同具有装饰类,从而实现不同行为。
- 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合可以创造出很多不同的行为组合,得到更强大的对象。
- 具体构建类与具体装饰类可以独立化,用户可以根据需要增加新的具体构建类和具体装饰类,原有类库代码无变化,符合开闭原则。
装饰类缺点
- 在使用装饰者模式进行系统设计时产生很多小对象,这些对象的区别在于它们之间互相连接的方式有所不同,而不会是它们的类或者属性值不同,大量的小对象生成势必占用更对的系统资源,在一定程度上影响性能;
- 装饰者模式提供了一个种比继承更加灵活,机动的决绝方案,但是也意味着比继承更加易于出错,排错也更加困难,对于多次装饰的对象,在调试寻找错误时可能需要逐级排查,比较繁琐;
装饰者模式使用场景
- 快速动态扩展和撤销一个类的功能场景,比如,有的场景下对API接口的安全性要求很高,那么就可以使用装饰者模式对传输的字符串数据进行压缩或者加密,如果安全线要求不高则可以不使用;
- 不支持几次扩展的场景,比如,使用final关键字的类,或者系统存在大量通过继承的子类;
适配器模式
适配器模式的原始定义是: 将类的接口转换为客户期望的另一个接口,适配器可以让不兼容的两个类一起协同工作
例如: 如果去欧洲国家旅游的话,他们的插座是欧洲的标准,而我们使用的插头是亚洲的,因此我们的笔记本电脑,手机在当地不能充电,所以需要一个插座转换器,转换器的第1面插入当地的插座,第2面供我们充电,这样是的我们的插头在当地也能使用。生活中这样的例子很多手机充电,读卡器,等,其实就是使用到了适配器模式;
实现适配器有两种方式:
- 类适配器: 使用继承的关系实现
- 对象适配器: 使用组合的关系实现
适配器的原理
适配器模式应用示例
假设有一台电脑目前只能读取SD卡的信息,这时我们想使用电脑读取TF卡的内容,就需要将TF卡加上卡套,转换城SD卡! 创建一个读卡器,将TF卡中的内容读取出来;
public class Computer {
public String read(SDCard sdCard){
// 只能读取sdka
return sdCard.readSD();
}
}
public class SDCardImpl implements SDCard {
@Override
public String readSD() {
String msg ="sd card reading data..";
return msg;
}
@Override
public void writeSD(String msg) {
System.out.println("sd card write data");
}
}
public class TFCardImpl implements TFCard {
@Override
public String readTF() {
return "tf card reading data..";
}
@Override
public void writeTF(String msg) {
System.out.println("tf card write data");
}
}
/**
* 适配器类(SD兼容TF)
*/
public class SDAapterTF extends TFCardImpl implements SDCard {
@Override
public String readSD() {
System.out.println("adapter read tf card");
// 这里返回的时候就返回TF的方法
return readTF();
}
@Override
public void writeSD(String msg) {
System.out.println("adapter write tf card");
writeTF(msg);
}
}
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCardImpl sdCard = new SDCardImpl();
String read = computer.read(sdCard);
System.out.println(read);
System.out.println("==========================");
SDAapterTF sdAapterTF = new SDAapterTF();
String s = sdAapterTF.readSD();
System.out.println(s);
}
}
对象适配器模式
public class SDApaterTF2 implements SDCard {
private TFCard tfCard;
public SDApaterTF2(TFCard tfCard) {
this.tfCard = tfCard;
}
@Override
public String readSD() {
System.out.println("adapter read tf card");
return tfCard.readTF();
}
@Override
public void writeSD(String msg) {
System.out.println("adapter write tf card");
tfCard.writeTF(msg);
}
}
适配器模式的优点
- 将目标类和适配器类解耦,通过引入一个适配器类来重用现有的适配者类,无需修改原有结构,
- 增加了类的透明和复用性,将具体业务实现过程封装在适配者类中,对于客户端而言就是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中服用
- 灵活性和扩展性都非常好,通过使用适配文件,可以方便的适更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,符合开闭原则;
适配器模式缺点
- 对于java等不支持多重继承而言,一次最多只适配一个适配者类,不能同时适配多个适配者;
- 适配者类不能为最终类
- 对象适配器缺点,与类适配器模式相比较,在该模式下要适配器中更换适配者类的某些地方比较麻烦
适配器模式使用场景
- 统一多个类的接口设计时
- 某个功能的视依赖多个外部系统或者类,通过适配器模式,将他们的接口适配为统一的接口定义;
- 需要依赖外部系统时
- 当我们把项目中的依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式可以减少对代码的改动,
- 原有的接口无法修改时,或者原有的接口功能太老旧有需要兼容;
- JDK1.0 到iterator 的替换,适用适配器模式保留Enumeration类,并将实现替换为直接调用Itertor
- 适配不同数据格式时;
- Slf4j日志框架,定义打印日志的统一接口,提供针对不同日志的适配器
总结
代理模式,桥接模式,装饰者模式,适配器模式 4种设计模式的区别
代理,桥接,装饰者,适配器,这4种模式是比较常用的结构性设计模式,他们的代码结构非常相似,但其各自的用意却不同;
- 代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强访问功能,这是跟装饰器模式最大的不同。
- 桥接模式:桥接模式的目的是将接口和部分实现分离,从而他们可以较为容易也相对独立的可以改变。
- 装饰器模式,装饰器模式在不改变原有类接口的情况下,对原始类功能进行增强,并支持多个装饰器的嵌套使用。
- 适配器模式: 将一个类的接口装换位客户希望的另一个接口,适配器模式让那些不兼容的类可以一个工作;
外观模式
外观模式定义: 外观模式也叫门面模式,外观模式的原始定义是:为了子系统中的一组接口提供统一的接口,它定义了一个更高级别的接口,使子系统更易于使用。
外观模式,是一种通过多个复杂的子系统提供一个一致的接口,而这些子系统更加容易被访问的模式,该模式对外有一个统一的接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂性,提高了程序的可维护性
门面即墨市有点类似之前说的迪米特法则(最少知识原则)和接口隔离原则: 两个有交互的系统,只暴露有限的必要的接口
外观模式原理
代码示例
public class Facade {
SubSystemA systemA= new SubSystemA();
SubSystemB systemB= new SubSystemB();
SubSystemC systemC= new SubSystemC();
public void method(){
systemA.methodA();
systemB.methodB();
systemC.methodC();
}
}
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.method();
facade.systemA.methodA();
}
}
外观模式的优点
- 他对客户端屏蔽了子系统组件,减少了客户端需要处理的对象数目,并使子系统使用起来更加容易,通过引入外观模式,客户端的代码将变得简单,与关联的对象也很少。
- 它实现了子系统与客户端之间的松耦合关系,这是的子系统不会营销调用它的客户端,只需要调用外观模式类即可
- 一个子系统的修改对其它子系统没有任何影响,而子系统内部发生变化越不会影响到外观模式对象
外观模式却缺点
- 不鞥很好的控制客户端直接使用子系统类,如果客户端访问子系统,如果客户端访问子系统做太多的限制则减少了可变性的灵活性
- 如果谁不当,增加性的子系统可能需要修改外观模式类的源代码,违背开闭原则。
使用场景分析
外观模式大部分使用的是在架构设计层面,而不是代码层面;
- 简化复杂系统,比如,当我们开发一个套电商系统后(包括订单,商品,支付,会员等系统),我们不能让用户依次使用这些系统后才能完成商品的购买,而是需要一个门户网站或者手机APP 这样简化过的门面系统来体统在线的购物工功能。
- 减少客户端处理的系统数量,比如:在web应用中,系统与系统之间的调用可能需要处理database数据库,Model 业务等对象等,其中使用Database对象就需要打开数据库,关闭连接操作等,然后转换为Model、业务独享,是在太麻烦了 如果能创建一个数据库门面系统,那么实现以上过程就变得容易很多
- 让一个系统为多个系统工作,比如线程池ThreadPool 就是一个门面模式,它为系统提供了统一的线程对象的创建,销毁,使用等。
- 联合更多的系统来扩展原有系统,当我们的电商进系统中需要一些新功能时比如人脸识别,我们可以不需要自己研发,而是购买别家公司的系统来提供服务,这时通过门面系统就能方便快捷的进行扩展;
组合模式
我们很容易将 "组合模式"和"组合关系" 搞混。组合模式最初只是用于树形结构的场景,更多的是处理对象组织结构之间的问题,而组合关系是通过将不同对象封装取来完成一个统一的功能。
组合模式介绍
组合模式的定义是: 将对象组合成树形结构表示整个部分的层次结构,组合模式可以让用户统一对待单个对象和对象的组合
比如windoes 操作系统重的目录结构,其实就是树形目录结构,通过tree 命令实现树形结构展示。
组合模式结构图
/**
* 抽象根节点角色
* 对客户端而言,只需要抽象变成,无需关心子类是树枝节点还是叶子节点
*/
public abstract class Component {
public abstract void add(Component component); // 新增节点
public abstract void remove(Component component); // 删除节点
public abstract Component getChild(int i); // 获取节点
public abstract void operation(); // 业务方法
}
/**
* 树枝节点
* 树枝节点类也是一个容器对象,它既可以包含树枝节点,也可以好汉叶子节点
*/
public class Composite extends Component{
// 定义集合属性 保存子节点的数据
private ArrayList<Component> list=new ArrayList<>();
@Override
public void add(Component component) {
list.add(component);
}
@Override
public void remove(Component component) {
list.remove(component);
}
@Override
public Component getChild(int i) {
return list.get(i);
}
/**
* 具体业务方法
*/
@Override
public void operation() {
//递归方式调用 其他节点中的operation()
for (Component component : list) {
component.operation();
}
}
}
/**
* 叶子节点
* 叶子节点不能包含子节点
*/
public class Leaf extends Component{
@Override
public void add(Component component) {
}
@Override
public void remove(Component component) {
}
@Override
public Component getChild(int i) {
return null;
}
/**
* 叶子节点的业务方法
*/
@Override
public void operation() {
}
}
@AllArgsConstructor
public class Dircetory extends Entry{
private String name; //文件名
// 文件夹和文件的集合
private ArrayList<Entry> dircetory= new ArrayList<>();
public Dircetory(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name ;
}
/**
* 获取文件大小方法
* 1.如果entry 对象是file类型,则调用getSize 方法获取文件大小
* 2,如果entry 对象是Dircetory 类型,会继续调用子文件夹的getSize() 方法,形成递归调用;
*
* @return
*/
@Override
public int getSize() {
int size=0;
//遍历获取文件大小
for (Entry entry : dircetory) {
size +=entry.getSize();
}
return size;
}
@Override
public Entry add(Entry entry) {
dircetory.add(entry);
return this;
}
@Override
public void printList(String prefix) {
System.out.println(prefix+"/" + this);
for (Entry entry : dircetory) {
entry.printList(prefix+"/"+name);
}
}
}
/**
* Entry 抽象类 (文件和文件夹)
*/
@ToString
public abstract class Entry {
public abstract String getName(); // 获取文件名
public abstract int getSize(); // 获取文件大小
//添加文件或文件夹方法
public abstract Entry add(Entry entry);
// 显示指定目录下的所有文件
public abstract void printList(String prefix);
}
/**
* 表示文件
*/
public class File extends Entry{
private String name; //文件名
private int size; // 文件大小
public File() {
}
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return size;
}
@Override
public Entry add(Entry entry) {
return null;
}
@Override
public void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
}
public class Client {
public static void main(String[] args) {
Dircetory root = new Dircetory("root");
Dircetory bin = new Dircetory("bin");
// 向bin目录添加叶子节点
bin.add(new File("vi",10000));
bin.add(new File("test",20000));
Dircetory tmpDir = new Dircetory("tmp");
Dircetory usrDir = new Dircetory("usr");
Dircetory mysqlDir = new Dircetory("mysql");
mysqlDir.add(new File("my.conf",30));
mysqlDir.add(new File("test.db",2230));
usrDir.add(mysqlDir);
// 将所有的子文件夹封装到根节点
root.add(bin);
root.add(tmpDir);
root.add(usrDir);
root.printList("");
}
}
组合模式总结
透明模式
透明模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比例生命了 Component生命了add() remove
,getchild 方法,这样做的好处是确保所有的构建类都是相同的接口,透明组合模式也是组合模式的标准形式。
透明组合模式的缺点是不够安全,因为叶子对象和容器对象本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此提供了add() remove()等方法是没有意义的,这样在编译节点不会报错,但是在运行节点如果调用这些方法会报错。(需要提供相应报错的处理)
安全组合模式,
在安全组合模式中,在抽象构建角色中没有声明任何用于管理成员的方法,而是在树枝节点类中声明了实现这些方法,安全模式组合模式的缺点是,不够透明,因为叶子节点构建容器具有不同的方法,且容器构建中那些用于管理成员对象的方法没有在抽象类中定义,因此客户端不能完全针对抽象层编程,必须有区别的对待叶子构建和容器构建。
组合模式优点
- 组合模式可以清楚的定义层次的复杂对象,表示对象的全部或者部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
- 客户端可以一致的使用一个组合结构或者其中单个对象,不必关心处理对象是单个对象还是整个组合结构,简化了客户端的代码
- 在组合模式中增加了新的树枝节点和叶子节点都很方便,无需对现有类库进行修改,符合"开闭原则"
- 组合模式为树形机构的面向对象变成提供了一种灵活的解决方案,通过叶子节点和树枝节点递归组合,可以形成复杂的树枝型结构,但对于树形结构的控制却非常简单。
组合模式的缺点
使用组合模式的前提在于,你的业务场景必须能够表示成树形结构,所有,组合模式的应用场景也比较的局限,它并不是一种很常见的设计模式。
组合模式的使用场景
- 处理一个树形结构,比如公司,人员组织架构,订单信息等;
- 跨越多个层次结构聚合数据,比如,统计文件夹下文件总数;
- 统一处理一个结构中的多个对象,比如,遍历文件夹下所有XML文件类型内容;
组合模式就是为需要反复计算或者反复统计的场景而设计的;
享元模式
享元模式的原始定义是: 摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,
从而让我们能够在有限的内存容量中载入更多的对象。
从定义中你可以发现,享元模式想要解决的核心问题就是节约内存空间,使用的办法就是找出相似对象之间共有的特征,然后复用这些特征,所有的"享元"顾名思义就是被共享的单元。
比如:一个文本字符串中存在大量重复的字符,如果每个字符都用一个单独的对象表示,将会被占用较多的内存空间,我们可以使用享元模式解决这一类问题。
享元模式通过共享技术实现相同或者相似对象的重复使用,在逻辑上每一个出现的字符都哟一个对象与之对应,然而在物理上他们确实共享同一个享元对象。
其中string 类 就是使用的享元池;
享元模式的机构图
享元模式中的两种状态:
- 内部状态: 不会随着环境的变化而改变的可共享部分
- 外部状态: 指的是随着环境的变化而改变的不可共享的部分
在实际开发中我们会使用工厂模式和享元模式一起搭配使用
享元模式的结构图
/**
* 可共享-具体享元类
* 具体的享元类中,需要将内部状态和外部状态分开处理
*/
public class ConcreateFlyweight extends Flyweight {
/**
* 内部状态
* inState 作为一个成员变量,同一个享元对象的内部状态是一致的
*/
private String inState;
public ConcreateFlyweight(String inState) {
this.inState = inState;
}
/**
* 外部状态 在使用的时,通常是外部设置,不保存在享元对象中,即使是同一个对象
* @param state
*/
@Override
public void operation(String state) {
System.out.println("享元对象的内部状态:" + inState + ",外部状态:" + state);
}
}
public abstract class Flyweight {
public abstract void operation(String state);
}
/**
*享元工厂类
* 作用:作为存储对象的享元池,用户获取享元对象时,先从享元池中获取,有就获取,没有就创建新的给用户并且在享元池中保存
*
*
*/
public class FlyweightFactory {
/**
*
* 定义map 集合用于存储享元对象,实现享元池
*/
private Map<String ,Flyweight> pool=new HashMap<>();
// 实现享元对象之间的床底
public FlyweightFactory() {
// 添加对应的内部状态
pool.put("A",new ConcreateFlyweight("A"));
pool.put("B",new ConcreateFlyweight("B"));
pool.put("C",new ConcreateFlyweight("C"));
// 根据内部状态查找
}
public Flyweight getFlyweight(String key){
// 判断对象是否存在
if(pool.containsKey(key)){
System.out.println(" 享元池中存在,直接使用key" + key);
}else {
// 如果对象不存在,就创建一个添加到享元池,然后返回
System.out.println("==== 享元池中不存在,创建并复用,key" + key);
ConcreateFlyweight concreateFlyweight = new ConcreateFlyweight(key);
pool.put(key,concreateFlyweight);
}
return pool.get(key);
}
}
/**
* 非共享类 的具体相应类
*
*/
public class UnsharedFlyweight extends Flyweight {
private String inState;
public UnsharedFlyweight(String inState) {
this.inState = inState;
}
/**
*
* @param state
*/
@Override
public void operation(String state) {
System.out.println("使用不共享对象的内部状态:" + inState + ",外部状态:" + state);
}
}
public class Client {
public static void main(String[] args) {
// 获取工厂对象
FlyweightFactory flyweightFactory = new FlyweightFactory();
// 通过工厂对象获取共享享元对象
Flyweight a1 = flyweightFactory.getFlyweight("A");
a1.operation("a1ExState");
Flyweight a2 = flyweightFactory.getFlyweight("A");
a2.operation("a2ExState");
System.out.println(a1==a2);
// 非共享的享元对象;
UnsharedFlyweight u1 = new UnsharedFlyweight("A");
UnsharedFlyweight u2 = new UnsharedFlyweight("A");
System.out.println(u1==u2);
}
}
享元模式应用实例
五子棋中有大量的黑子和白子 ,他们的形状大小都是一样的,只是出现的位置不同,所以一个棋子作为一个独立的对象存储在内存中,会导致大量的内存浪费,我们使用享元模式来优化。
享元模式总结
享元模式优点
极大减少内存中像是或者相同对象数量,节约系统资源,提高系统性能
比如: 大量商家的商品图片,固定文字,(如商品介绍,商品属性) 在不同的网页中进行展示时,通常不需要重复 创建对象,而是可以使用同一个对象,以避免重复存储而浪费空间,由于通过享元模式构建的对象是共享的,所以当程序在运行时不仅不会重复创建,还能减少程序与操作系统的IO交互次数,大大提升了读写性能。
- 享元模式中的外部状态相对独立,而且不影响内部状态
享元模式缺点
为了使对选哪个可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
使用场景
-
一个系统有大量相同或者相似对象,造成内存的大量消耗。
注意: 在使用享元模式是需要维护一个享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象是才值得使用享元模式。
-
在java中,享元模式一个常用的场景就是,使用数据类的包装类对象的valueOf() 方法,比如,使用Integer.valueOf() 方法时,实际的代码中有一个叫IntegerCache的静态类,它就是一直缓存了-127到128 范围内的数值,如下代码
行为型模式(11种)
行为型模式描述程序在运行时复杂的流程控制,即描述多个类或者对象之间相互协作完成单个对象都无法独立完成的任务,它设计算法与水箱间职责分配。
行为型模式为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为,由于组合关系或聚合关系都比继承关系耦合度低,满足"合成复用原则",所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式分为
- 观察者模式
- 模版方法模式
- 策略模式
- 责任链模式
- 状态模式
- 命令模式
- 中介者模式
- 迭代器模式
- 访问者模式
- 备忘录模式
- 解释器模式
以上11种都是行为型模式,除了模版方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。
过程小结:之前说的创建型模式,他主要是来解决创建问题,来取封装对象的复杂创建,将对象的使用和创建,进行解耦。
结构型模式:只要总结了一些类和对象组合在一起的一些经典结构,通过这些经典结构可以解决对应场景特定的问题。
观察者模式
观察者原始定义: 定义对象之间的依赖关系,这样的一个对象改变时,他所有的依赖项都会自动的得到更新和通知;
观察者模式,是建立对象和对象之家的一种依赖关系;
一个对象发生改变的时候,会相应的通知依赖的对象,然后依赖的对象会做出相应的处理。
发生改变的对象: 我们称为观察目标;
被通知的目标称为观察者
,其中观察者可以为多个,并且观察者之间没有任何联系;可以根据需求增加多个观察者;增加或者删除这个观察者,使得我们的系统得到容易得扩展。
观察者模式的别名: 称为发布-订阅模式,模型-视图模式,源-监听模式等
其中有连个角色
被观察者:
观察者:
观察者模式架构图
attch 方法,新增一个观察者
detach 减少一个观察者
notify 通知 所有的观察者
abservers 保存所有观察者对象
观察者模式总结
观察者模式的优点
- 降低了目标域观察者之间的耦合关系,两者之间是抽象耦合的关系。
- 被观察者发送通知,所有注册的观察者都会受到信息【可以实现广播机制】
观察着模式的缺点
- 如果观察者非常多的话,那么所有的观察者受到被观察者发送的通知会耗时。
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃。
观察者模式常见的使用场景
- 当一个对象发生改变的时候需要改变其他对象时,比如商品库存数量发生变化时,需要通知商品详情页,购物车等系统改变数量。
- 一个对象发生改变时,想要发送通知,而不是需要知道接受者是谁,比如,订阅微信公众号文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。
- 需要创建一种链式触发机制时,比如,在系统创建中触发链,A对象的行为影响B对象,B对象的行为影响C对象... 这样通过观察者模式更够良好的实现。
- 微博或微信朋友圈发送的场景,这是观察者模式的典型应用场景,一个人发微博或者朋友圈,只要是关联的朋友都会受到通知,一旦取消关注,此人以后将不会受到相关通知。
- 需要建立基于事件触发的场景,比如,基于java UI 的变成,所有键盘和鼠标事件都由它的监听器对象和指定函数处理。 当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它。
模版方法模式
模版方法模式原始定义是:在操作中定义算法的框架,将一些步骤推迟到子类中,模版方法让子类在不改变算法结构的情况下 重新定义算法的某些步骤 。
例如:我们去医院看病一般要经过一下4个步骤流程:挂号,区号,排队,医生问诊等,其中挂号,取号,排队对每个病人是一样的,现在父类实现,三十具体医生根据并且开药给每个人是不一样的,所有开药这个操作可以延迟到子类中实现。
模版方法模式是一种基于继承的代码复用技术,他是一种类行为模式,模版方法模式其结构中只存在与父类之间的继承关系,模版方法的作用主要是提高程序的复用性和扩展性。
- 复用的是,所有的子类可以服用父类中提供的模版方法代码。
- 扩展指的是框架通过模版方法模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制抓框架的功能。
模版方法模式结构图
/**
* 抽象父类
* 相当于是一个骨架,有的是实现了有的是抽象的未实现
*/
public abstract class AbstractClassTemplate {
void stpe1(String key){
System.out.println("在模版方法中-》执行步骤1");
if (stpe2(key)) {
stpe3(key);
}else {
stpe4(key);
}
stpe5(key);
}
boolean stpe2(String key){
System.out.println("在模版方法中-》执行步骤2");
if ("x".equals(key)) {
return true;
}
return false;
}
abstract void stpe3(String key);
abstract void stpe4(String key);
private void stpe5(String key) {
System.out.println("在模版方法中-》执行步骤5");
}
void run(String key){
stpe1( key);
}
}
public class ConceteClassA extends AbstractClassTemplate{
@Override
void stpe3(String key) {
System.out.println("子类A中执行步骤3");
}
@Override
void stpe4(String key) {
System.out.println("子类A中执行步骤4");
}
}
public class ConceteClassB extends AbstractClassTemplate{
@Override
void stpe3(String key) {
System.out.println("子类B中执行步骤3");
}
@Override
void stpe4(String key) {
System.out.println("子类B中执行步骤4");
}
}
public class Testo1 {
public static void main(String[] args) {
ConceteClassA conceteClassA = new ConceteClassA();
ConceteClassB conceteClassB = new ConceteClassB();
conceteClassA.run("x");
conceteClassB.run("");
}
}
模版方法总结
优点:
- 在父类中形式化的定义一个算法,而由他的子类来实现处理细节,在子类实现详细的处理代码时,并不会改变父类计算中步骤的执行顺序,
- 模版方法可以实现一种方向控制结构,通过子类覆盖父类的钩子方法来决定某个特定步骤是否需要执行,
- 在模版方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和新增加新的子类很方便,符合单一职责和开闭原则。
缺点:
- 对每个不同的视线都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种方向的控制结构,它提高了代码阅读的难度。
模版方法模式的使用场景
- 多个类有相同的方法并且逻辑可以共用时;
- 将通过计算的算法或固定流程设计为模版,在每一个具体的子类中再继续优化算法步骤或流程时;
- 重构超长代码时,发现某一个经常使用的公有方法。
策略模式
策略模式概述
策略模式的原始定义是: 定义一些列算法,将每一个算法封装起来,并使他们可以相互替换,策略模式让算法可以独立于使用它的客户端而变化
每一种交通工具就是一个策略,每种策略完成的目的是一样的,就是把我们送到目的地。
在软甲开发中,经常会遇到这种情况,开发一个功能,可以通过多个算法去实现,我们可以将所有的算法集中在一个类中,在一个类中提多个方法,每个方法对应一个算法,或者我们也可以将这些算法都封装在一个统一的方法中,使用if...else... 等条件判断语句进行选择,但是这两种方式都存在硬编码的问题,后期需要增加算法就需要修改代码。这会导致代码的维护变得困难。
在软件开发中也会遇到相似的情况,当实现某一个功能存在多种算法或者策略,我们就可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。
策略模式的原理
策略模式实现
/**
* 抽象策略类
*/
public interface Strategy {
void algorithm();
}
/**
* 上下文类: 策略模式的本质就是通过Context 类作为控制单元,对不同的策略进行调度分类
* 重要的一个类
*/
public class Context {
/**
* 维持抽象策略的引用
*/
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
//调用策略类中的方案
public void algorithm(){
strategy.algorithm();
}
}
/**
* 具体策略A
*/
public class ConcreteStrategyA implements Strategy{
@Override
public void algorithm() {
System.out.println("执行策略A");
}
}
/**
* 具体策略B
*/
public class ConcreteStrategyB implements Strategy{
@Override
public void algorithm() {
System.out.println("执行策略B");
}
}
public class CLient {
public static void main(String[] args) {
Strategy strategyA = new ConcreteStrategyA();
Context context = new Context(strategyA);
context.algorithm();
Strategy strategyB = new ConcreteStrategyB();
Context contextB = new Context(strategyB);
contextB.algorithm();
}
}
策略模式的应用实例
面试问题:如何使用设计模式消除代码中的if-else
物流行业中,通常涉及到EDI报文(XML格式文件)传输和回执接收,每发送一份EDI报文,后续都会受到与之关联的回执(标识该数据在第三方系统中的流转状态)。
这里列举几种回执类型:TM1101,TM2101,TM4101,MT8104,系统在收到不同的回执报文后,会执行对应的业务逻辑处理,我们就回执处理为演示案例。
不使用设计模式
public class Client {
public static void main(String[] args) {
List<Receipt> receipt = ReceiptBuild.getReceipt();
// TM1101,TM2101,TM4101,MT8104,
for (Receipt receipt1 : receipt) {
if("TM1101".equals(receipt1.getType())){
System.out.println("接收到TM1101的回执信息");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑A");
}else if("TM2101".equals(receipt1.getType())){
System.out.println("接收到TM2101的回执信息");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑B");
}else if("TM4101".equals(receipt1.getType())){
System.out.println("接收到TM4101的回执信息");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑C");
}else if("MT8104".equals(receipt1.getType())){
System.out.println("接收到MT8104的回执信息");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑D");
}
}
}
}
策略模式总结
策略模式优点:
-
策略类之间可以自由区切换
由于策略类都实现同一个接口,是他们之间可以自由切换
-
易于扩展
增加一个新的策略只需要添加一个工具体的策略类既可,基本不需要修改原有的代码,符合开闭原则
-
避免使用多重条件语句(if else),充分体现面向对象思想。
策略模式缺点,
- 客户端必选知道所有的策略类,并自行使用哪个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
策略模式的使用场景
- 一个系统需要动态的在几种算法选择一种时,可将每一个算法封装到策略类中。
策略模式最大的作用在于分离使用算法的逻辑和算法自身实现的逻辑,这样就意味着我们想要优化算法自身的逻辑实现就变得非常便捷,一方面可以采用最新的算法实现逻辑,另一方面可以直接弃用 就算发而采用新算法,使用策略模式能够很方便的进行替换。
-
一个类定义了很多种行为,并且这些行为在一个类的操作中以条件语句的形成出现,可将每个条件分支已入他们各自的策略类中代替这些条件语句。
在实际开发中没有许多可以实现某一功能,如查找,排序等,通过if else 等条件判断语句来进行选择非常方便,但是这样带来一个问题,当在这个算法中封装了大量查找算法时,该类的代码就会变得给常的复杂,维护也会突然扁的非常困难,虽然策略模式看上去比较笨重,但实际上在每一次新增策略时都通过新增类来进行隔离,短期虽然不如直接写上 if-else 来的效率高,但长期看来,维护单一的简单类消耗的时间其实远远低于维护一个超大的复杂类。
-
系统要求使用算法的客户应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
如果我们不希望客户知道复杂的,与算法相关的数据结构,在具体的策略类中封装算法与相关的数据结构,可以提高算法的保密性和安全性。
设计原则和思想其实比设计模式更加的普适和重要,掌握了代码的设计原则和思想,我们自然而然的就可以使用到设计模式 ,还有可能创建出一种新的设计模式。
职责链模式
职责链模式介绍
职责链模式定义: 避免一个请求的发送者和接受者耦合在一起,让多个对象都有机会处理请求机会,将接请求的对象连接成一条链,并且沿着这条链传递请求,知道有一个对象能够处理它为止。
在职责链模式中,多个处理器(也就是杠杆定义的"接收对象")依次处理同一个请求,一个请求先经过A处理,然后再把请求传递给B处理,B处理完后再传递给C处理,依次类推,形成一个链条,链条上的每个处理器各自承担者各自的职责,所以叫职责链模式。
具体代码实现
/**
* 抽象的处理者类
*/
public abstract class Handler {
// 后继处理者的引用
protected Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public abstract void handler(RequestData requestData);
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RequestData {
private String data;
}
public class HandlerA extends Handler{
@Override
public void handler(RequestData requestData) {
System.out.println("HandleA 执行代码逻辑! 处理:" + requestData.getData());
requestData.setData(requestData.getData().replace("A",""));
//判断是否向后继续处理器
if(successor !=null){
successor.handler(requestData);
}else {
System.out.println("执行终止");
}
}
}
public class HandlerB extends Handler{
@Override
public void handler(RequestData requestData) {
System.out.println("HandleB 执行代码逻辑! 处理:" + requestData.getData());
requestData.setData(requestData.getData().replace("B",""));
//判断是否向后继续处理器
if(successor !=null){
successor.handler(requestData);
}else {
System.out.println("执行终止");
}
}
}
public class HandlerC extends Handler{
@Override
public void handler(RequestData requestData) {
System.out.println("HandleC执行代码逻辑! 处理:" + requestData.getData());
requestData.setData(requestData.getData().replace("C",""));
//判断是否向后继续处理器
if(successor !=null){
successor.handler(requestData);
}else {
System.out.println("执行终止");
}
}
}
public class Client {
public static void main(String[] args) {
HandlerA h1 = new HandlerA();
HandlerB h2 = new HandlerB();
HandlerC h3 = new HandlerC();
// 创建处理链
h1.setSuccessor(h2);
h2.setSuccessor(h3);
RequestData requestData = new RequestData("请求数据:ABCD");
// 调用处理链头部的方法
h1.handler(requestData);
}
}
职责链模式的应用实例
模拟双11,业务系统审批流程,临近双十一公司会陆续有一些新的需求上线,为了保证上线系统的稳定性,我们对上线的审批流程做 严格的控制,审批的过程中会有不同级别的负责人进行审批(平常系统上线只需要三级负责人审批即可,双十一前后需要二级或者一级审核人员参与审批,接下来我们就使用职责链模式来设计该功能)
职责链模式总结
- 职责链模式的有点:
- 降低了对象之间的耦合度,该模式降低了请求发送者和接受者的耦合度。
- 增强了系统的可扩展性,可以根据需要增加请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性,当工作流发生变化,可以动态的改变链内的成员或者修改它们的次序,也可以动态新增或者删除责任。
- 责任链简化了对象之间的连接,一个对象需要保持一个指向后继者的引用,不需要保持其他所有处理者的引用,这避免了使用众多的if或者if...else 语句
- 责任分担,每个类只需要处理自己的工作,不能处理的传递给下一个对象完成,明确各类的职责范围,符合类的单一职责原则。
-
责任链模式的缺点:
- 不能保证每个请求一定被处理,由于一个请求没有明确的接受者,所以不能保证他一定会被处理,该请求可能一直传不到末端得不到处理。
- 对比较长的职责链,请求的处理涉及多个处理对象,系统性能将会收到一定的影响。
- 职责链建立的合理性需要靠客户端来保证,增加了客户端的复杂性,可能会犹豫职责链的错误设置而导致系统的错误,如额能会造城循环调用。
-
使用场景分析
责任链模式常见的一下几种使用场景。
-
在运行时需要动态的多个关联对象来处理同一次请求时,比如,请假流程,员工入职流程,编译打包上线发布流程等。
-
不想让使用者知道具体处理逻辑是,比如做权限校验的登录拦截。
-
需要动态更换处理对象时,比如工单处理系统,网管API过滤规则系统等。
-
职责链模式常用被宰框架中开发,用来实现框架的过滤器,拦截器功能,让框架的使用者在不修改源码的情况下,添加新的过滤拦截功能。
-
状态模式
状态模式介绍
自然界中很多事物都有多种状态,而且不同状态下会具有不同的行为,这种状态在特定的条件下会发生相互转换,比如水
软件系统中,有些对象也像水一样,具有多种状态,这些状态在某些情况下能够相互装换,而且对象在不同的状态下也将具有不同的行为。
状态模式的定义: **允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。**状态模式就是用于解决系统中复杂对象的状态转换及不同状态下行为的封装问题,状态模式将一个对象的状态从改对象中分离出来,封装到专门的状态中类中(用类来表示状态),使用对象状态可以灵活变化。
状态模式结构
具体代码实现
/**
* 抽象状态接口
*/
public interface State {
//声明一个抽闲方法,不同具体的状态可以有不同的实现
void handle(Context context);
}
@Data
@ToString
@AllArgsConstructor
public class Context {
private State currentState;
public Context() {
this.currentState=null;
}
}
public class ConcreteStateA implements State {
@Override
public void handle(Context context) {
System.out.println("进入到状态模式A.....");
context.setCurrentState(this);
}
@Override
public String toString() {
return "当前状态: ConcreteStateA";
}
}
public class ConcreteStateB implements State {
@Override
public void handle(Context context) {
System.out.println("进入到状态模式B.....");
context.setCurrentState(this);
}
@Override
public String toString() {
return "当前状态: ConcreteStateB";
}
}
public class Client {
public static void main(String[] args) {
Context context = new Context();
State state1= new ConcreteStateA();
state1.handle(context);
System.out.println(context.getCurrentState().toString());
System.out.println("=====================================");
State state2= new ConcreteStateB();
state2.handle(context);
System.out.println(context.getCurrentState().toString());
}
}
状态模式应用实例
模拟交通信号灯的状态变换,交通信号灯一般包括了红,黄,绿,三种颜色状态,不同状态之间的切换逻辑为,红灯只能雀环为黄灯,黄灯可以切换为绿灯或者红灯,绿灯只能切换为黄灯。
不使用状态模式
/**
* 交通灯类
*
*/
public class TrafficLitgh {
//初始状态
private String state ="红色";
// 切换为绿灯 ,通信状态
public void switchToGreen(){
if("绿".equals(state)){
System.out.println("当前为绿灯,无需切换!");
} else if ("红".equals(state)){
System.out.println("红灯不能切换为绿灯");
}else if("黄".equals(state)){
state="绿";
System.out.println("绿灯亮起时长 。。 60s");
}
}
// 切换为黄灯 ,通信状态
public void switchToYrllow(){
if("黄".equals(state)){
System.out.println("当前为黄灯,无需切换!");
} else if ("红".equals(state)||"绿".equals(state)){
state="黄";
System.out.println("黄灯亮起时长 。。 5s");
}
}
// 切换为红灯 ,通信状态
public void switchToRed(){
if("红".equals(state)){
System.out.println("当前为红灯,无需切换!");
} else if ("绿".equals(state)){
System.out.println("绿灯不能切换为绿红灯");
}else if("黄".equals(state)){
state="红";
System.out.println("红灯亮起时长 。。 60s");
}
}
}
使用设计模式重构
/**
* 交通灯状态接口
* 需要有切换状态的方法
*/
public interface State {
/**
* 切换绿灯
*/
void switchToGreen(TrafficLitgh trafficLitgh);
/**
* 切换黄灯
*/
void switchToYellow(TrafficLitgh trafficLitgh);
/**
* 切换红灯
*/
void switchToRed(TrafficLitgh trafficLitgh);
}
/**
* 交通灯类
* 相当于上下文类,维持状态
*/
public class TrafficLitgh {
/**
* 初始化-红灯
*/
State state=new ReadState();
public void setState(State state) {
this.state = state;
}
public void switchToGreen(){
state=new YellowState();
state.switchToGreen(this);
}
// 切换为黄灯 ,通信状态
public void switchToYrllow(){
state.switchToYellow(this);
}
// 切换为红灯 ,通信状态
public void switchToRed() {
state=new YellowState();
state.switchToRed(this);
}
public class GreenState implements State
{
@Override
public void switchToGreen(TrafficLitgh trafficLitgh) {
System.out.println("当前绿灯无需切换");
}
@Override
public void switchToYellow(TrafficLitgh trafficLitgh) {
System.out.println("黄灯亮起。。时长 5秒");
}
@Override
public void switchToRed(TrafficLitgh trafficLitgh) {
System.out.println("绿灯不能切换为红灯");
}
}
public class ReadState implements State
{
@Override
public void switchToGreen(TrafficLitgh trafficLitgh) {
System.out.println("红灯不能切换绿灯");
}
@Override
public void switchToYellow(TrafficLitgh trafficLitgh) {
System.out.println("黄灯亮起。。时长 5秒");
}
@Override
public void switchToRed(TrafficLitgh trafficLitgh) {
System.out.println("当前为红灯无需切换");
}
}
public class YellowState implements State
{
@Override
public void switchToGreen(TrafficLitgh trafficLitgh) {
System.out.println("绿灯亮起! 时长60秒");
}
@Override
public void switchToYellow(TrafficLitgh trafficLitgh) {
System.out.println("当前是黄灯无需切换");
}
@Override
public void switchToRed(TrafficLitgh trafficLitgh) {
System.out.println("红灯亮起,时长90秒");
}
}
public class Client {
public static void main(String[] args) {
TrafficLitgh trafficLitgh = new TrafficLitgh();
trafficLitgh.switchToRed();
trafficLitgh.switchToGreen();
trafficLitgh.switchToYrllow();
trafficLitgh.switchToGreen();
}
}
状态模式总结
- 状态模式的优点
- 就爱那个所有与某个状态有关的行为放大一个类中,并且可以方便的新增状态,只需要改变对象状态即可改变状态行为
- 允许状态转换逻辑与状态合成一体,而不是某一个巨大的条件语句。
2.状态模式的缺点
- 状态模式的使用必然增加系统类和对象的个数
- 状态模式的结构域实现较为复杂,如果使用不当将会导致程序结构和代码的混乱
- 状态模式对开闭原则的支持并不太好,添加新的状态需要修改那些负责状态转换的源代码
3.状态模式常见的使用场景
- 对象根据自身状态变化进行不同行为的操作时,比如购物车订单状态。
- 对象需要根据自身变量修改当前值改变行为,不期望使用大量的if -else 语句时,比如商品库存状态。
- 杜宇某些确定的状态和行为,不想使用大量重复代码时,比如,某一个会员当天的购物浏览记录。
迭代器模式
迭代器模式介绍
迭代器模式是我们学习一个设计时很少用到的,但编码实现却经常使用到的行为设计模式,在绝大多数编程语言中,迭代器已经成为一个基础的类库,直接使用来遍历集合对象,在平时开发中,我们更多的是直接使用它,很少会从零去实现一个迭代器。
迭代器模式又称游标模式,他的原始定义是 : 迭代器提供一种对容器对象中的个各元素进行访问的方法,而又不需要暴漏该对象的内部细节。
在软件系统中,容器对象拥有两个职责:一是存储数据,二是遍历数据,从依赖性上看,前者是聚合对象的基本职责,而后者是可变化的,又是可分离的,因此可以将便利数据的行从容器中抽取出来,封装到迭代器对象中,由迭代器爱提供遍历数据的行为这将简化聚合对象的设计,更加符合单一原则。
迭代器原理
迭代器模式的实现
public class ConcreIterator<E> implements Itreator<E>{
/**
* 游标
*/
private int cursor;
/**
* 容器
*/
private ArrayList<E> arrayList;
public ConcreIterator(ArrayList<E> arrayList) {
this.cursor=0;
this.arrayList = arrayList;
}
@Override
public boolean hasNext() {
return cursor !=arrayList.size();
}
@Override
public void next() {
cursor++;
}
@Override
public E currentItem() {
if(cursor>=arrayList.size()){
throw new NoSuchElementException();
}
return arrayList.get(cursor);
}
}
/**
* 迭代器接口
*/
public interface Itreator<E> {
/**
* 判断集合中是否有下一个
*/
boolean hasNext();
/**
* 将游标后移一位
*/
void next();
/**
* 返回当前游标指定的元素
*/
E currentItem();
}
public class Test01 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("zahngsan");
names.add("lisi");
names.add("wangwu");
Itreator<String> namesIterator = new ConcreIterator<>(names);
while (namesIterator.hasNext()){
System.out.println(namesIterator.currentItem());
namesIterator.next();
}
System.out.println("=========================================================");
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
迭代器模式总结
1.迭代器模式的优点:
- 迭代器模式支持不同方式遍历一个集合对象上定义多种遍历方式,在迭代器模式中,只需要用一个不同的迭代器来替换原有的迭代器,即可改变遍历算法,也可以自己定义迭代器子类支持性的遍历方式。
- 迭代器简化了集合类。由于引入了迭代器,在原有的集合对象中不需要自行提供遍历等算法,这样可以简化集合类的设计。
- 在迭代器模式中,由于引入了抽象层,增加新的集合和迭代器类都很方便,无需修改源代码,满足"基于接口编程而非实现"和"开闭原则"的要求。
2.迭代器的缺点:
- 由于迭代器模式将存储数据和遍历数据的职责分离,增加了类的个数,这在一定程度上增加了系统的复杂性。
- 抽象迭代器的设计难度较大,充分考虑到系统将来的扩展。
使用场景
- 减少程序中重复的遍历代码。
对于引入一个集合容器中的多个对象来说,访问必然涉及遍历算法,如果我们不将遍历算法封装到容器里(比如,List,Set,Map)那么就需要还是用容器的人自行去实现遍历算法,这样容易造成很多重复循环和条件判断的语句出现,不利于代码的复用和扩展,同时还会暴露不同容器的内部结构,而是用迭代器模式将便利算法作为容器对象自身的一种属性方法,来使用,能够有效的避免写很多重复代码,同时又不会暴漏内部结构。
- 当需要为遍历不同的集合提供一个统一的接口或者访问一个集合的内筒而无需暴漏其内部细节的表示时。
迭代器模式把不同集合类的访问逻辑抽象出来,这样在不用暴露集合内部的情况下,可以隐藏不同集合便利需要使用的算法,同时还能后对外提供更为简便的访问算法接口。
访问者模式
访问者模式介绍
访问者模式在实际开发中使用的非常少,因为他比较难以实现并且用该模式可能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况相下,建议不要使用访问者模式。
访问者模式定义:
允许在运行时将一个或多个操作于应用一组对象,将操作与对象结构分离。
这个定义比较抽象,但是我们依然能够看出两个关键点:
- 一个是:运行时使用一组对象的一个或多个操作,比如对不同类型的文件(.ftp , .xml, .properites)进行扫描;
- 另一个是:分离对象的操作和对象本身的结构,比如,扫描多个文件夹下的多个文件夹,对于文件夹来说,扫描是额外的业务操作,如果在每个文件对象上都加上一个扫描操作,太过于冗余,而扫描操作具有统一性,非常适合访问者模式。
访问者模式主要解决的是数据域算法的耦合问题,尤其是在数据结构比较稳定,而算法变化多的情况下,为了不污染数据本身,访问者会将多种算法独立归档,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并确保算法的自由扩展。
访问者模式结构图
访问者模式实现
我们超市购物为例,假设超市汇总的三种类商品:水果,糖果,酒水进行售卖,我们可以忽略没中商品的计价方法,因为最终结账时由收银员统一集中处理,在商品类中添加计价方法是不合理的设计,我们先来定义糖果类和酒水类,水果类。
/**
* 访问者接口- 根基入参的不同调用对应的重载方法
*/
public interface Visitor {
/**
* 糖果重载方法
*/
void visit(Candy candy);
/**
* 酒水重载方法
*/
void visit(Wine wine);
/**
* 水果重载方法
*/
void visit(Fruit fruit);
}
/**
* 接待者接口(抽象访问元素角色)
*/
public interface Acceptable {
void accept(Visitor visitor);
}
/**
* 折扣计价访问者类
*/
public class DiscountVisitor implements Visitor{
private LocalDate billDate;
public DiscountVisitor(LocalDate billDate) {
this.billDate = billDate;
System.out.println("结算日期: " + billDate);
}
@Override
public void visit(Candy candy) {
System.out.println("糖果: " + candy.getName());
// 糖果大于180天就进制售卖否则一律九折
long days =billDate.toEpochDay()-candy.getProduceDate().toEpochDay();
if(days>180){
System.out.println("超过半年的糖果,请勿食用!");
}else{
double realPrice=candy.getPrice()*0.9;
System.out.println("糖果打折后的价格为: " + NumberFormat.getCurrencyInstance().format(realPrice) );
}
}
@Override
public void visit(Wine wine) {
System.out.println("酒类: " + wine.getName() +"无折扣价格!");
double realPrice=wine.getPrice();
System.out.println("原价售卖: " + NumberFormat.getCurrencyInstance().format(realPrice) );
}
@Override
public void visit(Fruit fruit) {
System.out.println("水果: " + fruit.getName());
// 糖果大于180天就进制售卖否则一律九折
long days =billDate.toEpochDay()-fruit.getProduceDate().toEpochDay();
double rate =0;
if(days>7){
System.out.println("超过7天的的水果,请勿食用!");
}else if(days>3){
rate=0.5;
}else {
rate=1;
}
double realPrice=fruit.getPrice()*fruit.getWeight()*rate;
System.out.println("水果价格为: " + NumberFormat.getCurrencyInstance().format(realPrice) );
}
}
/**
* 抽象商品父类
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Product {
/**
* 商品名称
*/
private String name;
/**
* 生成日期
*/
private LocalDate produceDate;
/**
* 商品价格
*/
private double price;
}
@Data
public class Fruit extends Product implements Acceptable{
/**
* 重量
*/
private double weight;
public Fruit(String name, LocalDate produceDate, double price, double weight) {
super(name, produceDate, price);
this.weight = weight;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Candy extends Product implements Acceptable{
public Candy(String name, LocalDate produceDate, double price) {
super(name, produceDate, price);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Wine extends Product implements Acceptable{
public Wine(String name, LocalDate produceDate, double price) {
super(name, produceDate, price);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Client {
public static void main(String[] args) {
/* Candy candy = new Candy("德芙巧克力", LocalDate.of(2024, 5, 1), 10.0);
// 创建访问者类
DiscountVisitor visitor = new DiscountVisitor(LocalDate.of(2024, 6, 1));
visitor.visit(candy);*/
/* List<Product> products = Arrays.asList(
new Candy("糖果",LocalDate.of(2024,5,1),10),
new Wine("茅台",LocalDate.of(2024,5,1),5000),
new Fruit("水果",LocalDate.of(2024,5,1),10,1)
);
DiscountVisitor discountVisitor = new DiscountVisitor(LocalDate.of(2024, 6, 5));
for (Product product : products) {
discountVisitor.visit(product);
}
*/
/**
*模拟添加多个商品
*/
List<Acceptable> products = Arrays.asList(
new Candy("糖果",LocalDate.of(2024,5,1),10),
new Wine("茅台",LocalDate.of(2024,5,1),5000),
new Fruit("水果",LocalDate.of(2024,6,1),10,1)
);
Visitor visitor = new DiscountVisitor(LocalDate.of(2024, 6, 5));
for (Acceptable product : products) {
product.accept(visitor);
}
}
}
访问者模式总结
1.访问者模式优点
-
扩展性好
在不修改对象结构元素的情况下,为对象结构中的元素添加新的功能。
-
复用性好
通过访问者模式爱定义整个对象结构通用性,从而提高复用程度。
-
分离无关行为
通过访问者来分离无关行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
2.访问者模式缺点
-
对象结构变化很困难
在访问者模式中,每新增一个新的元素类,都要在每个具体访问者类中新增相应的具体操作,这违背了"开闭原则"。
-
违反了依赖倒置原则
访问者模式依赖了具体类,而没有依赖抽象类。
3.使用场景
- 当对象的数据结构相对稳稳定,而操作却经常变化的时候,例如:路由器本身的内部结构(也就是数据结构)不会怎么变化,但是在不同操作系统下的操作可能会经常变化,比如发送数据,接受数据。
- 需要将数据结构于不常用的操作进行分离的时候,比如,扫描文件内容,这个动作通常不是文件常用操作,但是对于文件夹和文件来说,和数据结构本身没有太大关系,(树形结构的遍历操作),扫描是一个额外的动作,如果每个文件都添加一个扫描操作会太过于重复,这时采用访问者模式非常适合的,能够很好分离文件和自身的遍历操作和外部的扫描操作。
- 需要再运行时动态决定使用那些对象和方法的时候,比如,对于监控系统来说,很多时候需要监控运行时的程序状态,但大多数时候又无法预知对象编译时的状态和阐述,这时使用访问者模式就可以动态的增加监控行为。
备忘录模式
备忘录模式介绍
备忘录模式提供了一种对象的撤销实现机制,当系统中某一个需要回复到某一历史状态时可以使用备忘模式进行设计。
在很多软件都提供了撤销(Undi)操作,如 word,记事本,ps,idea等软件在编辑时,Ctrl +Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在浏览器中的后退键,数据库事务管理中的回滚操作,玩游戏时中间结果存档功能,数据库与操作系统的备份操作,棋类游戏中的悔棋功能等都属于这类。
备忘录模式定义
定义:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
备忘录模式结构
备忘录模式实现
/**
* 发起人角色
*/
@Data
@ToString
public class Originator {
private String state="原始对象";
private String id;
private String name;
private String phone;
/**
* 备忘录对象
*/
public Momento createMemento(){
return new Momento(id,name,phone);
}
/**
* 恢复对象
*/
public void restoreMemento(Momento m){
this.state=m.getState();
this.id=m.getId();
this.name=m.getName();
this.phone=m.getPhone();
}
public Originator() {
}
}
/**
* 备忘录角色
* 访问权限: 默认,同包下可见(尽量保证只有发起者类可以访问备忘录类)
*/
@Data
@NoArgsConstructor
@ToString
class Momento {
private String state="从备份对象恢复原始对象";
private String id;
private String name;
private String phone;
public Momento(String id, String name, String phone) {
this.id = id;
this.name = name;
this.phone = phone;
}
}
/**
* 负责人类 - 获取保存备忘录对象
*/
@Data
public class Caretaker {
private Momento momento;
}
public class Client {
public static void main(String[] args) {
// 创建发起人对象
Originator o1 = new Originator();
o1.setId("1");
o1.setName("spike");
o1.setPhone("13711111111");
System.out.println(o1);
// 创建负责人对象
Caretaker caretaker = new Caretaker();
caretaker.setMomento(o1.createMemento());
o1.setName("update");
System.out.println(o1);
// 从负责人对象中获取备忘录对象,实现恢复操作;
o1.restoreMemento(caretaker.getMomento());
System.out.println(o1);
}
}
备忘录模式总结
1.备忘录模式的优点
- 提供了一种状态回复的实现机制,是的用户可以方便的回到一个特定的历史步骤,当新的状态无效或者存在问题的时候,可以使用暂时存储起来的备忘录将状态复原。
- 备忘录实现了对信息的封装,一个备忘录对象是一种发起者对象状态的表示,不会被其他代码所改动,备忘录保存了发起者的状态,采用集合来存储备忘录可以实现多次撤销操作。
2.备忘录模式的缺点
- 消耗资源过大,如果需要保存的发起者类的成员变量比较多,就不可避免的需要占用大量的存储空间,每保存一次对象的窗台,都需要消耗一定的系统资源。
3.备忘录模式使用场景
- 需要保存对象在某一时刻的状态时,就可以使用备忘录模式。
- 不希望外界直接访问对象内部状态时。
命令模式
命令模式介绍
命令模式定义:命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支撑请求(命令)的排队,记录日志,撤销等(附加控制)功能。
命令模式的核心是将命令封装称为一个对象,并将此对象作为参数发送给接收方执行,达到使命令的请求和执行方法解耦,双方值通过传递各种命令对象来完成任务。
我们知道,C语言,支持函数指针,我们可以把函数当作变量来传递来传递去,但是,大部分编程语言中,函数没法作为参数传递给其他函数,也没法赋值给变量,借助命令模式,我们可以将函数封装成对象,具体来说就是,设计一个包含这个函数的类,实例化对象传来传去,这样就可以把函数对象一样使用。
命令模式实现
模拟酒店后厨的出参流程,来对命令模式进行一个演示,命令模式角色的角色与案例中角色的对应关系如下
-
服务员: 即调用者角色,由她来发起命令
-
厨师:接受者,真正执行命令的对象
-
订单:命令中包含订单
/**
-
订单类
/
@Data
public class Order {
/*- 餐桌号码
*/
private int diningTable;
/**
- 存储菜名和份数
*/
private Map<String ,Integer> foodMenu =new HashMap<>();
- 餐桌号码
}
/*
- 服务员 -> Invoker 调用者
*/
public class Waiter {
private ArrayList<Command> commands; public Waiter() { this.commands=new ArrayList<>(); } public Waiter(ArrayList<Command> commands) { this.commands = commands; } public void setCommands(Command command) { this.commands.add(command); } // 发出指令 public void orderUp(){ System.out.println("叮咚! 服务员: 有新的订单,请师傅开始制作。。。"); for (Command command : commands) { if(command !=null){ command.execute(); } } }
}
/**
- 厨师类 ->接收者角色
*/
public class Chef {public void makeFood(int num,String foodName){ System.out.println(num + "份, " + foodName); }
}
/**
-
具体命令
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class OrderCommand implements Command{/**
- 接受者对象引用
*/
private Chef receiver;
private Order order;
@Override
public void execute() {
System.out.println(order.getDiningTable() + " 桌的订单");
Map<String, Integer> foodMenu = order.getFoodMenu();
for (String key : foodMenu.keySet()) {
receiver.makeFood(order.getFoodMenu().get(key),key);
}System.out.println(order.getDiningTable() + "桌的菜品已经上齐!");
}
} - 接受者对象引用
/**
- 抽象的命令接口
/
public interface Command {
/*- 统一的执行命令接口
*/
void execute();
- 统一的执行命令接口
}
public class Client {
public static void main(String[] args) {
Order order = new Order();
order.setDiningTable(10);
order.getFoodMenu().put("b鲍鱼",1);
order.getFoodMenu().put("海鲜面",1);Order order2 = new Order(); order2.setDiningTable(15); order2.getFoodMenu().put("回锅肉",1); order2.getFoodMenu().put("木耳肉",1); //接受者 Chef chef = new Chef(); // 将订单和接受者封装成命令对象 OrderCommand orderCommand = new OrderCommand(chef, order); OrderCommand orderCommand2 = new OrderCommand(chef, order2); // 创建调用者对象 Waiter waiter = new Waiter(); waiter.setCommands(orderCommand); waiter.setCommands(orderCommand2); // 将订单发送给厨师 上菜 waiter.orderUp(); }
}
-
命令模式总结
1.命令模式优点
- 降低系统的耦合度,命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令模式非常方便。采用命令模式增加与删除命令不会影响到其他子类,它满足"开闭原则",对扩展比较灵活。
- 可以实现宏命令,命令模式可以产于组合模式结合,将更多的命令配置组成一个命令,即宏命令。
2.命令模式缺点
- 使用命令模式可能会导致某些系统过多的具体命令类。
- 系统结构更加复杂。
3.使用场景
- 系统需要将请求调用者和请求接受者解耦,使得调用者和接受者不直接交互。
- 系统需要再不同时间指定请求,将请求排队或者执行请求。
- 系统需要支持命令的撤销,操作和恢复操作。
解释器模式
解释器模式介绍
解释器模式使用频率不算高,通常用来描述如何构建一个简单"语言"的语法解释器,它在一些特定的领域被用到,比如编译器,规则引擎,正则表达式,sql解析等。不过,了解它的实现原理同样重要,能够帮助你思考如何通过更简洁的规则来表示复杂的逻辑。
解释器模式原始定义:用于定义语言的语法规则表示,并提供解释器来处理句子的语法
我们通过一个例子解释一下解释器模式
- 假设我们设计一个软甲用来进行加减计算,我们第一想法就是使用工具,提供对应的加法和减法的工具方法。
上面的形式比较单一,有限,比如形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式;
例如1+2+3+4+5,1+2+4-2等等;
解释器模式描述了如何为了简单的语言定义一个文法,如何在语言中表示一个句子,以及如何解释这些句子
在上面提到加法,减法解释器中,每一个输入表达式(比如:3+4+5+2)都包含了3个语言单文,可以使用一下定义的发文规则定义:
发文规则用于描述语言的语法结构的形式规则;
注意:这里的符号::= 表示 "定义为"的意思,竖线 | 表示 "或" 左右的其中一个,引号内为字符本身,引号外为语法。
表达式可以是一个值,也可以是plus 或者minus 运算,而plus和minus又是表达式结合运算构成,值的类型为整型数。
抽象语法树
在解释器模式中还可以通过一种称为抽象语法树的图形方式来直观的表示语言构成,每一棵抽象语法树对应一个语言实例,例如加法,减法,表达式语言中"
1+2+3-4+1" 可以通过依稀抽象语法树表示
解释器模式原理图
解释器模式:只要是解释自己定义的语言;
解释器模式实现
为了更好的解释一下解释器模式,我们来定义一个进行加减乘除计算的"语言" ,语法规则如下:
- 运算符只包含 加,减,乘,除,并没有优先级的概念;
- 表达式中,先书写数字,后书写运算符,空格隔开;
我们举例来解释一下上面的语法规则;
- 比如"9 5 7 3 - + " 这样一个表达式,我们按照上面的语法规则来处理,取出数字 9 ,5 和运算符"-" 运算符,计算得到4,于是表达式就变成了 4 7 3 + * ,然后我们再取出4 7 和"+" 运算符,计算得到11,表达式变成了11 3 * 最后我们取出11 3 和"" 运算符,最终结果就是33.
不用解释器模式的实现
/**
* 表达式解释器
*/
public class ExpressionInterpreter {
/**
* 双向链表
* ,可以从两端队列增加或者删除
*/
private Deque<Long> numbers =new LinkedList<Long>();
/**
* 接收表达式进行解释
* @param expression
* @return
*/
public long interpret(String expression){
// 9 5 7 3 - + *
String[] elenents = expression.split(" ");
int length = elenents.length;
// 获取表达式中的数字
for (int i = 0; i < (length+1)/2; ++i) {
// 向集合的尾部添加元素
numbers.addLast(Long.parseLong(elenents[i]));
}
// 获取表达式中的符号
for (int i = (length+1)/2; i < length; ++i) {
// 向集合的尾部添加元素
String operator = elenents[i];
boolean isValid="+".equals(operator)||"-".equals(operator)||"*".equals(operator)||"/".equals(operator);
if(!isValid){
throw new RuntimeException("无效表达式! "+ expression);
}
//获取集合中的数字,移除集合中的第一z个元素,并且返回被移除的值
long number1 = numbers.pollFirst(); // 数字
long number2 = numbers.pollFirst(); // 数字
long result =0;
if("+".equals(operator)){
result= number1 + number2;
}else if("-".equals(operator)){
result= number1 - number2;
}else if("*".equals(operator)){
result= number1 * number2;
}else if("/".equals(operator)){
result= number1 / number2;
}
//将结果放到头部
numbers.addFirst(result);
}
// 运算的最终结果是被保存在集合中的
if(numbers.size()!=1){
throw new RuntimeException("无效表达式! "+ expression);
}
// 移除元素并返回
return numbers.pop();
}
}
public static void main(String[] args) {
ExpressionInterpreter interpreter = new ExpressionInterpreter();
long interpret = interpreter.interpret("9 5 7 3 - + *");
System.out.println("表达式的运行结果为:"+interpret);
}
}
使用解释器模式
/**
* 表达式接口
*/
public interface Expression {
long interpret();
}
/**
* 数字实现类
*/
public class NumExpression implements Expression{
private long number;
public NumExpression(long number) {
this.number = number;
}
public NumExpression(String number) {
this.number = Long.parseLong(number);
}
@Override
public long interpret() {
return this.number;
}
}
public class PluExpression implements Expression{
private Expression exp1;
private Expression exp2;
public PluExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret()+ exp2.interpret();
}
}
public class SubExpression implements Expression{
private Expression exp1;
private Expression exp2;
public SubExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret()-exp2.interpret();
}
}
public class MulExpression implements Expression{
private Expression exp1;
private Expression exp2;
public MulExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret()*exp2.interpret();
}
}
public class DivExpression implements Expression{
private Expression exp1;
private Expression exp2;
public DivExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret() / exp2.interpret();
}
}
/**
* 表达式解释器类
*
*/
public class ExpressionInterpreter {
private Deque<Expression> numbers=new LinkedList<>();
public long interpret(String expression){
String[] elements = expression.split(" ");
int length = elements.length;
for (int i = 0; i < (length+1)/2; ++i) {
numbers.addLast(new NumExpression(elements[i]));
}
for (int i = (length+1)/2; i < length; ++i) {
String operator = elements[i];
boolean isValid="+".equals(operator)||"-".equals(operator)||"*".equals(operator)||"/".equals(operator);
if(!isValid){
throw new RuntimeException("无效表达式! "+ expression);
}
Expression exp1 = numbers.pollFirst();
Expression exp2 = numbers.pollFirst();
Expression result =null;
if("+".equals(operator)){
result= new PluExpression(exp1,exp2);
}else if("-".equals(operator)){
result= new SubExpression(exp1,exp2);
}else if("*".equals(operator)){
result= new MulExpression(exp1,exp2);
}else if("/".equals(operator)){
result= new DivExpression(exp1,exp2);
}
long num = result.interpret();
numbers.addFirst(new NumExpression(num));
}
// 运算的最终结果是被保存在集合中的
if(numbers.size()!=1){
throw new RuntimeException("无效表达式! "+ expression);
}
// 移除元素并返回
return numbers.pop().interpret();
}
}
public class Client {
public static void main(String[] args) {
ExpressionInterpreter e = new ExpressionInterpreter();
long interpret = e.interpret("6 2 3 2 4 / - + *");
System.out.println(interpret);
}
}
解释器模式总结
1.解释器模式优点
-
易于改变和扩展文法.
因为在解释器模式中使用类俩表示语言的文法规则的,因此可以通过继承等机制改变或者扩展文法,每个文法规则都可以表示一个类,因此我们可以快速的实现一个迷你的语言。
-
实现文法比较容易
在抽象语法树中表达一个节点的实现都是相似的,这些类的代码编写不会特别复杂
-
增加新的解释器表达式比较方便
如果用户需要新增解释表达式,只需要新增对应的表达式类即可,原有的表达式类不需要修改,符合开闭原则
2.解释器模式缺点
- 对于复杂文法难以维护
在解释器中一条规则至少需要定义一个类,因此一个语言中如果有太多的文法规则,就会使类个数急剧增加,导致系统难以维护管理。
- 执行效率低
在解释器模式中,大量使用了循环和递归调用,所有复杂的句子执行起来,整个过程也是非常的繁琐。
3.使用场景
- 当语言的文法比较简单,并且执行效率不是关键问题。
- 当问题重复出现,就可以使用一种简单的语言表达
- 当一个语言需要解释执行,并且语言中的句子可以表示一个抽象的语法树的时候。
中介者模式
中介者模式接介绍
提到中介者模式,有一个比较经典的例子就是航空管制,为了让飞机在飞行的时候互不干扰,每架飞机都需要知道其他飞机每时每刻的位置,这就需要时刻跟其他飞机通讯,飞机通讯形成的通讯网络就会比较复杂,这个时候,我们通过引入"塔台"这样的一个中介,让每架飞机只跟塔台来通讯,发送自己的位置给塔台,由塔台来负责每架飞机的航线调度。这样就大大简化了通讯网络。
中介者模式的定义: 定义一个单独(中介)对象,来封装一组对象之间的交互,将这组对象之间的交互委派给予中介对象交互,来避免对象之间的交互。
中介者对象就是用于处理对象与对象之间的直接交互,封装了多个对象之间的交互细节,中介者模式的设计跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系从多对多的网状关系,转换为一对多的星状关心,原来一个对象要跟N个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高代码的可读性和可维护性。
中介者模式实现
public interface Mediator {
/**
* 处理同事对象注册于转发同事信息的方法
* @param key
*/
void apply(String key);
}
/**
* 具体中介者
* 管理对象,并且协调同事之间的关系
*/
public class MediatorImpl implements Mediator{
@Override
public void apply(String key) {
System.out.println("最终中介者执行的操作,key:" + key);
}
}
/**
* 抽象的同事
*/
public abstract class Colleague {
private Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public Mediator getMediator() {
return mediator;
}
/**
* 同时之间进行交互的抽象方法
*/
public abstract void exec(String key);
}
public class ConcreteColleagueA extends Colleague{
public ConcreteColleagueA(Mediator mediator) {
super(mediator);
}
@Override
public void exec(String key) {
System.out.println("在A同事中,通过中介者执行!");
getMediator().apply(key);
}
}
public class ConcreteColleagueB extends Colleague{
public ConcreteColleagueB(Mediator mediator) {
super(mediator);
}
@Override
public void exec(String key) {
System.out.println("在B同事中,通过中介者执行!");
getMediator().apply(key);
}
}
public class Client {
public static void main(String[] args) {
/**
* 创建中介者
*/
Mediator mediator = new MediatorImpl();
Colleague c1 = new ConcreteColleagueA(mediator);
c1.exec("key-A");
Colleague c2 = new ConcreteColleagueA(mediator);
c2.exec("key-B");
}
}
案例代码实现
/**
* 抽象同事类
*/
@Data
public abstract class Person {
protected String name;
/**
* 持有中介者的引用
*/
protected Medaitor medaitor;
public Person(String name, Medaitor medaitor) {
this.name = name;
this.medaitor = medaitor;
}
}
public abstract class Medaitor {
public abstract void contact(String message, Person person);
}
/**
* 具体的中介者-具体的中机构
*/
@Data
public class MedaitorStructure extends Medaitor{
/**
* 中介知悉 房屋出租人和承租人的信息
* @param message
* @param person
*/
private HouseOwner houseOwner; // 房主
private Tenant tenant; //承租人
@Override
public void contact(String message, Person person) {
if(person==houseOwner){
/**
* 如果是房主,则租房者获得信息
*/
tenant.getMessage(message);
}else{
/**
* 否则是房主获得信息
*/
houseOwner.getMessage(message);
}
}
}
public class HouseOwner extends Person{
public HouseOwner(String name, Medaitor medaitor) {
super(name, medaitor);
}
/**
* 与中介联系
*/
public void contact(String message){
medaitor.contact(message,this);
}
/**
* 获取信息
*/
public void getMessage(String message){
System.out.println("房主: " + name + ",获取到的信息: " + message);
}
}
/**
* 具体的同事类--具体承租人
*/
public class Tenant extends Person{
public Tenant(String name, Medaitor medaitor) {
super(name, medaitor);
}
/**
* 与中介联系的方法
*/
public void contact(String message){
medaitor.contact(message,this);
}
public void getMessage(String message){
System.out.println("租房者 " + name + " ,获取到的信息: "+message);
}
}
public class Client {
public static void main(String[] args) {
/**
* 创建中介者
*/
Mediator mediator = new MediatorImpl();
Colleague c1 = new ConcreteColleagueA(mediator);
c1.exec("key-A");
Colleague c2 = new ConcreteColleagueA(mediator);
c2.exec("key-B");
}
}
中介者模式总结
1.中介者模式的优点
- 中介者模式简化了对象之间的交互,它用中介者和同事的一对多代替了原来的同事之间的多对多的交互,一对多关系更好理解,易于维护和扩展,将原本难以理解的网状结构和转换成相对简单的星型结构。
- 可以将各个同事就对象进行解耦,中介者有利于各同事之间的松散耦合,可以独立的改变或者复用每一个同事或者中介者,增加新的中介者类和新同事类都比较方便,符合开闭原则。
- 可以减少子类生产,中介者将原本分布式与多个对象的行为集中在了一起,改变这些行为只需要生成新的中介者的子类即可,是的同事类可以被重用,无需直接对同事类进行扩展。
2.中介者模式缺点
- 在具体的中介者类中包含了大量同事类之间的交互细节,可能导致中介者类变得复杂,是的系统不好维护。
3.中介者模式使用场景
- 系统中对象之间存在复杂的引用关系,关系结构混乱难以理解。
- 一个对象由于引用了其他的很多对象,并且直接和这些对象进行通讯,导致难以复用该对象。
- 想要通过一个中间类来封装多个类中的行为,而又不想生成太多的子类,此时可以通过引用中介者类来实现,在中介者类中定义对象的交互和公共行为,如果需要改变行为则可以在增加新的中介者类。
你的语言。
-
实现文法比较容易
在抽象语法树中表达一个节点的实现都是相似的,这些类的代码编写不会特别复杂
-
增加新的解释器表达式比较方便
如果用户需要新增解释表达式,只需要新增对应的表达式类即可,原有的表达式类不需要修改,符合开闭原则
2.解释器模式缺点
- 对于复杂文法难以维护
在解释器中一条规则至少需要定义一个类,因此一个语言中如果有太多的文法规则,就会使类个数急剧增加,导致系统难以维护管理。
- 执行效率低
在解释器模式中,大量使用了循环和递归调用,所有复杂的句子执行起来,整个过程也是非常的繁琐。
3.使用场景
- 当语言的文法比较简单,并且执行效率不是关键问题。
- 当问题重复出现,就可以使用一种简单的语言表达
- 当一个语言需要解释执行,并且语言中的句子可以表示一个抽象的语法树的时候。
中介者模式
中介者模式接介绍
提到中介者模式,有一个比较经典的例子就是航空管制,为了让飞机在飞行的时候互不干扰,每架飞机都需要知道其他飞机每时每刻的位置,这就需要时刻跟其他飞机通讯,飞机通讯形成的通讯网络就会比较复杂,这个时候,我们通过引入"塔台"这样的一个中介,让每架飞机只跟塔台来通讯,发送自己的位置给塔台,由塔台来负责每架飞机的航线调度。这样就大大简化了通讯网络。
[外链图片转存中...(img-L2nzf9k4-1720963028526)]
中介者模式的定义: 定义一个单独(中介)对象,来封装一组对象之间的交互,将这组对象之间的交互委派给予中介对象交互,来避免对象之间的交互。
中介者对象就是用于处理对象与对象之间的直接交互,封装了多个对象之间的交互细节,中介者模式的设计跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系从多对多的网状关系,转换为一对多的星状关心,原来一个对象要跟N个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高代码的可读性和可维护性。
[外链图片转存中...(img-uBlZFacz-1720963028527)]
中介者模式实现
public interface Mediator {
/**
* 处理同事对象注册于转发同事信息的方法
* @param key
*/
void apply(String key);
}
/**
* 具体中介者
* 管理对象,并且协调同事之间的关系
*/
public class MediatorImpl implements Mediator{
@Override
public void apply(String key) {
System.out.println("最终中介者执行的操作,key:" + key);
}
}
/**
* 抽象的同事
*/
public abstract class Colleague {
private Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public Mediator getMediator() {
return mediator;
}
/**
* 同时之间进行交互的抽象方法
*/
public abstract void exec(String key);
}
public class ConcreteColleagueA extends Colleague{
public ConcreteColleagueA(Mediator mediator) {
super(mediator);
}
@Override
public void exec(String key) {
System.out.println("在A同事中,通过中介者执行!");
getMediator().apply(key);
}
}
public class ConcreteColleagueB extends Colleague{
public ConcreteColleagueB(Mediator mediator) {
super(mediator);
}
@Override
public void exec(String key) {
System.out.println("在B同事中,通过中介者执行!");
getMediator().apply(key);
}
}
public class Client {
public static void main(String[] args) {
/**
* 创建中介者
*/
Mediator mediator = new MediatorImpl();
Colleague c1 = new ConcreteColleagueA(mediator);
c1.exec("key-A");
Colleague c2 = new ConcreteColleagueA(mediator);
c2.exec("key-B");
}
}
案例代码实现
/**
* 抽象同事类
*/
@Data
public abstract class Person {
protected String name;
/**
* 持有中介者的引用
*/
protected Medaitor medaitor;
public Person(String name, Medaitor medaitor) {
this.name = name;
this.medaitor = medaitor;
}
}
public abstract class Medaitor {
public abstract void contact(String message, Person person);
}
/**
* 具体的中介者-具体的中机构
*/
@Data
public class MedaitorStructure extends Medaitor{
/**
* 中介知悉 房屋出租人和承租人的信息
* @param message
* @param person
*/
private HouseOwner houseOwner; // 房主
private Tenant tenant; //承租人
@Override
public void contact(String message, Person person) {
if(person==houseOwner){
/**
* 如果是房主,则租房者获得信息
*/
tenant.getMessage(message);
}else{
/**
* 否则是房主获得信息
*/
houseOwner.getMessage(message);
}
}
}
public class HouseOwner extends Person{
public HouseOwner(String name, Medaitor medaitor) {
super(name, medaitor);
}
/**
* 与中介联系
*/
public void contact(String message){
medaitor.contact(message,this);
}
/**
* 获取信息
*/
public void getMessage(String message){
System.out.println("房主: " + name + ",获取到的信息: " + message);
}
}
/**
* 具体的同事类--具体承租人
*/
public class Tenant extends Person{
public Tenant(String name, Medaitor medaitor) {
super(name, medaitor);
}
/**
* 与中介联系的方法
*/
public void contact(String message){
medaitor.contact(message,this);
}
public void getMessage(String message){
System.out.println("租房者 " + name + " ,获取到的信息: "+message);
}
}
public class Client {
public static void main(String[] args) {
/**
* 创建中介者
*/
Mediator mediator = new MediatorImpl();
Colleague c1 = new ConcreteColleagueA(mediator);
c1.exec("key-A");
Colleague c2 = new ConcreteColleagueA(mediator);
c2.exec("key-B");
}
}
中介者模式总结
1.中介者模式的优点
- 中介者模式简化了对象之间的交互,它用中介者和同事的一对多代替了原来的同事之间的多对多的交互,一对多关系更好理解,易于维护和扩展,将原本难以理解的网状结构和转换成相对简单的星型结构。
- 可以将各个同事就对象进行解耦,中介者有利于各同事之间的松散耦合,可以独立的改变或者复用每一个同事或者中介者,增加新的中介者类和新同事类都比较方便,符合开闭原则。
- 可以减少子类生产,中介者将原本分布式与多个对象的行为集中在了一起,改变这些行为只需要生成新的中介者的子类即可,是的同事类可以被重用,无需直接对同事类进行扩展。
2.中介者模式缺点
- 在具体的中介者类中包含了大量同事类之间的交互细节,可能导致中介者类变得复杂,是的系统不好维护。
3.中介者模式使用场景
- 系统中对象之间存在复杂的引用关系,关系结构混乱难以理解。
- 一个对象由于引用了其他的很多对象,并且直接和这些对象进行通讯,导致难以复用该对象。
- 想要通过一个中间类来封装多个类中的行为,而又不想生成太多的子类,此时可以通过引用中介者类来实现,在中介者类中定义对象的交互和公共行为,如果需要改变行为则可以在增加新的中介者类。