26.设计模式使用场景开发定位

设计模式的分类

根据其目的(模式是用来做什么的)可分为创建型(Creational),结构型(Structural)和行为型(Behavioral)三种:
    • 创建型模式主要用于创建对象。
    • 结构型模式主要用于处理类或对象的组合。
    • 行为型模式主要用于描述对类或对象怎样交互和怎样分配职责。
    
根据范围(模式主要是用于处理类之间关系还是处理对象之间的关系)可分为类模式和对象模式两种:
    •类模式处理类和子类之间的关系,这些关系通过继承建立,在编译时刻就被确定下来,是属于静态的。
    •对象模式处理对象间的关系,这些关系在运行时刻变化,更具动态性。

=======================================================================

1. 创建型模式

单例模式

使用场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象频繁访问数据库或文件的对象(比如数据源、session工厂等)。

极客时间
实战案例一:处理资源访问冲突
- 解决资源竞争问题的办法还有很多,分布式锁是最常听到的一种解决方案。不过,实现一个安全可靠、无 bug、高性能的分布式锁,并不是件容易的事情。除此之外,并发队列(比如 Java 中的 BlockingQueue)也可以解决这个问题:多个线程同时往并发队列里写日志,一个单独的线程负责将并发队列中的数据,写入到日志文件。这种方式实现起来也稍微有点复杂。
实战案例二:表示全局唯一类
如何实现一个单例?
单例存在哪些问题?
有何替代解决方案?

工厂模式

极客时间
简单工厂(Simple Factory)
工厂方法(Factory Method)
抽象工厂(Abstract Factory)
工厂模式和 DI 容器有何区别?

案例:
披萨的项目,披萨的种类很多,种类下面又有地域的不同,客户在点披萨时,可以点不同口味的披萨,比如北京的奶酪pizza、北京的胡椒pizza 或者是伦敦的奶酪pizza、伦敦的胡椒pizza。披萨的制作是固定流程,请完成披萨店订购功能;需要考虑到业务的扩展性。

使用场景:将创建对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦,提高代码的扩展和维护,调用者不需要了解内部结构,依赖抽象层,符合ocp原则;多个调用者调用(对修改关闭)------>抽象层(工厂)----->实现层(对扩展开放)

应用:JDK-Calendar 应用的源码分析

原型模式

案例:
克隆羊问题:现在有一只羊tom,姓名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和tom羊 属性完全相同的10只羊。

原理结构图:
在这里插入图片描述
原理结构图说明
Prototype : 原型类,声明一个克隆自己的接口
ConcretePrototype: 具体的原型类, 实现一个克隆自己的操作
Client: 让一个原型对象克隆自己,从而创建一个新的对象(属性一样)

使用场景:创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率;如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码。

应用: Spring中原型bean(原型Bean)的创建,就是原型模式的应用

极客时间
什么是原型模式?
原型模式的原理与应用
那何为“对象的创建成本比较大”?
原型模式的实现方式:深拷贝和浅拷贝

建造者模式

案例:
盖房项目需求:1.需要建房子:这一过程为打桩、砌墙、封顶,大体流程一样;2.房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不同的.;

传统的方式会把产品(即:房子) 和 创建产品的过程(即:建房子流程) 封装在一起,,耦合性增强了。这样用户就必须知道内部的具体构建细节。

原理结构图:
在这里插入图片描述
原理结构图说明
1.Product(产品角色): 一个具体的产品对象。
2.Builder(抽象建造者): 创建一个Product对象的各个部件指定的 接口/抽象类。
3.ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件,可能有多个。
4.Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

应用:java.lang.StringBuilder 中的建造者模式

极客时间
为什么需要建造者模式?
与工厂模式有何区别?

=======================================================================
前面几节,我们学习了设计模式中的创建型模式。创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。

其中,单例模式用来创建全局唯一的对象。
工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
建造者模式是用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。
原型模式针对创建成本比较大的对象,利用对已有对象进行复制的方式进行创建,以达到节省创建时间的目的。

从今天起,我们开始学习另外一种类型的设计模式:结构型模式。结构型模式主要总结了一些类或对象组合在一起的经典结构,这些经典的结构可以解决特定应用场景的问题。
结构型模式包括:代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式。

=======================================================================

2. 结构型模式

适配器模式

案例:
现实生活中手机充电,将220V适配成5V就是使用到适配器设配。

分为三类:类适配器模式、对象适配器模式、接口适配器模式

应用:SpringMvc 中 的HandlerAdapter, 就使用了适配器模式

极客时间
适配器模式的原理与实现
适配器模式应用场景总结

  • 一般来说,适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。
  1. 封装有缺陷的接口设计 2. 统一多个类的接口设计 3. 替换依赖的外部系统 4. 兼容老版本接口 5. 适配不同格式的数据

剖析适配器模式在 Java 日志中的应用
代理、桥接、装饰器、适配器 4 种设计模式的区别:
代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。
尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。这里我就简单说一下它们之间的区别。

代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

桥接模式

案例:
手机操作问题:手机有不同的样式和品牌,每一种样式下面有各种品牌每一种品牌也有多种样式,当然随时可能增加或者减少某一种样式或者某一种品牌的手机,需要考虑ocp原则;
在这里插入图片描述
桥接模式(Bridge模式)是指:将 实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。抽象聚合了实现
在这里插入图片描述
设计方案:选取了手机(Phone)作为抽象品牌(Brand)作为实现来进行设计的,同时将品牌Brand聚合到手机Phone里面了。

使用场景
1.对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.
2.常见的应用场景:

  • JDBC驱动程序
  • 银行转账系统
    转账分类: 网上转账,柜台转账,AMT转账 ---------抽象
    转账用户类型:普通用户,银卡用户,金卡用户… -----实现
  • 消息管理
    消息类型:即时消息,延时消息 ---------抽象
    消息分类:手机短信,邮件消息,QQ消息… -----实现

极客时间
桥接模式的原理解析

  • “一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”通过组合关系来替代继承关系,避免继承层次的指数级爆炸。这种理解方式非常类似于,我们之前讲过的“组合优于继承”设计原则,所以,这里我就不多解释了

桥接模式的应用举例

  • 我们讲过一个 API 接口监控告警的例子:根据不同的告警规则,触发不同类型的告警。告警支持多种通知渠道,包括:邮件、短信、微信、自动语音电话。通知的紧急程度有多种类型,包括:SEVERE(严重)、URGENCY(紧急)、NORMAL(普通)、TRIVIAL(无关紧要)。不同的紧急程度对应不同的通知渠道。比如,SERVE(严重)级别的消息会通过“自动语音电话”告知相关人员。

桥接模式有两种理解方式。第一种理解方式是“将抽象和实现解耦,让它们能独立开发”。这种理解方式比较特别,应用场景也不多。另一种理解方式更加简单,类似“组合优于继承”设计原则,这种理解方式更加通用,应用场景比较多。不管是哪种理解方式,它们的代码结构都是相同的,都是一种类之间的组合关系。

装饰者模式

案例:
星巴克咖啡订单项目:

  1. 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
  2. 调料:Milk、Soy(豆浆)、Chocolate
  3. 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
  4. 使用OO的来计算不同种类咖啡的 费用: 客户可以点 单品咖啡 ,也可以 单品咖啡+ 调料组合,也就是咖啡种类 + 调料(可选,调料动态变化的)

装饰者模式动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp),比如打包一个快递,对于不同的物品打包的方式,材料可以都不一样。

例如,设计商品价格的时候,往往会有很多折扣活动、红包活动,我们可以用装饰模式去设计这个业务。

在JDK InputStream 流的创建就使用到了该模式。

极客时间
Java IO 类的“奇怪”用法
基于继承的设计方案
基于装饰器模式的设计方案

  • 第一个比较特殊的地方是:装饰器类和原始类继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类。
  • 第二个比较特殊的地方是:装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点。实际上,符合“组合关系”这种代码结构的设计模式有很多,比如之前讲过的代理模式、桥接模式,还有现在的装饰器模式。尽管它们的代码结构很相似,但是每种设计模式的意图是不同的。就拿比较相似的代理模式和装饰器模式来说吧,代理模式中,代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能
  • 装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。**它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。**除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。

组合模式

案例:
学校院系展示需求:编写程序展示一个学校院系结构,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。一对多,类似于树的结构扩展。

组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
在这里插入图片描述
极客时间

组合模式跟我们之前讲的面向对象设计中的“组合关系(通过组合来组装两个类)”,完全是两码事。这里讲的“组合模式”,主要是用来处理树形结构数据。这里的“数据”,你可以简单理解为一组对象集合,待会我们会详细讲解。

正因为其应用场景的特殊性,数据必须能表示成树形结构,这也导致了这种模式在实际的项目开发中并不那么常用。但是,一旦数据满足树形结构,应用这种模式就能发挥很大的作用,能让代码变得非常简洁。

组合模式的原理与实现
组合模式的应用场景举例

外观模式

案例:
组建一个家庭影院
外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用;通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节

极客时间

门面模式的原理与实现
门面模式的应用场景举例

  1. 解决易用性问题
  2. 解决性能问题
  3. 解决分布式事务问题
  4. 我们知道,类、模块、系统之间的“通信”,一般都是通过接口调用来完成的。接口设计的好坏,直接影响到类、模块、系统是否好用。所以,我们要多花点心思在接口设计上。我经常说,完成接口设计,就相当于完成了一半的开发任务。只要接口设计得好,那代码就差不到哪里去。
  5. 接口粒度设计得太大,太小都不好。太大会导致接口不可复用,太小会导致接口不易用。在实际的开发中,接口的可复用性和易用性需要“微妙”的权衡。针对这个问题,我的一个基本的处理原则是,尽量保持接口的可复用性,但针对特殊情况,允许提供冗余的门面接口,来提供更易用的接口。

享元模式

案例:
展示网站项目需求:小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:有客户要求以新闻的形式发布,有客户人要求以博客的形式发布,有客户希望以微信公众号的形式发布。
在这里插入图片描述
应用:享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方;享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池。

极客时间

享元模式原理与实现
开发一个棋牌游戏(比如象棋)
享元模式在文本编辑器中的应用
享元模式 vs 单例、缓存、对象池
享元模式在 Java Integer 中的应用
享元模式在 Java String 中的应用

代理模式

案例: A老师不能上课了,找B老师代课;B老师除了讲课外还讲了故事;
代理模式:为一个对象 提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的 好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。被代理的对象(目标对象)可以是远程对象、 创建开销大的对象或 需要安全控制的对象;

极客时间
代理模式的原理解析
动态代理的原理解析
代理模式的应用场景
1. 业务系统的非功能性需求开发,代理模式最常用的一个应用场景就是,在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。实际上,前面举的搜集接口请求信息的例子,就是这个应用场景的一个典型例子。
2. 如果你熟悉 Java 语言和 Spring 开发框架,这部分工作都是可以在 Spring AOP 切面中完成的。前面我们也提到,Spring AOP 底层的实现原理就是基于动态代理。
3. 代理模式在 RPC、缓存中的应用,实际上,RPC 框架也可以看作一种代理模式,GoF 的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。

=======================================================================
我们常把 23 种经典的设计模式分为三类:创建型、结构型、行为型。前面我们已经学习了创建型和结构型,从今天起,我们开始学习行为型设计模式。我们知道,创建型设计模式主要解决“对象的创建”问题,结构型设计模式主要解决“类或对象的组合或组装”问题,那行为型设计模式主要解决的就是“类或对象之间的交互”问题。

行为型设计模式比较多,有 11 个,几乎占了 23 种经典设计模式的一半。它们分别是:观察者模式、模板模式、策略模式、职责链模式、状态模式、迭代器模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。

=======================================================================

行为型模式

模板方法模式

案例:
编写制作豆浆的程序,说明如下:

  1. 制作豆浆的流程 选材—>添加配料—>浸泡—>放到豆浆机打碎,流程是固定的,只是其中的某一步骤是不一样的
  2. 通过添加不同的配料,可以制作出不同口味的豆浆
  3. 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的,当创建某个对象的步骤流程大致一样时,可以用 模板+建造者

极客时间
模板模式主要是用来解决复用和扩展两个问题

模板模式的原理与实现

  • 模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。

模板模式作用一:复用
模板模式作用二:扩展
回调的原理解析

  • 应用举例一:JdbcTemplate (doInStatement(Statement stmt))
  • 应用举例二:setClickListener() (doInRedis(RedisConnection connection))
  • 应用举例三:addShutdownHook()
  • 应用举例四:RedisCallback(StringRedisTemplate)

模板模式 VS 回调

  • 从应用场景上来看,同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。而异步回调跟模板模式有较大差别,更像是观察者模式。
  • 模板模式主要起到代码复用和扩展的作用。除此之外,我们还讲到了回调,它跟模板模式的作用类似,但使用起来更加灵活。它们之间的主要区别在于代码实现,模板模式基于继承来实现,回调基于组合来实现。

利用工厂模式 + 模板方法模式,消除 if…else 和重复代码

命令模式

案例:
智能生活项目需求:

  1. 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。
  2. 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。
  3. 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用,这 就可以考虑使用命令模式。
  4. 命令模式可将**“动作的请求者”从“动作的执行者”对象中解耦出来.**
    在这里插入图片描述
    命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制

访问者模式

案例:
测评系统的需求
在这里插入图片描述
分析上面案例:男人可以对歌手评价成功,失败,或者是待定;同时成功,失败,或者待定可能是男人或女人的测评;可以理解为是一种关联关系;一个评价由 谁和分类 确定的。

访问者模式(Visitor Pattern ),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口。访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决

它是有别于桥接模式的

极客时间

带你“发明”访问者模式

  • 假设我们从网站上爬取了很多资源文件,它们的格式有三种:PDF、PPT、Word。我们现在要开发一个工具来处理这批资源文件。这个工具的其中一个功能是,把这些资源文件中的文本内容抽取出来放到 txt 文件中。如果让你来实现,你会怎么来做呢?

重新来看访问者模式

  • 允许一个或者多个操作应用到一组对象上,解耦操作和对象本身
  • 比如 Extractor 负责抽取文本内容,Compressor 负责压缩。当我们新添加一个业务功能的时候,资源文件类不需要做任何修改
  • 一般来说,访问者模式针对的是一组类型不同的对象(PdfFile、PPTFile、WordFile)。不过,尽管这组对象的类型是不同的,但是,它们继承相同的父类(ResourceFile)或者实现相同的接口。在不同的应用场景下,我们需要对这组对象进行一系列不相关的业务操作(抽取文本、压缩等),但为了避免不断添加功能导致类(PdfFile、PPTFile、WordFile)不断膨胀,职责越来越不单一,以及避免频繁地添加功能导致的频繁代码修改,我们使用访问者模式,将对象与操作解耦,将这些业务操作抽离出来,定义在独立细分的访问者类(Extractor、Compressor)中。

为什么支持双分派的语言不需要访问者模式?

  • 如何理解“Dispatch”这个单词呢? 在面向对象编程语言中,我们可以把方法调用理解为一种消息传递,也就是“Dispatch”。一个对象调用另一个对象的方法,就相当于给它发送一条消息。这条消息起码要包含对象名、方法名、方法参数。
  • 如何理解“Single”“Double”这两个单词呢?“Single”“Double”指的是执行哪个对象的哪个方法,跟几个因素的运行时类型有关。我们进一步解释一下。Single Dispatch 之所以称为“Single”,是因为执行哪个对象的哪个方法,只跟“对象”的运行时类型有关。Double Dispatch 之所以称为“Double”,是因为执行哪个对象的哪个方法,跟“对象”和“方法参数”两者的运行时类型有关。

除了访问者模式,上一节的例子还有其他实现方案吗?

  • 实际上,开发这个工具有很多种代码设计和实现思路。为了讲解访问者模式,上节课我们选择了用访问者模式来实现。实际上,我们还有其他的实现方法,比如,我们还可以利用工厂模式来实现,定义一个包含 extract2txt() 接口函数的 Extractor 接口。PdfExtractor、PPTExtractor、WordExtractor 类实现 Extractor 接口,并且在各自的 extract2txt() 函数中,分别实现 Pdf、PPT、Word 格式文件的文本内容抽取。ExtractorFactory 工厂类根据不同的文件类型,返回不同的 Extractor。
  • 当需要添加新的功能的时候,比如压缩资源文件,类似抽取文本内容功能的代码实现,我们只需要添加一个 Compressor 接口,PdfCompressor、PPTCompressor、WordCompressor 三个实现类,以及创建它们的 CompressorFactory 工厂类即可。唯一需要修改的只有最上层的 ToolApplication 类。基本上符合“对扩展开放、对修改关闭”的设计原则。

迭代器模式

**案例:**编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系
组成,一个学校有多个学院,一个学院有多个系。

如果我们的集合元素是用不同的方式实现 的,有数组,还有java的集合类,或者还有其他方式,当客户端要 遍历这 些 集合 元 素 的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。

观察者模式

案例:
天气预报项目需求,具体要求如下:

  1. 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
  2. 需要设计开放型API,便于其他第三方也能接入气象站获取数据。
  3. 提供温度、气压和湿度的接口
  4. 测量数据更新时,要能实时的通知给第三方(订阅者)

观察者模式的好处
5. 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
6. 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修改代码,遵守了ocp原则

极客时间
是在实际的开发中用得比较多的一种模式:观察者模式。根据应用场景的不同,观察者模式会对应不同的代码实现方式:有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式

原理及应用场景剖析

  • 在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。一般情况下,被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者

  • 前面我们已经学习了很多设计模式,不知道你有没有发现,实际上,设计模式要干的事情就是解耦。创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦,具体到观察者模式,它是将观察者和被观察者代码解耦。借助设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则、高内聚松耦合等特性,以此来控制和应对代码的复杂性,提高代码的可扩展性。

基于不同应用场景的不同实现方式
异步非阻塞观察者模式的简易实现
EventBus 框架功能需求介绍
手把手实现一个 EventBus 框架

中介者模式

案例:
智能家庭项目:

  1. 智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘 等
  2. 主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流程为:闹铃响起->咖啡机开始做咖啡->窗帘自动落下->电视机开始播放;之前有用外观模式处理过;

备忘录模式

案例:
游戏角色状态恢复问题:游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态。

适用的应用场景:1.后悔药。 2.打游戏时的存档。 3.Windows 里的 ctri + z。4.IE 中的后退。 5.数据库的事务管理. 6.重启 Chrome 可以选择恢复之前打开的页面

极客时间

备忘录模式的原理与实现

  • 主要是用来防丢失、撤销、恢复等
  • 在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
  • 备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。这个模式的定义表达了两部分内容:一部分是,存储副本以便后期恢复;另一部分是,要在不违背封装原则的前提下,进行对象的备份和恢复。

如何优化内存和时间消耗?

解释器模式

案例:
四则运算问题
通过解释器模式来实现四则运算,如计算a+b-c的值,具体要求;先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复;在分别输入a ,b, c, d, e 的值。

状态模式

案例:
APP抽奖活动问题
在这里插入图片描述

应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式

多个 if 判断逻辑可以考虑用状态模式,策略模式,责任链模式等解决

极客时间

  • 从今天起,我们开始学习状态模式。在实际的软件开发中,状态模式并不是很常用,但是在能够用到的场景里,它可以发挥很大的作用。从这一点上来看,它有点像我们之前讲到的组合模式。

什么是有限状态机?

  • 有限状态机,英文翻译是 Finite State Machine,缩写为 FSM,简称为状态机。状态机有 3 个组成部分:状态(State)、事件(Event)、动作(Action)。其中,事件也称为转移条件(Transition Condition)。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。
  • 状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。不过,状态机的实现方式有多种,除了状态模式,比较常用的还有分支逻辑法和查表法。今天,我们就详细讲讲这几种实现方式,并且对比一下它们的优劣和应用场景。
  • 案例:超级马里奥”游戏不知道你玩过没有?在游戏中,马里奥可以变身为多种形态,比如小马里奥(Small Mario)、超级马里奥(Super Mario)、火焰马里奥(Fire Mario)、斗篷马里奥(Cape Mario)等等。在不同的游戏情节下,各个形态会互相转化,并相应的增减积分。比如,初始形态是小马里奥,吃了蘑菇之后就会变成超级马里奥,并且增加 100 积分。
  • 实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的**“状态”,游戏情节(比如吃了蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”**。比如,吃蘑菇这个事件,会触发状态的转移:从小马里奥转移到超级马里奥,以及触发动作的执行(增加 100 积分)。
    在这里插入图片描述

状态机实现方式一:分支逻辑法
状态机实现方式二:查表法
状态机实现方式三:状态模式

针对状态机,今天我们总结了三种实现方式。
第一种实现方式叫分支逻辑法。利用 if-else 或者 switch-case 分支逻辑,参照状态转移图,将每一个状态转移原模原样地直译成代码。对于简单的状态机来说,这种实现方式最简单、最直接,是首选。

第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适。通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性。

第三种实现方式叫状态模式。对于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,我们首选这种实现方式。

策略模式

案例:
鸭子问题:编写鸭子项目,具体要求如下:1.有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如 叫、飞行等) 2.显示鸭子的信息

极客时间
策略模式的原理与实现

  • 策略模式。在实际的项目开发中,这个模式也比较常用。最常见的应用场景是,利用它来避免冗长的 if-else 或 switch 分支判断。不过,它的作用还不止如此。它也可以像模板模式那样,提供框架的扩展点等等。
  1. 策略的定义
  2. 策略的创建
  3. 策略的使用

如何利用策略模式避免分支判断?

  • 实际上,能够移除分支判断逻辑的模式不仅仅有策略模式,后面我们要讲的状态模式也可以。对于使用哪种模式,具体还要看应用场景来定。 策略模式适用于根据不同类型的动态,决定使用哪种策略这样一种应用场景。
  • 案例:将不同类型订单的打折策略设计成策略类
  • 策略模式用来解耦策略的定义、创建、使用。实际上,一个完整的策略模式就是由这三个部分组成的。

结合“给文件排序”这样一个具体的例子

  • 问题与解决思路
  • 代码实现与分析
  • 代码优化与重构

职责链模式

案例:
OA系统采购审批
在这里插入图片描述
应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web中Tomcat对Encoding的处理、拦截器

极客时间

  • 前几节课中,我们学习了模板模式、策略模式,今天,我们来学习职责链模式。这三种模式具有相同的作用:复用和扩展,在实际的项目开发中比较常用,特别是框架开发中,我们可以利用它们来提供框架的扩展点,能够让框架的使用者在不修改框架源码的情况下,基于扩展点定制化框架的功能。

职责链模式的原理和实现

  • 将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
  • 第一种实现方式如下所示。其中,Handler 是所有处理器类的抽象父类,handle() 是抽象方法
  • 我们再来看第二种实现方式,代码如下所示。这种实现方式更加简单

职责链模式的应用场景举例

  • 对于支持 UGC(User Generated Content,用户生成内容)的应用(比如论坛)来说,用户生成的内容(比如,在论坛中发表的帖子)可能会包含一些敏感词(比如涉黄、广告、反动等词汇)。针对这个应用场景,我们就可以利用职责链模式来过滤这些敏感词。

Servlet Filter
Spring Interceptor

  • 它们不同之处在于,Servlet Filter 是 Servlet 规范的一部分,实现依赖于 Web 容器。Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架来提供实现。客户端发送的请求,会先经过 Servlet Filter,然后再经过 Spring Interceptor,最后到达具体的业务代码中。我画了一张图来阐述一个请求的处理流程,具体如下所示。
    在这里插入图片描述
  • 在项目中,我们该如何使用 Spring Interceptor 呢?我写了一个简单的示例代码,如下所示。LogInterceptor 实现的功能跟刚才的 LogFilter 完全相同,只是实现方式上稍有区别。LogFilter 对请求和响应的拦截是在 doFilter() 一个函数中实现的,而 LogInterceptor 对请求的拦截在 preHandle() 中实现,对响应的拦截在 postHandle() 中实现。
  • 职责链模式常用在框架开发中,用来实现框架的过滤器、拦截器功能,让框架的使用者在不需要修改框架源码的情况下,添加新的过滤拦截功能。这也体现了之前讲到的对扩展开放、对修改关闭的设计原则。

看一看:
设计模式学习笔记(总结篇:模式分类)
Java知识体系最强总结(2020版)–设计模式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值