Java设计模式

  • 工厂模式(Spring的BeanFactory,ApplicationContext)
    • 工厂模式中的三种:简单工厂模式、工厂方法模式、抽象工厂模式;实现了创建者和调用者的分离,调用者不需要知道具体的创建者是什么类,只需要知道工厂的接口以及自己想要的产品名称,就可以进行调用得到想要的产品;
    • 简单工厂模式:简单工厂模式也称为静态工厂模式,工厂类一般采用静态方法,根据接收的参数不同来确定返回对象实例,Spring 中的 BeanFactory 就是简单工厂模式的体现,根据传入 一个唯一的标识来获得 Bean 对象。普通工厂模式就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建
  • 单例模式
    • 一. 单例模式概述
      • 单例模式(Singleton),是一种常用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候,整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。
      • 比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,显然,这种方式简化了在复杂环境下的配置管理。特别地,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。事实上,这些应用都或多或少具有资源管理器的功能。例如,每台计算机可以有若干个打印机,但只能有一个 Printer Spooler (单例) ,以避免两个打印作业同时输出到打印机中。再比如,每台计算机可以有若干通信端口,系统应当集中(单例) 管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。
      • 单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。
    • 二. 单例模式及其单线程环境下的经典实现
      • 1、单例模式理论基础
        • 定义: 确保一个类只有一个实例,并为整个系统提供一个全局访问点 (向整个系统提供这个实例)。
        • 类型: 创建型模式

          • 类图分为三部分,依次是类名、属性、方法;
          • 以<<开头和以>>结尾的为注释信息;
          • 修饰符+代表public,-代表private,#代表protected,什么都没有代表包可见;
          • 带下划线的属性或方法代表是静态的。
        • 三要素:
          • 私有的构造方法;
          • 指向自己实例的私有静态引用;
          • 以自己实例为返回值的静态的公有方法。
      • 2、单线程环境下的两种经典实现
        • 立即加载 : 在类加载初始化的时候就主动创建实例 ;饿汉式单例(更好)

        • 延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。懒汉式单例(在多线程可能会创建多个对象)

      • 3、单例模式的优点
        • 在内存中只有一对象,节省内存空间;
        • 避免频繁的创建销毁对象,可以提高性能;
        • 避免对共享资源的多重占用,简化访问;
        • 为整个系统提供一个全局访问点。
      • 4、单例模式的使用场景
        • 由于单例模式具有以上优点,并且形式上比较简单,所以是日常开发中用的比较多的一种设计模式,其核心在于为整个系统提供一个唯一的实例,其应用场景包括但不仅限于以下几种:
        • 有状态的工具类对象;
        • 频繁访问数据库或文件的对象;
    • 三. 多线程环境下单例模式的实现
      • 1、饿汉式单例天生就是线程安全的:类加载的方式是按需加载,且只加载一次。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。换句话说,在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。因此就说,饿汉式单例天生就是线程安全的。
      • 2、传统的懒汉式单例为什么是非线程安全:上面发生非线程安全的一个显著原因是,会有多个线程同时进入 if (singleton2 == null) {…} 语句块的情形发生。当这种这种情形发生后,该单例类就会创建出多个实例,违背单例模式的初衷。因此,传统的懒汉式单例是非线程安全的。
      • 3、实现线程安全的懒汉式单例的几种正确姿势
        • 1)、同步延迟加载 — synchronized方法:

        • 2)、同步延迟加载 — synchronized块

        • 3)、同步延迟加载 — 使用内部类实现延迟加载

    • 四. 单例模式与双重检查(Double-Check idiom)
      • 使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率:如上述代码所示,为了在保证单例的前提下提高运行效率,我们需要对 singleton3 进行第二次检查,目的是避开过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同步获取锁了)。这种做法无疑是优秀的,但是我们必须注意一点:必须使用volatile关键字修饰单例引用。操作3不是一个原子操作,他至少包含3个操作,1、开辟空间;2、对空间进行初始化 ;3、将该空间复制给变量(volatile---依旧可以重排,只不过以volatile分界,上下不能调换位置);如果此操作的顺序行发送改变,2-3对调, 等下次询问 singleton3==null,他已经将空间复制给变量,所以他不是空,就直接返回该对象引用,使用了一个没有被初始化的空间,系统崩溃;

    • 六. 小结
      • 本文首先介绍了单例模式的定义和结构,并给出了其在单线程和多线程环境下的几种经典实现。特别地,我们知道,传统的饿汉式单例无论在单线程还是多线程环境下都是线程安全的,但是传统的懒汉式单例在多线程环境下是非线程安全的。为此,我们特别介绍了五种方式来在多线程环境下创建线程安全的单例,包括:
        • 使用synchronized方法实现懒汉式单例;
        • 使用synchronized块实现懒汉式单例;
        • 使用静态内部类实现懒汉式单例;
        • 使用双重检查模式实现懒汉式单例;
      • 我们可以总结出,要想实现效率高的线程安全的单例,我们必须注意以下两点:
        • 尽量减少同步块的作用域;
        • 尽量使用细粒度的锁。
  • Builder 模式
    • 一. 动机
      • 当我们需要创建一个复杂的对象时,使用静态工厂或者构造器的方式就显得特别笨拙和丑陋,因为它们有个共同的局限性:它们都不能很好地扩展到大量的可选参数。考虑用一个Person类来描述一个人,除了姓名,性别,生日,邮箱等必要的属性外,还有很多可选的属性,比如:身高,学历,绰号,体重,通讯地址等等。对于这样的类,我们应该如何创建对象呢?无论是常见的重叠构造器模式还是JavaBeans模式,它们都不能很好地解决这类问题,而我们本文所着重阐述的Builder模式则正好是解决此类问题的利剑。为了更深入的了解Builder模式所带来的好处,我们先分别采用重叠构造器模式和JavaBeans模式来解决上述问题。
    • 二. 使用重叠构造器模式创建复杂对象(构造器模式)
      • 在这种模式下,我们提供的第一个构造器只有必要的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推,最后一个构造器含有所有参数。
      • 使用这种模式创建对象时,存在一下几点不足:
        • 灵活性很差:如果客户端只想创建一个给定姓名,性别,生日,邮箱和体重的人,那么他将调用的构造函数无意中就“被迫”设置了他本不想设置的一些参数。
        • 代码难以编写与阅读:当属性有很多的时候,代码不但看起来很丑陋,而且极易出错。试想,若客户端不小心颠倒了参数列表中两个参数的顺序 (例如,颠倒了参数“email”和“edu”),编译器也不会出错,但是在运行时就会出现错误的行为,并且这种错误难以发现。
    • 三. 使用JavaBeans模式创建复杂对象(get,set模式)
      • 其本身也存在这一些固有的缺点,比如:
        • Setter的存在妨碍了其成为不可变类的可能:这样,在并发环境下,我们就不得不考虑其线程安全性;
        • 代码丑陋且对象易处于不一致状态:上面创建对象的方式也比较丑陋,同时由于对象的构造过程分为若干个函数调用,所以容易导致对象处于不一致状态。
    • 四. 使用Builder模式创建复杂对象
      • 使用Builder模式创建复杂对象,不但可以避免上述两种方式的缺点,而且还能兼顾们各自的优点。该模式的内涵是:不直接生成想要的对象,而是让客户端利用 所有必要的参数 构造一个Builder对象,然后在此基础上,调用类似于Setter的方法来设置每个可选参数,最后通过调用无参的build()方法来生成不可变对象。一般地,所属Builder是它所构建类的静态成员类。代码如下:

      • 我们可以通过下面的方式来创建一个Person对象:

      • 显而易见,使用这种方式创建对象不但灵活而且易于阅读,且不易出错。总的来说,这种模式具有以下特点:

        • Person类的构造方法是私有的: 也就是说,客户端不能直接创建User对象;
        • Person类是不可变的: 所有的属性都被final修饰,在构造方法中设置参数值,并且不对外提供setters方法;
        • Builder模式的高可读性: Builder模式使用了链式调用,可读性更佳。
        • Builder对象与目标对象的异同: Person与Builder拥有共同的属性,并且Builder内部类构造方法中只接收必传的参数,同时只有这些必传的参数使用了final修饰符。
    • 五. Builder模式中的参数约束与线程安全性
      • 我们知道,Person对象是不可变的,因此是线程安全的;但是,Builder对象并不具有线程安全性。因此,当我们需要对Person对象的参数强加约束条件时,我们应该可以对builder()方法中所创建出来的Person对象进行检验,即我们可以将builder()方法进行如下重写:

  • 策略模式(比较器Comparator)
    • 一. 引子
      • 在Java的集合框架中,经常需要通过构造方法传入一个比较器Comparator,或者创建比较器传入Collections的静态方法中作为方法参数,进行比较排序等,这其实就是策略模式的应用。例如,在数组工具类 Arrays 存在一个这样的方法:

      • 该方法旨在根据指定比较器对指定对象数组进行排序。我们知道,程序设计的基本目标是:将保持不变的事物与会发生改变的事物相分离(封装变化)。在这里,不变的是通用的排序算法,变化的是各种对象相互比较的方式。因此,JDK不是将进行比较的代码编写成不同的子程序,而是借用策略模式进行处理。通过使用策略可以将“会发生变化的代码”封装在单独的类中(策略对象),你可以将不同的策略对象总是传递给相同的代码,这些代码将使用策略来完成其算法。
    • 二. 定义与结构
      • 策略模式属于对象的行为模式,其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
      • 1、定义
        • 定义 : 定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
        • 策略模式体现的两个基本的面向对象设计思想:封装变化 和 面向接口编程。
      • 2、结构
        • 策略模式主要包含三个角色,即 抽象策略角色 、具体策略角色 以及 环境角色,如下图所示:

          • 抽象策略角色:抽象角色,通常由一个接口或抽象类实现,此角色给出所有的具体策略类所需的接口;
          • 具体策略角色:包装了相关的算法和行为;
          • 环境角色:持有一个Strategy的引用。
    • 四. 总结
      • 1、策略模式的特征
        • 策略模式的重心 : 策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性;
        • 算法的平等性 : 策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。所以,可以这样描述这一系列策略算法:策略算法是相同行为的不同实现;
        • 运行时策略的唯一性 : 运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个;
        • 公有的行为 : 经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色 Strategy 类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口(Java 1.8 以后允许接口中有默认方法)。
      • 2、策略模式的优点
        • (1). 策略模式提供了管理相关的算法族的办法,策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
        • (2). 使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。
      • 3、策略模式的缺点
        • (1). 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须了解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。
        • (2). 由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。
  • 装饰器模式(应用场景:若想对一个类从不同角度进行功能扩展,例如java.io中,InputStream是一个抽象类,标准类库中提供了FileInputStream\ByteArrayInputStream等各种不同的子类,分别从不同角度对InputStream进行了功能扩展。这些不同的实现类其构造函数的输入均为InputStream(的实现类),然后对InputStream添加不同层次的逻辑,从而实现不同的功能,这就是装饰。)
    • (1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
    • (2) 装饰对象包含一个真实对象的引用(reference)
    • (3) 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
    • (4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。
  • 适配器模式:
    • 有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行了。
  • 观察者模式(各种事件监听器)
  • Java代理模式及其应用
    • 一. 代理与代理模式
      • 1). 代理
        • 代理模式其实很常见,比如买火车票这件小事:黄牛相当于是我们本人的的代理,我们可以通过黄牛买票。通过黄牛买票,我们可以避免与火车站的直接交互,可以省很多事,并且还能享受到黄牛更好的服务(如果钱给够的话)。在软件开发中,代理也具有类似的作用,并且一般可以分为静态代理和动态代理两种,上述的这个黄牛买票的例子就是静态代理。
        • 那么,静态代理与动态代理的区别是什么呢?所谓静态代理,其实质是自己手写(或者用工具生成)代理类,也就是在程序运行前就已经存在的编译好的代理类。但是,如果我们需要很多的代理,每一个都这么去创建实属浪费时间,而且会有大量的重复代码,此时我们就可以采用动态代理,动态代理可以在程序运行期间根据需要动态的创建代理类及其实例来完成具体的功能。总的来说,根据代理类的创建时机和创建方式的不同,我们可以将代理分为静态代理和动态代理两种形式。
        • 就像我们也可以自己直接去买票一样,在软件开发中,方法直接调用就可以完成功能,为什么非要通过代理呢?原因是采用代理模式可以有效的将具体的实现(买票过程)与调用方(买票者)进行解耦,通过面向接口进行编码完全将具体的实现(买票过程)隐藏在内部(黄牛)。此外,代理类不仅仅是一个隔离客户端和目标类的中介,我们还可以借助代理来在增加一些功能,而不需要修改原有代码,是开闭原则的典型应用。代理类主要负责为目标类预处理消息、过滤消息、把消息转发给目标类,以及事后处理消息等。代理类与目标类之间通常会存在关联关系,一个代理类的对象与一个目标类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用目标类的对象的相关方法,来提供特定的服务。总的来说,
        • 代理对象存在的价值主要用于拦截对真实业务对象的访问(我们不需要直接与火车站打交道,而是把这个任务委托给黄牛);
        • 代理对象应该具有和目标对象(真实业务对象)相同的方法,即实现共同的接口或继承于同一个类;
        • 代理对象应该是目标对象的增强,否则我们就没有必要使用代理了。
        • 事实上,真正的业务功能还是由目标类来实现,代理类只是用于扩展、增强目标类的行为。例如,在项目开发中我们没有加入缓冲、日志这些功能而后期需要加入,我们就可以使用代理来实现,而没有必要去直接修改已经封装好的目标类。
      • 2). 代理模式
        • 代理模式是较常见的模式之一,在许多框架中经常见到,比如Spring的面向切面的编程,MyBatis中缓存机制对PooledConnection的管理等。代理模式使得客户端在使用目标对象的时候间接通过操作代理对象进行,代理对象是对目标对象的增强

    • 二. 静态代理
      • 静态代理的实现模式一般是:首先创建一个接口(JDK代理都是面向接口的),然后创建具体实现类来实现这个接口,然后再创建一个代理类同样实现这个接口,不同之处在于,具体实现类的方法中需要将接口中定义的方法的业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层。
        • 我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?
        • 电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告,现在用代码来进行模拟。
      • 1). 抽象主题(接口)
        • 首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为Movie,代表电影这个主题。

      • 2). 被代理角色(目标类)与代理角色(代理类)
        • 然后,我们要有一个真正的实现这个 Movie 接口的类和一个只是实现接口的代理类。

        • 这个表示真正的影片。它实现了 Movie 接口,play()方法调用时,影片就开始播放。那么代理类呢?Cinema 就是代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。也就是说,Cinema(代理类) 与 RealMovie(目标类) 都可以播放电影,但是除此之外,Cinema(代理类)还对“播放电影”这个行为进行进一步增强,即增加了额外的处理,同时不影响RealMovie(目标类)的实现

      • 3). 客户端:
        • 现在可以看到,代理模式可以在不修改被代理对象的基础上(符合开闭原则),通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。如前所述,由于Cinema(代理类)是事先编写、编译好的,而不是在程序运行过程中动态生成的,因此这个例子是一个静态代理的应用。

    • 三. 动态代理
      • 1). 抽象主题(接口)
        • 同样地,首先得有一个接口,通用的接口是代理模式实现的基础。

      • 2). 被代理角色(目标类)
        • 然后,我们要有一个真正的实现这个 Subject 接口的类,以便代理。

      • 3). 代理角色(代理类)与客户端
        • 在动态代理中,代理类及其实例是程序自动生成的,因此我们不需要手动去创建代理类。在Java的动态代理机制中,InvocationHandler(Interface)接口和Proxy(Class)类是实现我们动态代理所必须用到的。事实上,Proxy通过使用InvocationHandler对象生成具体的代理代理对象,下面我们看一下对InvocationHandler接口的实现:

        • 在实现了InvocationHandler接口后,我们就可以创建代理对象了。在Java的动态代理机制中,我们使用Proxy类的静态方法newProxyInstance创建代理对象,如下:

      • 4). JDK中InvocationHandler接口与Proxy类
        • (1). InvocationHandler接口
          • InvocationHandler 是一个接口,官方文档解释说:每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。

        • (2). Proxy类
          • JDK通过 Proxy 的静态方法 newProxyInstance 动态地创建代理,该方法在Java中的声明如下;事实上,Proxy 动态产生的代理对象调用目标方法时,代理对象会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。

    • 四、小结
      • 1、实现动态代理的关键技术是反射;
      • 2、代理对象是对目标对象的增强,以便对消息进行预处理和后处理;
      • 3、InvocationHandler中的invoke()方法是代理类完整逻辑的集中体现,包括要切入的增强逻辑和进行反射执行的真实业务逻辑;
      • 4、使用JDK动态代理机制为某一真实业务对象生成代理,只需要指定目标接口、目标接口的类加载器以及具体的InvocationHandler即可。
      • 5、JDK动态代理的典型应用包括但不仅限于AOP、RPC、Struts2、Spring等重要经典框架。
  • 责任链模式
    • 一. 引子 —— 责任链模式提出动机与应用场景
      • 责任链模式在我们生活中有着诸多的应用。比如,在我们玩打扑克的游戏时,某人出牌给他的下家,下家会看看手中的牌,如果要不起上家的牌,则将出牌请求再转发给他的下家,其下家再进行判断,如此反复。一个循环下来,如果其他人都要不起该牌,则最初的出牌者可以打出新的牌。在这个过程中,扑克牌作为一个请求沿着一条链( 环)在传递,每一位纸牌的玩家都可以处理该请求。在设计模式中,我们也有一种专门用于处理这种 请求链式传递问题 的模式,即 责任链模式 (Chain of Responsibility Pattern)。
      • 此外,采购的分级审批问题也是责任链模式的一个应用典范。我们知道,采购审批往往是分级进行的。也就是说,其常常根据采购金额的不同由不同层次的主管人员来审批。例如,主任可以审批 5 万元以下(不包括 5 万元)的采购单,副董事长可以审批 5 万元至 10 万元(不包括 10 万元)的采购单,董事长可以审批 10 万元至 50 万元(不包括 50 万元)的采购单,50 万元及以上的采购单就需要开董事会讨论决定。此案例如图所示:

    • 二. CoR 模式概述
      • 很多情况下,在一个软件系统中可以处理某个请求的对象不止一个。例如上面提到的采购审批子系统,主任、副董事长、董事长和董事会都可以处理采购单,他们可以构成一条处理采购单的链式结构,采购单(可以看作是要处理的信息)沿着这条链进行传递,这条链就称为责任链。责任链可以是一条直线、一个环或者一个树形结构,最常见的职责链是直线型,即沿着一条单向的链来传递请求,如下图所示。链上的每一个对象都是请求处理者,责任链模式可以将请求的处理者组织成一条链,并让请求沿着链传递,由链上的处理者对请求进行相应的处理。在此过程中,客户端实际上无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,从而实现请求发送者和请求处理者解耦。
    • 三. CoR 模式定义与结构
      • 顾名思义,责任链模式(Chain of Responsibility Pattern) 指的是用一系列类(classes)去处理一个请求request,这些类之间是一个松散的耦合,它们之间的唯一联系就是在它们之间传递的 request。也就是说,如果来了一个请求,那么A类先处理;若A类处理不了,就传递到B类进行处理;如果B类也处理不了,就传递给C类进行处理,这些请求处理类像一个链条(chain)一样,请求在这条链上不断的传递下去,直至其被处理。
      • 1、责任链模式的定义
        • 定义: 使多个对象都有机会处理请求,从而避免请求的发送者与请求处理者耦合在一起。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
        • 类型: 对象行为型模式
        • 实质: 责任链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,从而实现请求发送者与请求处理者的解耦。
      • 2、责任链模式的结构
        • 我们可以从责任链模式的结构图中看到,具体的请求处理者可以有多个,并且所有的请求处理者均具有相同的接口(继承于同一抽象类)。 责任链模式主要包含如下两个角色:
          • Handler(抽象处理者):处理请求的接口,一般设计为具有抽象请求处理方法的抽象类,以便于不同的具体处理者进行继承,从而实现具体的请求处理方法。此外,由于每一个请求处理者的下家还是一个处理者,因此抽象处理者本身还包含了一个本身的引用( successor)作为其对下家的引用,以便将处理者链成一条链;
          • ConcreteHandler(具体处理者):抽象处理者的子类,可以处理用户请求,其实现了抽象处理者中定义的请求处理方法。在具体处理请求时需要进行判断,若其具有相应的处理权限,那么就处理它;否则,其将请求 转发 给后继者,以便让后面的处理者进行处理。
          • 在责任链模式里,由每一个请求处理者对象对其下家的引用而连接起来形成一条请求处理链。请求将在这条链上一直传递,直到链上的某一个请求处理者能够处理此请求。事实上,发出这个请求的客户端并不知道链上的哪一个请求处理者将处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。
    • 四. CoR 模式的灵活性
      • 1、CoR 模式的可扩展性
        • 我们在上一节中已经给出了使用责任链模式解决审批问题的完整解决方案。如果需要在系统增加一个新的具体处理者,比如增加一个经理(Manager)角色可以审批 5 万元至 8 万元(不包括 8 万元)的采购单,则只需要编写一个抽象处理者类 Approver 的新的子类 Manager,并实现在 Approver 类中定义的抽象处理方法,如果采购金额大于等于 8 万元,则将请求转发给下家
      • 2、CoR 模式的优点
        • 降低耦合度,使请求的发送者和接收者解耦,便于灵活的、可插拔的定义请求处理过程;
        • 简化、封装了请求的处理过程,并且这个过程对客户端而言是透明的,以便于动态地重新组织链以及分配责任,增强请求处理的灵活性;
        • 在上面的示例中,这两个优点主要体现为如下两点:
          • 第一,我们可以随时改变内部的请求处理规则,每个请求处理者都可以去动态地指定他的继任者。也就是说,主任完全可以跳过副董事长直接找到董事长进行审批。
          • 第二,我们可以从职责链任何一个节点开始,即如果主任不在,可以直接去找副董事长,责任链还会继续,不会有任何影响。
        • 事实上,如果我们不使用责任链,我们需要和公司中的每一个层级都发生耦合关系,反映在代码上就是我们需要在一个类中写上很多丑陋的 if….else 语句。如果我们使用了责任链,相当于我们面对的是一个黑箱子,我们只需要认识其中的一个部门,然后让黑箱内部去负责处理和传递就好了。
  • 迭代器模式(ArrayList等集合框架中的迭代器):提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,
    • Iterator(抽象迭代器):它定义了访问和遍历元素的接口,声明了用于遍历数据元素的方法,例如:用于获取第一个元素的first()方法,用于访问下一个元素的next()方法,用于判断是否还有下一个元素的hasNext()方法,用于获取当前元素的currentItem()方法等,在具体迭代器中将实现这些方法。
    • ConcreteIterator(具体迭代器):它实现了抽象迭代器接口,完成对聚合对象的遍历,同时在具体迭代器中通过游标来记录在聚合对象中所处的当前位置,在具体实现时,游标通常是一个表示位置的非负整数。
    • Aggregate(抽象聚合类):它用于存储和管理元素对象,声明一个createIterator()方法用于创建一个迭代器对象,充当抽象迭代器工厂角色。
    • ConcreteAggregate(具体聚合类):它实现了在抽象聚合类中声明的createIterator()方法,该方法返回一个与该具体聚合类对应的具体迭代器ConcreteIterator实例。
    • 在迭代器模式中,提供了一个外部的迭代器来对聚合对象进行访问和遍历,迭代器定义了一个访问该聚合元素的接口,并且可以跟踪当前遍历的元素,了解哪些元素已经遍历过而哪些没有。迭代器的引入,将使得对一个复杂聚合对象的操作变得简单。
    • 迭代器模式的主要优点如下:
      • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
      • 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
      • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求。
    • 迭代器模式的主要缺点如下:
      • 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
      • 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展,例如JDK内置迭代器Iterator就无法实现逆向遍历,如果需要实现逆向遍历,只能通过其子类ListIterator等来实现,而ListIterator迭代器无法用于操作Set类型的聚合对象。在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是件很容易的事情。
    • 适用场景:
      • 访问一个聚合对象的内容而无须暴露它的内部表示。将聚合对象的访问与内部数据的存储分离,使得访问聚合对象时无须了解其内部实现细节。
      • 需要为一个聚合对象提供多种遍历方式。
      • 为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口。
  • 生产者消费者模式(消息队列)要能手撕
    • 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值