23种设计模式(JAVA语言)

设计模式七大原则

1、七大设计原则核心思想

     1)单一职责原则

            对类来说的,即一个类应该只负责一项职责,如类A负责两个不同的职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2

              单一职责原则注意事项和细节:

                 降低类的复杂度,一个类只负责一项职责

                 提高类的可读性,可维护性

                 降低变更引起的风险

                 通常情况下,我们应当遵循单一职责原则,只有逻辑足够简单,才可以在代码级违反单                  一职责原则,只有类中方法数量足够少,才可以在方法级别保持单一职责原则

     2)接口隔离原则

             客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上

             类A通过接口Interface1依赖B,类C通过接口Interface1依赖类D,如果接口Interface1对于            类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法

            按接口隔离原则处理

                将接口Interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关                  系。也就是采用接口隔离原则

 

     3)依赖倒转原则

         高层模块不应该依赖低层模块,二者应该依赖其抽象

         抽象不应该依赖细节,细节应该依赖抽象

         依赖倒转(倒置)的中心思想是面向接口编程

         依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多,

       以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽         象类,细节就是具体的实现类

         使用接口或者抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交         给他们的实现类去完成

     4)里氏替换原则

        在编程中使用继承要遵循里氏替换原则,在子类中尽量不要重写父类的方法,所有引用基类          的地方必须能透明地使用其子类的对象。

        里氏替换原则在适当的情况下,可以通过聚合,组合,依赖来解决问题

        通常的作法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依          赖,聚合,组合等关系代替

     5)开闭原则ocp

         一个软件实体如类,模块和函数应该对扩展开放,对修改关闭,用抽象构建框架,用实现扩         展细节

     6)迪米特法则

         对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部,对外提供public方法,不对外泄露任何信息

         直接的朋友:每说这两个对象之间是朋友关系,耦合的方式很多,依赖、关联个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就、组合、聚合等。其中我们称出现成员变量、方法参数、方法返回值中的类为直接朋友,而出现在局部变量中的类不是直接朋友,也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

     7)合成复用原则

        原则是尽量使用合成/聚合的方式,而不是使用继承

        依赖

             B类中方法的参数是A类型

          

         聚合

               B类中有A类型的成员变量,通过set方法赋值

          

         组合

               B类中有A类型的成员变量,直接实例化A类赋值给成员变量

          

 设计模式类型

1)创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式

2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

3)行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介模式、备忘录模式、解释器模式、状态模式、策略模式,职责链模式(责任链模式)

先看几个经典的面试题

原型设计模式

原型设计模式问题:

1、请使用UML类图画出原型模式核心角色

2、原型设计模式的深拷贝和浅拷贝是什么,并写出深拷贝的两种方式的源码(重写clone方法实现深拷贝,使用序列化来实现深拷贝

3、在Spring框架中哪里使用原型模式,并对源码进行分析

     bean.xml

     <bean id="id01" class="com.hzz.spring.bean.Monster" scope="prototype"/>

     Spring中原型bean的创建就是原型模式的应用

    

<bean id="id01" class="com.hzz.spring.bean.Monster" scope="prototype">


ApplicationContext = applicationContext = new ClassPathXmlApplication("bean.xml");

//获取monster(通过id获取monster)
Object bean = applicationContext.getBean("id01");

Sysctem.out.println("bean"+bean);



@override
public Object getBean(String name) throws BeansException{

    return doGetBean(name, null, null, false);

}

2、能够以类图的说明设计原则

3、在项目实际开发中,你在哪里使用到ocp(开闭原则)原则

    ocp原则在工厂模式中用到

解释器设计模式

1、介绍解释器设计模式是什么?

2、画出解释器设计模式的UML类图,分析设计模式中的各个角色是什么?

3、请说明Spring的框架中,哪里使用到了解释器设计模式,并做源码级别的分析

        Spring框架中SpelExpressionParser就使用到解释器模式

单例设计模式

介绍

所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)

单例设计模式一共有几种实现方式,请分别用代码实现,并说明各个实现方式的优缺点

1)饿汉式 两种

2)懒汉式三种

3)双重检查

4)静态内部类

5)枚举

饿汉式(静态变量)

优缺点:

  1)优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题

  2)缺点:在类装载的时候就完成了实例化,没有达到Lazy Loading的效果,如果从始至终从未使用过这个实例,则会造成内存的浪费

饿汉式(静态代码块)

优缺点:

   1)这种方式和上面的方式类似,只不过将类的实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例,优缺点和上面一样

懒汉式(线程不安全)

优缺点:

   1)起到了Lazy Loading的效果,但是只能在单线程下使用

   2)如果在多线程下,一个线程进入if(singleton==null)判断语句,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例,所以在多线程环境下不可使用这种方式

   3)在实际开发中,不要使用这种方式

  懒汉式(线程安全) 

  优缺点:

    1)解决了线程不安全问题

    2)效率太低,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步,而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了,方法进行同步效率太低

    3)在实际开发中,不推荐使用这种方式

双重检查

    1)Double-Check概念是多线程开发中常使用到的,如代码所示,我们进行了两次if(singleton == null)检查,这样就可以保证线程安全了

    2)这样实例化代码只用执行一次,后面再次访问时,判断if(singleton == null)直接return实例化对象,也避免反复进行方法同步

    3)线程安全,延迟加载,效率较高

    4)在实际开发中,推荐使用这种单例设计模式

静态内部类

    1)这种方式采用了类装载的机制来保证初始化实例只有一个线程

    2)静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化是,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化

    3)累的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮我们保证了线程的安全性,在类进行初始化时,别的线程无法进入

    4)避免线程不安全,利用静态内部类特点实现延迟加载,效率高

     6)推荐使用

枚举

 优缺点:

     1)这借助JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象

     2)推荐使用

JDK中的Runtime用到了饿汉式静态变量内部类

单列模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

单例模式使用场景

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

桥接模式

 基本介绍

  1)桥接模式是指将现实与抽象放在两个不同的类层次中,使两个层次可以独立

  2)是一种结构型设计模式

  3)Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象与行为实现分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展

基本类图

 桥接模式在JDBC的源码分析

  1)JDBC的Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以由MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类

 桥接模式注意事项

  1)实现了抽象和实现分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统

 2)对于系统高层部分,只需要知道抽象部分和实现部分接口就可以了,其它的部分由具体业务来完成

 3)桥接模式替代多层继承方案,可以减少子类的个数降低系统的管理和维护成本

常见应用场景

   JDBC驱动程序

   银行转账系统

   转账分类:网上转账、柜台转账,ATM转账

   转账用户类型:普通用户、银卡用户、金卡用户

   消息管理

   消息类型:即时消息,延时消息

   消息分类:手机短信、邮件消息、QQ消息

装饰者设计模式

星巴克咖啡订单项目

 1)咖啡种类/订单咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)

 2)调料:Milk、Soy(豆浆)、Chocolate

 3)要求在扩展新咖啡种类时,具有良好的扩展性、改动方便、维护方便

 4)使用OO的来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合

装饰者模式定义

  装饰者模式动态的将新功能附加到对象上,在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)

装饰者模式工作原理

   1)装饰者模式就像打包一个快递

   2)Component是主体,类似前面的Drink

   3)ConcreteComponent和Decorator, ConcreteComponent是具体的主体,比如前面的各个单品咖啡

   4)在如图Component与ConcreteComponent之间,如果ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象成一个类

装饰者模式在JDK中源码分析

 public abstract class InputStream implements Closeable{}  是一个抽象类,即Component

 public class FilterInputStream extends InputStream{}  是一个装饰者类Decorator

 protected volatile InputStream in 被装饰者对象

1. InputStream是抽象类,类似我们前面讲的Drink

2. FileInputStream 是 InputStream子类,类似我们前

3. FilterInputStream 是 InputStream子类,类似我们前面的Decorator修饰者

4. DataInputStream 是FilterInputStream子类,具体的修饰者,类似前面的Milk,Soy等

5. FilterInputStream类有protected volatile IputStream in; 即含被装饰者

6. 分析得出在jdk的io体系中就使用到了装饰者模式

组合模式

例子需求

  编写程序展示一个学校院系结构,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系

      清华大学

     计算机学院

    计算机科学与技术、软件工程、网络工程

    信息工程学院

    通信工程、信息工程

基本介绍

  1)组合模式创建了对象组的树形结构,讲对象组合成树状结构,以表示“整体-部分”的层次关系

  2)组合模式依据树形结构来组合对象,用来表示部分以及整体层次

  3)这种类型的设计模式属于结构型模式

  4)组合模式使得用户对单个对象和组合对象的访问具有一致性,即组合能让客户以一致的方式处理个别对象以及组合对象

原理类图

  1)Component是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component子部件,Component可以是抽象类或者接口

  2)Leaf在组合中表示叶子节点,叶子节点没有子节点

  3)Composite非叶子节点,用于存储子部件,在Component接口中实现子部件的相关操作,比如add,remove

例子类图

HashMap中组合模式源码分析

   说明

     1.Map就是一个抽象的构建(类似我们的Component)

     2.HashMap是一个中间的构建(Composite),实现/继承了相关方法

     3.Node 是HashMap的静态内部类,类似Leaf 叶子节点,这里没有put,putAll方法 

         static class Node<K,V> implement Map.Entry<K,V>

外观模式

例子需求

  组建一个家庭影院 DVD播放器、投影仪、自动屏幕、怀绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为直接使用遥控器统筹各设备开关,开爆米花机,放下屏幕,开投影仪,开音响,开DVD,选dvd,去拿爆米花,调暗灯光,播放、观影结束后,关闭各种设备

基本介绍

  1)外观模式为子系统中的一组接口提供了一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

  2)外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节

原理类图

   1)外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象

   2)调用者(Client)外观接口的调用者

   3)子系统的集合:模块或者子系统,处理Facade对象指派的任务,他是功能的实际提供者  

外观模式在Mybatis框架中的源码分析

  1)Mybatis中的Configuration去创建MetaObject对象使用到外观模式 

 外观模式的注意事项

  1)外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性

  2)外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展

  3)通过合理的使用外观模式,可以帮我们更好的划分访问的层次

  4)当系统需要8进行分层设计时,可以帮我们更好的划分访问的层次

  享元模式

项目需求

   小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:

    1)有客户要求以新闻的形式发布

    2)有客户要求以博客的形式发布

    3)有客户希望以微信公众号的形式发布

问题分析

  1)需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费

  2)解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源

  3)对于代码来说,由于是一份实例,维护和扩展都更加容易

  4)上面的解决思路就可以使用享元模式来解决

享元模式经典应用场景就是池技术,String常量池,数据库连接池,缓冲池等等都是享元模式的应用,享元模式的池技术的重要实现方式 

原理类图

   1)FlyWeight是抽象的享元角色,它是产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现

   2)ConcreteFlyWeight是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务

   3)UnSharedConcreteFlyWeight是不可共享的角色,一般不会出现在享元工厂

   4)FlyWeightFactory享元工厂类,用于创建一个池容器(集合),同时提供从池中获取对象方法

例子原理类图

 享元模式注意事项

   1)在享元模式中“享”就表示共享,“元”表示对象

   2)系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式

   3)用唯一标识判断,如果内存中有,则返回这个唯一标识的对象,用HashMap/Hashtable存储

   4)享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率

代理模式

代理模式基本介绍

  1)为一个对象提供一个替身,以控制对这个对象的访问,即通过代理对象访问目标对象,这样做的好处是可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能

  2)被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象

  3)代理模式有不同的形式,主要有三种静态代理、动态代理(JDK代理、接口代理)、和Cglib代理(可以在内存动态的创建对象,而不需要实现接口,他是属于动态代理的范畴)

静态代理

  静态代理模式的基本介绍:

     静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类

原理类图:

Client创建TeacherDaoProxy代理对象

创建TeacherDao对象

将TeacherDao对象,交给TeacherDaoProxy对象 执行方法 

缺点:

    优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展

    缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护

动态代理

  基本介绍:

    1)代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理

    2)代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象

    3)动态代理也叫做:JDK代理,接口代理

    4)代理类所在包:java.lang.reflect.Proxy

    5) JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

           static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

Cglib代理

   1)静态代理和JDK代理模式都要求目标对象来实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何接口,这个时候可使用目标对象子类来实现--这就是Cglib代理

   2)Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理

   3)Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口,它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截

   4)在AOP编程中如何选择代理模式

      1. 目标对象需要实现接口,用JDK代理

      2.目标对象不需要实现接口,用Cglib代理

   5)Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类

在内存中动态构建子类,注意代理的类不能为final,否则报错java.lang.IllegalArgumentException

目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

实体类图:

 模板方法模式

 豆浆制作问题

   1)制作豆浆的流程:选材-->添加配料-->浸泡-->放到豆浆机打碎

   2)通过添加不同的配料,可以制作出不同口味的豆浆

   3)选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的

   4)请使用模板方法模式完成(说明:因为模板方法模式比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式)

基本介绍

  1)模板方法模式又叫模板模式,在一个抽象类公开定义了执行它的方法的模板,它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行

  2)简单说,模板方法模式定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定的步骤

原理类图

   1)AbstractClass抽象类,类中实现了模板方法,定义了算法的骨架,具体子类需要去实现,其它的抽象方法operation2,3,4

   2)ConcreteClass实现抽象方法operation2,3,4,以完成算法中特点子类的步骤

例子类图

 模板方法模式的钩子方法

  1)在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”

模板方法模式在Spring框架中的分析

  Spring IOC容器初始化时运用到模板方法模式(AbstractApplicationContext中的refresh相当于例子中的make方法,onfresh方法和postProcessBeanFactory是钩子方法)

模板方法模式注意事项

  1)基本思想:算法只存在于一个地方,也就是在父类中,容易修改,需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改

  2)实现了最大化代码复用,父类的模板方法和已实现的某些步骤会被子类继承而直接使用

  3)既统一了算法,也提供了很大的灵活性,父类的模板方法确保了算法的结构保持不变,同时由于类提供部分步骤的实现

命令模式

智能生活项目需求

  1)我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作

  2)这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电

  3)要实现一个app控制所有智能家电的需要,则每个智能家电都要提供一个统一的接口给app调用,这时就可以考虑使用命令模式

 4)命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来

 5)在我们的例子中,动作的请求在是手机app,动作的执行者是每个厂商的一个家电产品

基本介绍

  1)在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需要在程序运行时指定具体的请求接受者即可,此时可以使用命令模式来进行设计

  2)命令模式使得请求发送者与请求接受者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦

  3)在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命令),同时命令模式也支持可撤销的操作

  4)通俗易懂的理解,将军发布命令,士兵去执行,其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)

    Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象

原理类图

    1)Invoker是调用者角色

    2)Command:是命令角色,需要执行的所有命令都在这里,可以是接口或者抽象类

    3)Receiver:接收者角色,知道如何实施和执行一个请求相关的操作

    4)ConcreteCommand:将一个接收者对象与一个动作,调用接收者相应的操作,实现execute 

命令模式在Spring框架JdbcTemplate的应用

   Spring'框架的JdbcTemplate使用到了命令模式

  1)StatementCallback8接口,类似命令接口(Command)

        class QueryStatementCallback implements StatementCallback<T>, SqlProvider,匿名内部类,实现了命令接口,同时也充当命令接受者

       命令调用者是JdbcTemplate,其中execute(StatementCallback<T> action)方法中,调用action.doInstatement方法,不同的实现StatementCallback接口对象,对应不同的doInStatement实现逻辑

命令模式注意事项

  1)将发起请求的对象与执行请求的对象解耦,发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁,是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说“请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用

  2)容易设计一个命令队列,只要把命令对象放到列队,就可以多线程的执行命令

  3)容易实现对请求的撤销和重做

访问者模式

完成测评系统需求

  将观众为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如成功,失败等)

基本介绍

  1)访问者模式封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作

  2)主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题

  3)访问者模式的基本工作原理是在被访问的类里面加一个对外提供接待访问者的接口

  4)访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作“污染”这些对象的类,可以选用访问者模式解决

原理类图

   1)Visitor是抽象的访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit操作

   2)ConcreteVisitor是一个具体的访问者,实现每个有Visitor声明的操作,是每个操作实现的部分

   3)ObjectStructure能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素

   4)Element定义了一个accept方法,接收访问者对象

   5)ConcreteElement为具体元素,实现accept方法

应用案例小结

   所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行,双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型

  评分案例假设我们要添加一个Wait的状态类,考察Man类和Woman类的反应,由于使用了双分派,只需增加一个Action子类在客户端调用即可,不需要改动任何其他类的代码

访问者模式的注意事项

  1)访问者模式符合单一职责原则,让程序具有优秀的扩展性,灵活性非常高

  2)访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统

迭代器模式

具体需求

  编写程序展示一个学校院系结构,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个院系

基本介绍

  1)迭代器模式是常用的设计模式,属于行为模式

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

  3)迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即不暴露内部的结构

原理类图

   1)Iterator迭代器接口,是系统提供的,含有hasNext、next、remove

   2)ConcreteIterator具体的迭代器类,管理迭代

   3)Aggregate是一个统一的聚合接口,将客户端和具体聚合解耦

   4)ConcreteAggregate是具体的聚合,持有对象集合,提供一个方法,返回一个迭代器

   5)Client是客户端,通过Iterator和Aggregate依赖子类

    JDK的ArrayList集合中就使用了迭代器模式    

    

   1)内部类Itr充当具体实现迭代器Iterator的类,作为ArrayList内部类

   2)List就是充当接口,含有一个Iterator()方法,返回一个迭代器对象

   3)ArrayList是实现聚合接口List的子类,实现了iterator()

   4) Iterator接口系统提供

   5)迭代器模式解决了不同集合(ArrayList,LinkedList)同意遍历问题

迭代器模式的注意事项.

  优点:

    1)提供一个统一的方法遍历对象,客户不用再考虑聚合类型,使用一种方法就可以遍历对象

    2)隐藏了聚合内部结构,客户端要遍历聚合对象的时候只能取到迭代器,而不会知道聚合的具体组成

    3)提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象,而如果遍历方式改变的话,只影响到迭代器

   4)当要展示一组相似对象,或者遍历一组相同对象时使用,适合使用迭代器模式

  缺点:

    每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类

观察者模式

项目需求

  1)气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)

  2)需要设计开放型API,便于其他第三方也能接入气象站获取数据

  3)提供温度、气压和湿度的接口

  4)测量数据更新时,要能实时的通知第三方

观察者模式原理

 观察者模式类似定牛奶业务

  1)奶站/气象局:Subject

  2)用户第三方网站:Observer

   Subject:登记注册,移除和通知

      1)registerObserver 注册

      2)removeObserver移除

      3)notifyObservers() 通知所有的注册用户,根据不同需求,可以更新数据,让用户来去,也可能是实时推送,看需求而定

      观察者模式是对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,Subject通知Observer变化,比如这里的奶站是Subject,是1的一方,用户是Observer,是多的一方

原理类图

观察者模式在JDK源码分析

  1)JDK的Observable类就使用了观察者模式

  2)Observable的作用和地位等价于我们前面讲过Subject

  3)Observable是类,不是接口,类中已经实现了核心的方法,即管理Observer的方法add.. delete.. notify..

  4)Observer的作用和地位等价于我们前面讲过的Observer,有update

  5)Observable和Observer的使用方法和前面讲过的一样,只是Observable是类,通过继承来说实现观察者模式

中介者模式

  智能家庭项目

    1)智能家庭包括各种设备:闹钟、咖啡机、电视机、窗帘等

    2)主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流程为:

       闹钟响起->咖啡机开始做咖啡->窗帘自动落下->电视机开始播放

原理类图

 基本介绍

  1)中介者模式用一个中介对象来封装一系列的对象交互,中介者使各个对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互

  2)中介者模式属于行为型模式,代码易于维护

  3)比如MVC模式,C(Controller控制器) 是M(Model模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用

  1)Mediator就是抽象中介者,定义了同事对象到中介者对象的接口

  2)Colleague是抽象同事类

  3)ConcreteMediator具体的中介者对象,实现抽象方法,他需要知道所有的具体的同事类,即以一个集合来管理HashMap,并接受某个同事对象消息,完成相应的任务

  4)ConcreteColleague具体的同事类,会有很多,每个同事只知道自己的行为,而不了解其他同事类的行为(方法)但是他们都依赖中介者对象

中介者模式注意事项

  1)多个类相互耦合,会形成网状结构,使用中介者模式将网状结构分离为星型结构,进行解耦

  2)减少类间依赖,降低耦合,符合迪米特原则

  3)中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响

备忘录模式

项目实例

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

基本介绍

   1)备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态

   2)现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了,而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做退回时,可以从备忘录对象里获取原来的数据进行恢复操作

   3)备忘录模式属于行为模式

原理类图

originator:对象(需要保存状态的对象)

Memento:备忘录对象,负责保存记录,即Originator内部状态

Caretaker:守护者对象,负责保存多个备忘录对象,使用集合管理,提高效率

说明:如果希望保存多个originator对象的不同时间的状态。也可以,只需要HashMap<String,集合>

 注意事项

1) 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便的回到某个历史的状态

2)实现了信息的封装,是的用户不需要关心状态的保存细节

3)如果累的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存,这个需要注意

4)适用场景:1、后悔药    2、打游戏时的存档     3、Windows里的ctrl + z     4、IE中的后退      5、数据库的事务管理

5)为了节约内存,备忘录模式可以和原型模式使用

解释器模式

项目实例

通过解释器模式来实现四则运算,如计算a+b-c的值,具体要求

 1)先输入表达式的形式,比如a+b+c-d+e,要求表达式的字母不能重复

 2)在分别输入a,b,c,d,e的值

 3)最后求出结果

基本介绍

1)在编译器原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树,这里的词法分析器和语法分析器都可以看做解释器

2)解释器模式:是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)

3)应用场景

    应用可以讲一个需要解释执行的语言中的句子表示为一个抽象语法书

    一些重复出现的问题可以用一种简单的语言表达

   一个简单语法需要解释的场景

4)这样例子还有编译器、运算表达式,正则表达式,机器人等

原理类图

1) Context:是环境角色,含有解释器之外的全局信息

2)AbstractExpression:抽象表达式,声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享

3)TerminalExpression:为终结表达式,实现与文法中的终结符相关的解释操作

4)NonTerminalExpression:为非终结符实现解释操作

5)说明:输入Context 和TerminalExperssion信息通过Client输入即可

解释器在Spring框架中的应用

1)Spring框架中SpelExpressionParser就使用到了解释器模式

2)说明:

      Expression接口 表达式接口

      下面有不同的实现类,比如SpelExpression,或者CompositeStringExpression。根据创建的不同的Parser对象,返回不同的Expression对象

public Expression parseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
		if (context != null && context.isTemplate()) {
			return parseTemplate(expressionString, context);   //返回的就是CompositeStringExpressiono
		}
		else {
			return doParseExpression(expressionString, context);  //返回的就是SpelExpression
		}
	}

     使用得当Expression对象,调用getValue解释执行表达式,最后得到结果

解释器模式注意事项

  1)当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性

  2)应用场景:编译器、运算表达式计算、正则表达式,机器人等

  3)使用解释器可能带来的问题:解释器模式会引起类膨胀,解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低

状态模式

基本介绍

  1)状态模式:它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题,状态和行为是一一对应的,状态之间可以相互转换

  2)当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变其类

原理类图

   对原理类图的说明-即(状态模式的角色及职责)  

1)Context 类为环境角色,用于维护State实例,这个实例定义当前状态

2)State是抽象状态角色,定义一个接口封装与Context的一个特点接口相关行为

3)ConcreteState具体的状态角色,每个子类实现一个与Context的一个状态相关行为

状态模式注意事项

 1)代码有很强的可读性,状态模式将每个状态的行为封装到对应的一个类中

 2)方便维护,将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时当前是什么状态,不但会产出很多if-else语句,而且容易出错

 3)符合“开闭原则”。容易增删状态

 4)会产生很多类,每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度

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

策略模式

鸭子问题

 1)有各种鸭子(比如 野鸭、北京鸭、水鸭等,鸭子有各种行为,比如叫、飞行等)

 2)显示鸭子的信息

传统方式解决鸭子问题分析和解决方案

1)其他鸭子都继承了Duck类,所以fly让所有子类都会飞了,这是不正确的

2)上面说的1的问题,其实就是继承带来的问题,对类的局部改动,尤其超类的局部改动,会影响其他部分,会有溢出效应

3)为了改进1问题,我们可以通过覆盖fly方法来解决

4)问题又来了,如果有一个ToyDuck,这样就需要ToyDuck去覆盖Duck的所有实现的方法,解决思路--策略模式

基本介绍

1)策略模式中定义算法族,分别封装起来,让他们之间可以互相替代,此模式让算法的变化独立于使用算法的客户

2)这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三,多用组合/聚合,少用继承(客户通过组合方式使用策略)

原理类图

职责链模式

职责链模式在SpringMVC框架应用的源码分析

  说明:

      springmvc 请求的流程图中,执行了拦截器相关方法,interceptor.preHandler等等

     在处理SpringMvc请求时,使用到职责链模式还使用到适配器模式

    HandlerExecutionChain 主要负责的是请求拦截器的执行和请求处理,但是他本身不处理请求,  只是将请求分配给链上注册处理器执行,这是职责链实现方式,减少职责链本身与处理逻辑之间的耦合,规范了处理流程

   HandlerExecution 维护了HandlerInterceptor的集合,可以向其中注册相应的拦截器

职责链模式的注意事项和细节

1)将请求和处理分开,实现解耦,提高系统的灵活性

2)简化了对象,使对象不需要知道链的结构

3)性能会受到影响,特别是在链比较长的时候,因此需控制链中的最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能

4)调试不方便,采用了类似递归的方式,调试是逻辑可能比较复杂

5)最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求,请假/加薪等审批流程、Java Web中Tomcat对Encoding处理、拦截器

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值