# Design-pattern **Repository Path**: zyxscuec/Design-pattern ## Basic Information - **Project Name**: Design-pattern - **Description**: Java中23种设计模式结合代码理解 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 3 - **Created**: 2020-07-28 - **Last Updated**: 2022-04-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 设计模式 [TOC] **所有的源码请参考** [https://gitee.com/zyxscuec/Design-pattern.git](https://gitee.com/zyxscuec/Design-pattern.git) ## 一、创造型设计模式 **一共五种** ### 1.1 单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。 单例模式的类结构图 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200728160601.png) **注意:** - 1、单例类只能有一个实例。 - 2、单例类必须自己创建自己的唯一实例。 - 3、单例类必须给所有其他对象提供这一实例。 这种很常见的应用在了spring的applicationContext.xml,在Servlet中的ServletContext、ServletConfig、 #### 1.1.1 饿汉式单例模式 Spring 中 IOC 容器 ApplicationContext 本身就是典型的饿汉式单例 饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题。 - 优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。 - 缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存。 ~~~Java package com.alibaba.design.singlepattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-15:51 * 先静态后动态 * 先属性后方法 * 先上后下 */ public class HungrySingleton { private static final HungrySingleton hungrySingleton = new HungrySingleton(); private HungrySingleton(){ } public static HungrySingleton getInstance(){ return hungrySingleton; } } ~~~ 或者也可以用下面这样,在静态代码块中去实例化。 ~~~Java package com.alibaba.design.singlepattern; //饿汉式静态块单例 public class HungryStaticSingleton { private static final HungryStaticSingleton hungrySingleton; static { hungrySingleton = new HungryStaticSingleton(); } private HungryStaticSingleton(){ } public static HungryStaticSingleton getInstance(){ return hungrySingleton; } } ~~~ #### 1.1.2 懒汉式单例模式 (类加载时不初始化) ##### (1)线程不安全的情况 创建一个最简单的懒汉式单例模式,在不加synchronized的前提下是容易出现线程不安全的状况 ~~~java package com.alibaba.design.singlepattern.lazy; /** * Created by Tom. */ //懒汉式单例 //在外部需要使用的时候才进行实例化 public class LazySimpleSingleton { private LazySimpleSingleton(){} //静态块,公共内存区域 private static LazySimpleSingleton lazy = null; public /*synchronized*/ static LazySimpleSingleton getInstance(){ if(lazy == null){ lazy = new LazySimpleSingleton(); } return lazy; } } ~~~ 创建线程池,开辟线程 ~~~java package com.alibaba.design.singlepattern.test; import com.alibaba.design.singlepattern.lazy.LazySimpleSingleton; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-16:23 */ public class ThreadSingletonTest implements Runnable{ @Override public void run() { LazySimpleSingleton singleton = LazySimpleSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + " : " + singleton); } } ~~~ 测试懒汉式单例模式的线程 ~~~Java package com.alibaba.design.singlepattern.test; import com.alibaba.design.singlepattern.lazy.LazySimpleSingleton; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-16:22 */ public class LazySimpleSingletonTest { public static void main(String[] args) { System.out.println("============="); Thread t1 = new Thread(new ThreadSingletonTest()); Thread t2 = new Thread(new ThreadSingletonTest()); Thread t3 = new Thread(new ThreadSingletonTest()); Thread t4 = new Thread(new ThreadSingletonTest()); Thread t5 = new Thread(new ThreadSingletonTest()); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); System.out.println("============="); } } ~~~ 正常情况下应该是每次都会是相同的实例 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200728164210.png) 不过如果运行多次,也有一定的概率出现不同的实例 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200728163816.png) 如图所示,出现了多个不同的单例,由此可见在多线程的情况下是容易出现多个实例的,这样是不安全的。 ##### (2)线程安全的情况 ###### 1. 实例化的方法上加synchronized同步锁 为了解决这个不安全的状况,可以在实例化方法那里加上synchronized关键字 ~~~Java package com.alibaba.design.singlepattern.lazy; /** * Created by Tom. */ //懒汉式单例 //在外部需要使用的时候才进行实例化 public class LazySimpleSingleton { private LazySimpleSingleton(){} //静态块,公共内存区域 private static LazySimpleSingleton lazy = null; public synchronized static LazySimpleSingleton getInstance(){ if(lazy == null){ lazy = new LazySimpleSingleton(); } return lazy; } } ~~~ 然后我开辟了100个线程随机测试,测试多次每次得到的实例对象都是同一个,解决了线程不安全的问题 ~~~Java package com.alibaba.design.singlepattern.test; import com.alibaba.design.singlepattern.lazy.LazySimpleSingleton; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-16:22 */ public class LazySimpleSingletonTest { public static void main(String[] args) { System.out.println("============="); // Thread t1 = new Thread(new ThreadSingletonTest()); // Thread t2 = new Thread(new ThreadSingletonTest()); // Thread t3 = new Thread(new ThreadSingletonTest()); // Thread t4 = new Thread(new ThreadSingletonTest()); // Thread t5 = new Thread(new ThreadSingletonTest()); // t1.start(); // t2.start(); // t3.start(); // t4.start(); // t5.start(); for (int i = 0; i < 100; i++) { Thread t = new Thread(new ThreadSingletonTest()); t.start(); } System.out.println("============="); } } ~~~ ![](https://gitee.com/zyxscuec/image/raw/master//img/20200728212320.png) ###### 2. 双检锁/双重校验锁(DCL,即 double-checked locking) **JDK 版本:JDK1.5 起** **是否 Lazy 初始化:是** **是否多线程安全:是** **实现难度:较复杂** **描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。** getInstance() 的性能对应用程序很关键。 在私有成员变量的时候加上volatile关键字,初始化赋值null,真正的静态实例化方法中来个双重判断 ~~~java package com.alibaba.design.singlepattern.lazy; /** *@author zhouyanxiang * @create 2020-07-2020/7/28-18:22 */ public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton lazy = null; private LazyDoubleCheckSingleton(){} public static LazyDoubleCheckSingleton getInstance(){ if(lazy == null){ synchronized (LazyDoubleCheckSingleton.class){ if(lazy == null){ lazy = new LazyDoubleCheckSingleton(); //1.分配内存给这个对象 //2.初始化对象 //3.设置lazy指向刚分配的内存地址 //4.初次访问对象 } } } return lazy; } } ~~~ ###### 3. 登记式/静态内部类 **是否 Lazy 初始化:是** **是否多线程安全:是** **实现难度:一般** **描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。** **这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟 饿汉式单例模式方式不同的是:饿汉式单例模式方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比饿汉式单例模式 方式就显得很合理。** ~~~java public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){ } public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } } ~~~ ###### 4. 效率很高且线程安全的实现方式 ~~~Java package com.alibaba.design.singlepattern.lazy; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-17:51 */ //懒汉式单例 //这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题 //完美地屏蔽了这两个缺点 //史上最牛B的单例模式的实现方式 public class LazyInnerClassSingleton { //默认使用LazyInnerClassGeneral的时候,会先初始化内部类 //如果没使用的话,内部类是不加载的 private LazyInnerClassSingleton(){ if(LazyHolder.LAZY != null){ throw new RuntimeException("不允许创建多个实例"); } } //每一个关键字都不是多余的 //static 是为了使单例的空间共享 //保证这个方法不会被重写,重载 public static final LazyInnerClassSingleton getInstance(){ //在返回结果以前,一定会先加载内部类 return LazyHolder.LAZY; } //默认不加载 private static class LazyHolder{ private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } } ~~~ #### 1.1.3 枚举 **JDK 版本:JDK1.5 起** **是否 Lazy 初始化:否** **是否多线程安全:是** **实现难度:易** **描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。** **这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。** ~~~java package com.alibaba.design.singlepattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-21:37 */ public enum EnumSingleton { Instance; public void doSomeThing(){ System.out.println("Something has been done"); } } ~~~ #### 单例模式小结 **单例模式可以保证内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用。** **单例模式看起来非常简单,实现起来其实也非常简单。** **经验之谈:一般情况下,不建议使用线程不安全的和直接在实例化上加上synchronized关键字的懒汉方式,建议使用饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 3 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用第 2 种双检锁方式。** ### 1.2 工厂模式 #### 1.2.1 简单工厂模式 #### ##### (1)概念 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。 ##### (2)适用场景 我们明确地计划不同条件下创建不同实例时使用工厂模式。 **使用场景:** 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。 **注意事项:**作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。 ##### (3)代码示例 整体类图 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730175024.png) 首先创建抽象类交通工具类:Vehicle ~~~Java package com.alibaba.design.factorypattern.simplefactory; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-9:49 */ public abstract class Vehicle { private String name; public Vehicle(String name) { this.name = name; System.out.println(name); } @Override public String toString(){ return "Vehicle" + name; } abstract public Vehicle newInstance(); } ~~~ 然后分别创建三个实体工具类Car、Bike、Truck - Car ``` package com.alibaba.design.factorypattern.simplefactory; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-9:50 */ public class Car extends Vehicle { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public Car(String name) { super(name); System.out.println(name); } @Override public String toString(){ return "Car:" + name; } @Override public Vehicle newInstance() { return new Car("Car ..."); } } ``` - Bike ``` package com.alibaba.design.factorypattern.simplefactory; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-10:06 */ public class Bike extends Vehicle { public Bike(String name) { super(name); } @Override public Vehicle newInstance() { return new Bike("Bike ..."); } } ``` - Truck ``` package com.alibaba.design.factorypattern.simplefactory; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-10:07 */ public class Truck extends Vehicle { public Truck(String name) { super(name); } @Override public Vehicle newInstance() { return new Truck("Truck ..."); } } ``` - 创建交通工具工厂类VehicleFactory 避免反射机制,使用注册新Vehicle类的类似工厂类,不再将类添加到map对象中,而是将要注册得到每种对象实例添加其中。每个产品类都能够创造自己的实例。 ``` package com.alibaba.design.factorypattern.simplefactory; import java.util.HashMap; import java.util.Map; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-9:47 */ public class VehicleFactory { private Map registeredProducts = new HashMap<>(); public Vehicle createVehicle(String vehicleName){ return registeredProducts.get(vehicleName).newInstance(); } public void registerVehicle(String vehicleName,Vehicle vehicle){ registeredProducts.put(vehicleName,vehicle); } } ``` - 客户端测试类 ``` package com.alibaba.design.factorypattern.simplefactory; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-9:54 */ public class Test { public static void main(String[] args) { Vehicle vehicle = new Vehicle("A Car") { @Override public Vehicle newInstance() { return new Car(" audi "); } }; // Vehicle vehicle2 = new Car("A Car"); // System.out.println(vehicle.toString()); // System.out.println(vehicle2.toString()); } } ``` ##### (4)模式在源码中的体现 在JDK源码中 ,java.util.Calendar使用了工厂模式的简单工厂模式 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730181003.png) ``` public static Calendar getInstance(TimeZone zone, Locale aLocale) { return createCalendar(zone, aLocale); } private static Calendar createCalendar(TimeZone zone,Locale aLocale){ CalendarProvider provider = LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale) .getCalendarProvider(); if (provider != null) { try { return provider.getInstance(zone, aLocale); } catch (IllegalArgumentException iae) { // fall back to the default instantiation } } Calendar cal = null; if (aLocale.hasExtensions()) { String caltype = aLocale.getUnicodeLocaleType("ca"); if (caltype != null) { switch (caltype) { case "buddhist": cal = new BuddhistCalendar(zone, aLocale); break; case "japanese": cal = new JapaneseImperialCalendar(zone, aLocale); break; case "gregory": cal = new GregorianCalendar(zone, aLocale); break; } } } if (cal == null) { // If no known calendar type is explicitly specified, // perform the traditional way to create a Calendar: // create a BuddhistCalendar for th_TH locale, // a JapaneseImperialCalendar for ja_JP_JP locale, or // a GregorianCalendar for any other locales. // NOTE: The language, country and variant strings are interned. if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") { cal = new BuddhistCalendar(zone, aLocale); } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") { cal = new JapaneseImperialCalendar(zone, aLocale); } else { cal = new GregorianCalendar(zone, aLocale); } } return cal; } ``` ##### (5)简单工厂模式的优缺点 - **优点:** 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。 - **缺点:**每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。 #### 1.2.2 抽象工厂模式 ##### (1) 概念 抽象工厂模式,对方法工厂模式进行抽象。 ##### (2)适用场景 当需要创建的对象是一系列相互关联或相互依赖的产品族时,便可以使用抽象工厂模式。说的更明白一点,就是一个继承体系中,如果存在着多个等级结构(即存在着多个抽象类),并且分属各个等级结构中的实现类之间存在着一定的关联或者约束,就可以使用抽象工厂模式。假如各个等级结构中的实现类之间不存在关联或约束,则使用多个独立的工厂来对产品进行创建,则更合适一点。 ##### (3)代码实例 类图 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730210915.png) 还是通过交通工具这一事例来讲述, Vehicle接口,交通工具具有出行的作用,有一个toTravel()方法 ``` package com.alibaba.design.factorypattern.factorymethod; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-10:32 */ public interface Vehicle { void toTravel(); } ``` 抽象的自行车类实现Vehicle接口 ``` package com.alibaba.design.factorypattern.factorymethod; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-18:20 */ public abstract class Bike implements Vehicle { public abstract void toTravel(); } ``` 抽象的汽车类实现Vehicle接口 ``` package com.alibaba.design.factorypattern.factorymethod; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-18:19 */ public abstract class Car implements Vehicle { public abstract void toTravel(); } ``` 实际的ChinaBikeFactory继承Bike类,重写出行toTravel()方法 ``` package com.alibaba.design.factorypattern.factorymethod; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-10:55 */ public class ChinaBikeFactory extends Bike { @Override public void toTravel() { System.out.println("In China,I will Choose bike to travel"); } } ``` 实际的ChinaCarFactory继承Bike类,重写出行toTravel()方法 ``` package com.alibaba.design.factorypattern.factorymethod; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-20:05 */ public class ChinaCarFactory extends Car{ @Override public void toTravel() { System.out.println("In China,I will choose car to travel"); } } ``` 实际的EnglandBikeFactory继承Bike类,重写出行toTravel()方法 ``` package com.alibaba.design.factorypattern.factorymethod; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-18:36 */ public class EnglandBikeFactory extends Bike { @Override public void toTravel() { System.out.println("In England,I will Choose bike to travel"); } } ``` 实际的EnglandCarFactory继承Bike类,重写出行toTravel()方法 ``` package com.alibaba.design.factorypattern.factorymethod; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-20:06 */ public class EnglandCarFactory extends Car { @Override public void toTravel() { System.out.println("In England,I will choose car to travel"); } } ``` VehicleFactory接口,用来实例化两种交通工具的出行方法 ``` package com.alibaba.design.factorypattern.factorymethod; /** * @author zhouyanxiang * @create 2020-07-2020/7/28-10:25 */ public interface VehicleFactory { // 实例化单车出行模式 public Vehicle bikeToTravel(); // 实例化小汽车出行模式 public Vehicle carToTravel(); } ``` ChinaFactory实现VehicleFactory接口 ``` package com.alibaba.design.factorypattern.factorymethod; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-20:07 */ public class ChinaFactory implements VehicleFactory { @Override public Vehicle bikeToTravel() { return new ChinaBikeFactory(); } @Override public Vehicle carToTravel() { return new ChinaCarFactory(); } } ``` EnglandFactory实现VehicleFactory接口 ``` package com.alibaba.design.factorypattern.factorymethod; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-20:08 */ public class EnglandFactory implements VehicleFactory { @Override public Vehicle bikeToTravel() { return new EnglandBikeFactory(); } @Override public Vehicle carToTravel() { return new EnglandCarFactory(); } } ``` 运行结果 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730211811.png) ##### (4)模式在源代码中的体现 在java.sql.Connection接口中 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730213115.png) Connection接口就是一个抽象工厂接口,描述了不同产品等级Statement、PreparedStatement和CallableStatement,它们都位于抽象接口Statement产品等级结构中。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730213218.png) ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730213315.png) 从MySQL(产品族)的Connection中获取的Statement、PreparedStatement和CallableStatement肯定都是MySQL,因为他们都是一个产品族,而从SQL Server中获取的肯定都是SQL Server对应的sql执行器。 假如以后又有新的数据库出现,想在Java中使用它就需要扩展产品族并实现相关接口即可,并不需要修改原有的接口。 参考: https://blog.csdn.net/qq_23633427/article/details/107304244 ##### (5)抽象工厂模式的优缺点 - **优点** 抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。所谓的产品族,一般或多或少的都存在一定的关联,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理。 - **缺点** 产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的。 #### 1.2.3 工厂方法模式 ##### (1)概念 ​ 工厂方法模式(FACTORY METHOD)是一种常用的类创建型设计模式,此模式的核心精神是封装类中变化的部分,提取其中个性化善变的部分为独立类,通过依赖注入以达到解耦、复用和方便后期维护拓展的目的。它的核心结构有四个角色,分别是抽象工厂;具体工厂;抽象产品;具体产品 可以看做是简单工厂模式的升级版;工厂方法模式就是一个工厂接口和多个工厂实现类,要增加一个新的产品,增加一个新的工厂实现类即可,针对之前的老的工厂实现类也不需要修改。 工厂方法模式相当于在简单工厂模式的基础上,增加了对于不同的产品进行多个不同工厂的实现类的添加,不同的工厂用于Get不同的产品,用于进行不同产品的具体生产。 Client进行调用的时候,直接通过识别不同工厂,然后通过工厂接口类提供的公共方法,即可进行接口方法调用,获取产品;还需要知道具体的产品接口,用于进行具体的产品信息的获取。 ##### (2)适用场景 目标可以无限扩展,工厂类也要随之扩展,一对一存在,满足了开闭原则,但如果目标实现较多,工厂实现类也会增多,不简洁。 MyBatis中使用的比较多,事务模块和数据源模块都使用了工厂方法模式。 ##### (3)代码实例 整体类图: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730220532.png) 简单工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改。假如增加其他品牌交通工具,工厂类需要修改,如何解决?就用到工厂方法模式,创建一个工厂接口和创建多个工厂实现类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。 ``` package com.alibaba.design.factorypattern.factorymothed; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-21:45 */ public interface Factory { public Vehicle toTravel(); } ``` 交通工具接口Vehicle继承工厂接口 ``` package com.alibaba.design.factorypattern.factorymothed; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-21:43 */ public interface Vehicle extends Factory{ public void toTravelInfo(); } ``` CarFactory继承Factory ``` package com.alibaba.design.factorypattern.factorymothed; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-21:48 */ public class CarFactory implements Factory { @Override public Vehicle toTravel() { return new Car(); } } ``` BikeFactory继承Factory ``` package com.alibaba.design.factorypattern.factorymothed; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-21:49 */ public class BikeFactory implements Factory { @Override public Vehicle toTravel() { return new Bike(); } } ``` Bike继承BikeFactory并且实现Vehicle接口 ``` package com.alibaba.design.factorypattern.factorymothed; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-21:54 */ public class Bike extends BikeFactory implements Vehicle { @Override public void toTravelInfo() { System.out.println("Choose bike to travel"); } } ``` Car继承CarFactory并且实现Vehicle接口 ``` package com.alibaba.design.factorypattern.factorymothed; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-21:55 */ public class Car extends CarFactory implements Vehicle { @Override public void toTravelInfo() { System.out.println("Choose car to travel "); } } ``` 客户端测试类 ``` package com.alibaba.design.factorypattern.factorymothed; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-21:57 */ public class Test { public static void main(String[] args) { Factory bikeFactory = new BikeFactory(); bikeFactory.toTravel().toTravelInfo(); System.out.println("=================="); Factory carFactory = new CarFactory(); carFactory.toTravel().toTravelInfo(); } } ``` 最后输出结果 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730220546.png) ##### (4)工厂方法模式的在源码中的体现(在Android源码体现的比较多) 在Android源码中,ListActivity继承自Activity,将Activity作为工厂方法,生成具有ListView特点的Activity,对ListActivity的说明如下: 参考文章:https://www.cnblogs.com/yemeishu/archive/2013/01/08/2850586.html ##### (5)工厂方法模式的优缺点 满足了OCP(Open-Closed Principle)开闭原则,增加新的类需要修建新的工厂,增加了代码量。 如果同时需要修改多个工厂类的时候会很麻烦,而简单工厂模式只需要修改一个类,工厂方法模式是升级版的简单工厂模式。 ### 1.3 建造者模式 #### (1)概念 建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。 #### (2)适用场景 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。 **注意事项:**与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。 #### (3)代码示例 我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。 我们将创建一个表示食物条目(比如汉堡和冷饮)的 *Item* 接口和实现 *Item* 接口的实体类,以及一个表示食物包装的 *Packing* 接口和实现 *Packing* 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。 然后我们创建一个 *Meal* 类,带有 *Item* 的 *ArrayList* 和一个通过结合 *Item* 来创建不同类型的 *Meal* 对象的 *MealBuilder*。*BuilderPatternDemo*,我们的演示类使用 *MealBuilder* 来创建一个 *Meal*。 整体类图: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731110533.png) 创建一个表示食物条目和食物包装的接口。 - Item ~~~Java package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:45 */ public interface Item { public String name(); public Packing packing(); public float price(); } ~~~ - Packing ~~~Java package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:46 */ public interface Packing { public String pack(); } ~~~ 创建实现 Packing 接口的实体类。 - Wrapper ``` package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:46 */ public class Wrapper implements Packing{ @Override public String pack() { return "Wrapper"; } } ``` - Bottle ``` package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:47 */ public class Bottle implements Packing { @Override public String pack() { return "Bottle"; } } ``` 创建实现 Item 接口的抽象类,该类提供了默认的功能。 - Burger ``` package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:47 */ public abstract class Burger implements Item { public Packing packing(){ return new Wrapper(); } public abstract float price(); } ``` - ColdDrink ``` package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:48 */ public abstract class ColdDrink implements Item { @Override public Packing packing() { return new Bottle(); } @Override public abstract float price(); } ``` 创建扩展了 Burger 和 ColdDrink 的实体类。 - ChickenBurger ``` package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:52 */ public class ChickenBurger extends Burger { @Override public String name() { return "ChickenBurger"; } @Override public float price() { return 50.5f; } } ``` - VegBurger ``` package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:51 */ public class VegBurger extends Burger { @Override public String name() { return "VegBurger"; } @Override public float price() { return 25.0f; } } ``` - Coke ``` package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:52 */ public class Coke extends ColdDrink { @Override public String name() { return "Coke"; } @Override public float price() { return 30.0f; } } ``` - Pepsi ``` package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:53 */ public class Pepsi extends ColdDrink { @Override public String name() { return "Pepsi"; } @Override public float price() { return 35.5f; } } ``` 创建一个 Meal 类,带有上面定义的 Item 对象。 ``` package com.alibaba.design.builderpattern; import java.util.ArrayList; import java.util.List; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:56 */ public class Meal { private List items = new ArrayList(); public void addItem(Item item){ items.add(item); } public float getCost(){ float cost = 0.0f; for (Item item : items) { cost += item.price(); } return cost; } public void showItems(){ for (Item item : items) { System.out.print("Item : "+item.name()); System.out.print(", Packing : "+item.packing().pack()); System.out.println(", Price : "+item.price()); } } } ``` 创建一个 MealBuilder 类,实际的 builder 类负责创建 Meal 对象。 ``` package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-10:57 */ public class MealBuilder { public Meal prepareVegMeal (){ Meal meal = new Meal(); meal.addItem(new VegBurger()); meal.addItem(new Coke()); return meal; } public Meal prepareNonVegMeal (){ Meal meal = new Meal(); meal.addItem(new ChickenBurger()); meal.addItem(new Pepsi()); return meal; } } ``` 客户端测试类 ``` package com.alibaba.design.builderpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-11:00 */ public class Test { public static void main(String[] args) { MealBuilder mealBuilder = new MealBuilder(); Meal vegMeal = mealBuilder.prepareVegMeal(); System.out.println("VegMeal"); vegMeal.showItems(); System.out.println("Total Cost: " +vegMeal.getCost()); Meal nonVegMeal = mealBuilder.prepareNonVegMeal(); System.out.println("\n\nNon-Veg Meal"); nonVegMeal.showItems(); System.out.println("Total Cost: " +nonVegMeal.getCost()); } } ``` 输出结果 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731110523.png) #### (4)在源码中的体现 在StringBuilder类中的源码 ~~~Java @Override public StringBuilder append(String str) { super.append(str); return this; } ~~~ ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731113314.png) 再看看BeanDefinitionBuilder类 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731113531.png) 来到setFactoryMethod方法,我们写的第二种与其类似,就是典型的建造者模式 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731113555.png) MyBatis中的SqlSessionFactoryBuilder类,也是用了建造者模式,返回SqlSessionFactory对象 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731113824.png) 看其中一个build方法 ``` public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } ``` 可以看到在parseConfiguration方法的参数就是一个根节点,内部就是各个组件的装配流程 至此可以知道XMLConfigBuilder创建复杂对象的Configuration,而SqlSessionFactoryBuilder只不过是对XMLConfigBuilder做了一层简单的封装,用建造者来包装一层建造者 再来看看SqlSessionManager类,可以明显看出多个newInnetstance重载函数的就调用了SqlSessionFactoryBuilder建造者来创建复杂对象 ~~~Java public class SqlSessionManager implements SqlSessionFactory, SqlSession { private final SqlSessionFactory sqlSessionFactory; private final SqlSession sqlSessionProxy; private ThreadLocal localSqlSession = new ThreadLocal(); private SqlSessionManager(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionInterceptor()); } public static SqlSessionManager newInnetstance(Reader reader) { return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, null)); } public static SqlSessionManager newInstance(Reader reader, String environment) { return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, environment, null)); } public static SqlSessionManager newInstance(Reader reader, Properties properties) { return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, properties)); } public static SqlSessionManager newInstance(InputStream inputStream) { return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, null)); } public static SqlSessionManager newInstance(InputStream inputStream, String environment) { return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, environment, null)); } public static SqlSessionManager newInstance(InputStream inputStream, Properties properties) { return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, properties)); } public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) { return new SqlSessionManager(sqlSessionFactory); } ... } ~~~ 参考: [cnblogs.com/yeweiqiang/p/12940152.html](cnblogs.com/yeweiqiang/p/12940152.html) #### (5)建造者模式的优缺点 - **优点:** 1、建造者独立,易扩展。 2、便于控制细节风险。 - **缺点:** 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。 ### 1.4 原型模式 #### (1)概念 原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。 #### (2)适用场景 **何时使用:** 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。 **如何解决:**利用已有的一个原型对象,快速地生成和原型对象一样的实例。 **关键代码:** 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。 **应用实例:** 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。 #### (3)代码示例 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731154005.png) 创建一个实现了 *Cloneable* 接口的抽象类。 ``` package com.alibaba.design.prototypepattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-15:31 */ public abstract class Shape implements Cloneable { private String id; protected String type; abstract void draw(); public String getType(){ return type; } public String getId() { return id; } public void setId(String id) { this.id = id; } public Object clone() { Object clone = null; try { clone = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } } ``` - Circle.java ``` package com.alibaba.design.prototypepattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-15:34 */ public class Circle extends Shape { public Circle(){ type = "Circle"; } @Override public void draw() { System.out.println("Inside Circle::draw() method."); } } ``` - Rectangle.java ``` package com.alibaba.design.prototypepattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-15:32 */ public class Rectangle extends Shape { public Rectangle(){ type = "Rectangle"; } @Override void draw() { System.out.println("Inside Rectangle::draw() method."); } } ``` - Square.java ``` package com.alibaba.design.prototypepattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-15:33 */ public class Square extends Shape { public Square() { type = "Square"; } @Override void draw() { System.out.println("Inside Square::draw() method."); } } ``` 创建一个类,从数据库获取实体类,并把它们存储在一个 *Hashtable* 中。 ``` package com.alibaba.design.prototypepattern; import java.util.Hashtable; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-15:35 */ public class ShapeCache { private static Hashtable shapeMap = new Hashtable(); public static Shape getShape(String shapeId) { Shape cachedShape = shapeMap.get(shapeId); return (Shape) cachedShape.clone(); } // 对每种形状都运行数据库查询,并创建该形状 // shapeMap.put(shapeKey, shape); // 例如,我们要添加三种形状 public static void loadCache() { Circle circle = new Circle(); circle.setId("1"); shapeMap.put(circle.getId(),circle); Square square = new Square(); square.setId("2"); shapeMap.put(square.getId(),square); Rectangle rectangle = new Rectangle(); rectangle.setId("3"); shapeMap.put(rectangle.getId(),rectangle); } } ``` *PrototypePatternDemo* 使用 *ShapeCache* 类来获取存储在 *Hashtable* 中的形状的克隆。 ``` package com.alibaba.design.prototypepattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-15:37 */ public class PrototypePatternDemo { public static void main(String[] args) { ShapeCache.loadCache(); Shape clonedShape = (Shape) ShapeCache.getShape("1"); System.out.println("Shape : " + clonedShape.getType()); Shape clonedShape2 = (Shape) ShapeCache.getShape("2"); System.out.println("Shape : " + clonedShape2.getType()); Shape clonedShape3 = (Shape) ShapeCache.getShape("3"); System.out.println("Shape : " + clonedShape3.getType()); } } ``` ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731154359.png) #### (4)该模式在源码中的体现 ~~~java 我们看一下Object这个对象,我们直接看一下克隆这个方法 protected native Object clone() throws CloneNotSupportedException; 很明显看出来,他是一个native的方法,接下来我们再看一个,Cloneable这个接口,public interface Cloneable, 只要看看哪些类实现这个接口呢,就知道了这个原型模式,是如何使用的,我们按一下Ctrl+T,这里面有一个定位, 直接点一下,会在下边显示出来,这里面开始搜索,在搜索的时候呢,我们再看一个类,ArrayList,相信这个类大家都是知道的, public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable 他实现了Cloneable这个接口,我们看一下它是如何重写的呢, public Object clone() { try { ArrayList v = (ArrayList) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } } 通过Arrays.copyOf这个方法把这里面的元素,copy了一份,同理HashMap这里面 public class HashMap extends AbstractMap implements Map, Cloneable, Serializable 是一样的,他也实现了Cloneable这个接口,同时他也重写了克隆这个方法 @SuppressWarnings("unchecked") @Override public Object clone() { HashMap result; try { result = (HashMap)super.clone(); } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } result.reinitialize(); result.putMapEntries(this, false); return result; } // These methods are also used when serializing HashSets final float loadFactor() { return loadFactor; } final int capacity() { return (table != null) ? table.length : (threshold > 0) ? threshold : DEFAULT_INITIAL_CAPACITY; } 有兴趣的可以来看一下,那在这里想说一下,就是对于原型模式,我们平时在使用的时候,一定要检对象是否和预期是一致的, 也就是说这个对象,是新创建出来的呢,还是只是创建一个引用,指向的是同一个地址,也就是说要把深克隆和浅克隆一定要 应用好,我们看一下下面搜索出来的都是实现了Cloneable这个接口的实现类,这里又很多,这个是JDK的 ~~~ ![img](https://img-blog.csdnimg.cn/20190603145139975.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xlb25fSmluaGFpX1N1bg==,size_16,color_FFFFFF,t_70) ~~~java 包括redission,还有redis的client,还有Spring里面的mybatis,这里面都是实现了Cloneable接口,我们看一下Cache, CacheKey,这个类也实现了Cloneable, public class CacheKey implements Cloneable CacheKey是Mybatis里面关于Cache使用的一个类,我们看一下它是如何重写的 @Override public CacheKey clone() throws CloneNotSupportedException { CacheKey clonedCacheKey = (CacheKey) super.clone(); clonedCacheKey.updateList = new ArrayList(updateList); return clonedCacheKey; } 这里面可以看到,首先是super.clone(),然后强转一个新的出来,然后把里面的List赋值成一个新的List,并把这个元素放到 新的List里面,进行返回,这样就保证了ArrayList,是一个新创建的ArrayList,但是注意里面的updateList private List updateList; 这个又是一个List,有兴趣的,建议可以试一下,克隆出来的里面的,这个元素,是相同的对象还是不同的对象,非常你们能够操作一下, 你可以模拟CacheKey来写一个类,主要是List,然后再进一步的说,如果List里面包装的,不是String,而是Date对象,我们再看一下效果, 希望能够动起手来,那接着看,下面这些是实现Cloneable接口的,包括在开源框架中,会有很多,有兴趣的可以自己挨个看一下,这里面 还是挺有意思的,那原型模式在开源框架中,使用的非常广泛,就拿CacheKey来说,这里面也是非常注重克隆出来的对象,引用问题, 所以我们在实现原型模式的时候,这一点是一定一定要注意的. ~~~ #### (5)原型模式的优缺点 - **优点:** 1、性能提高。 2、逃避构造函数的约束。 - **缺点:** 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。 ### 1.5 对象池模式 #### (1) 概念 Object Pool,即对象池,对象被预先创建并初始化后放入对象池中,对象提供者就能利用已有的对象来处理请求,减少对象频繁创建所占用的内存空间和初始化时间,例如数据库连接对象基本上都是创建后就被放入连接池中,后续的查询请求使用的是连接池中的对象,从而加快了查询速度。类似被放入对象池中的对象还包括Socket对象、线程对象和绘图对象(GDI对象)等。 在Object Pool设计模式中,主要有两个参与者:对象池的管理者和对象池的用户,用户从管理者那里获取对象,对象池对于用户来讲是透明的,但是用户必须遵守这些对象的使用规则,使用完对象后必须归还或者关闭对象,例如数据库连接对象使用完后必须关闭,否则该对象就会被一直占用着。 对象管理者需要维护对象池,包括初始化对象池、扩充对象池的大小、重置归还对象的状态等。 对象池在被初始化时,可能只有几个对象,甚至没有对象,按需创建对象能节省资源和时间,对于响应时间要求较高的情况,可以预先创建若干个对象。 当对象池中没有对象可供使用时,管理者一般需要使用某种策略来扩充对象池,比如将对象池的大小翻倍。另外,在多线程的情况下,可以让请求资源的线程等待,直到其他线程归还了占用的对象。 一般来说,对象池中的对象在逻辑状态上是相同的,如果都是无状态对象(即没有成员变量的对象),那么这些对象的管理会方便的多,否则,对象被使用后的状态重置工作就要由管理者来承担 #### (2)适用场景 对象池模式经常用在频繁创建、销毁对象(并且对象创建、销毁开销很大)的场景,比如数据库连接池、线程池、任务队列池等。 #### (3)代码示例 示例的整体类图: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731211855.png) - Connection接口 ``` package com.alibaba.design.objectpoolpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-19:14 */ public interface Connection { Object get(); void set(Object x); } ``` - 实现接口的连接类ConnectionImplementation ``` package com.alibaba.design.objectpoolpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-19:14 */ public class ConnectionImplementation implements Connection { @Override public Object get() { return new Object(); } @Override public void set(Object x) { System.out.println("设置连接线程为: " + x); } } ``` 连接池类ConnectionPool ``` package com.alibaba.design.objectpoolpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-19:15 */ public class ConnectionPool { //池管理对象 private static PoolManager pool = new PoolManager(); //指定连接数 并添加 public static void addConnections(int number) { for (int i = 0; i < number; i++) { pool.add(new ConnectionImplementation()); System.out.println("添加第 " + i + " 个连接资源"); } } //获取连接 public static Connection getConnection() throws PoolManager.EmptyPoolException { return (Connection) pool.get(); } //释放指定的连接 public static void releaseConnection(Connection c) { pool.release(c); System.out.println("释放整个连接资源: " + c); } } ``` 连接池管理类PoolManager ``` package com.alibaba.design.objectpoolpattern; import java.util.ArrayList; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-19:12 */ public class PoolManager { //连接池对象 public static class PoolItem { boolean inUse = false; Object item; //池数据 PoolItem(Object item) { this.item = item; } } //连接池集合 private ArrayList items = new ArrayList(); public void add(Object item) { this.items.add(new PoolItem(item)); } static class EmptyPoolException extends Exception { } public Object get() throws EmptyPoolException { for (int i = 0; i < items.size(); i++) { PoolItem pitem = (PoolItem) items.get(i); if (pitem.inUse == false) { pitem.inUse = true; System.out.println("获取连接资源为: " + items.get(i)); return pitem.item; } } throw new EmptyPoolException(); // return null; } /** * 释放连接 * @param item */ public void release(Object item) { for (int i = 0; i < items.size(); i++) { PoolItem pitem = (PoolItem) items.get(i); if (item == pitem.item) { pitem.inUse = false; System.out.println("释放连接资源 : " + items.get(i)); return; } } throw new RuntimeException(item + " not null"); } } ``` 客户端测试类 ``` package com.alibaba.design.objectpoolpattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-19:16 */ public class Test { static { ConnectionPool.addConnections(5); } public void test1() { Connection c; try { // 获得连接 c = ConnectionPool.getConnection(); } catch (PoolManager.EmptyPoolException e) { throw new RuntimeException(e); } // 设值 c.set(new Object()); //获取 c.get(); // 释放 ConnectionPool.releaseConnection(c); } public static void main(String args[]) { Test test = new Test(); test.test1(); } } ``` 输出结果 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731212057.png) #### (4)该模式在源码中的体现 javax.sql.PooledConnection ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731212533.png) 看它的注释 ~~~Java /** * An object that provides hooks for connection pool management. * A PooledConnection object * represents a physical connection to a data source. The connection * can be recycled rather than being closed when an application is * finished with it, thus reducing the number of connections that * need to be made. *

* An application programmer does not use the PooledConnection * interface directly; rather, it is used by a middle tier infrastructure * that manages the pooling of connections. *

* When an application calls the method DataSource.getConnection, * it gets back a Connection object. If connection pooling is * being done, that Connection object is actually a handle to * a PooledConnection object, which is a physical connection. *

* The connection pool manager, typically the application server, maintains * a pool of PooledConnection objects. If there is a * PooledConnection object available in the pool, the * connection pool manager returns a Connection object that * is a handle to that physical connection. * If no PooledConnection object is available, the * connection pool manager calls the ConnectionPoolDataSource * method getPoolConnection to create a new physical connection. The * JDBC driver implementing ConnectionPoolDataSource creates a * new PooledConnection object and returns a handle to it. *

* When an application closes a connection, it calls the Connection * method close. When connection pooling is being done, * the connection pool manager is notified because it has registered itself as * a ConnectionEventListener object using the * ConnectionPool method addConnectionEventListener. * The connection pool manager deactivates the handle to * the PooledConnection object and returns the * PooledConnection object to the pool of connections so that * it can be used again. Thus, when an application closes its connection, * the underlying physical connection is recycled rather than being closed. *

* The physical connection is not closed until the connection pool manager * calls the PooledConnection method close. * This method is generally called to have an orderly shutdown of the server or * if a fatal error has made the connection unusable. * *

* A connection pool manager is often also a statement pool manager, maintaining * a pool of PreparedStatement objects. * When an application closes a prepared statement, it calls the * PreparedStatement * method close. When Statement pooling is being done, * the pool manager is notified because it has registered itself as * a StatementEventListener object using the * ConnectionPool method addStatementEventListener. * Thus, when an application closes its PreparedStatement, * the underlying prepared statement is recycled rather than being closed. *

* * @since 1.4 */ ~~~ ~~~Java / ** *提供用于连接池管理的挂钩的对象。 * PooledConnection 对象 *表示与数据源的物理连接。连接 *可以回收而不是在应用程序关闭时关闭 *完成后,减少了连接数 *必须填写。 *

*应用程序程序员不使用 PooledConnection *直接接口;而是由中间层基础架构使用 *管理连接池。 *

*当应用程序调用方法 DataSource.getConnection 时, *它返回一个 Connection 对象。如果连接池是 *完成后,该 Connection 对象实际上是 * PooledConnection 对象,它是物理连接。 *

*连接池管理器(通常是应用程序服务器)维护 * PooledConnection 对象的池。如果有 * PooledConnection 对象在池中可用, *连接池管理器返回一个 Connection 对象,该对象 *是该物理连接的句柄。 *如果没有 PooledConnection 对象可用,则 *连接池管理器调用 ConnectionPoolDataSource *方法 getPoolConnection 创建一个新的物理连接。的 *实现 ConnectionPoolDataSource 的JDBC驱动程序创建了一个 *新的 PooledConnection 对象并返回其句柄。 *

*当应用程序关闭连接时,它会调用 Connection *方法 close 。完成连接池后, *连接池管理器已被通知,因为它已将自己注册为 *使用 ConnectionEventListener 对象 * ConnectionPool 方法 addConnectionEventListener 。 *连接池管理器取消激活 * PooledConnection 对象并返回 * PooledConnection 对象到连接池,以便 *它可以再次使用。因此,当应用程序关闭其连接时, *基础物理连接被回收而不是关闭。 *

*直到连接池管理器才关闭物理连接 *调用 PooledConnection 方法 close 。 *此方法通常被称为有序关闭服务器或 *如果发生致命错误使连接无法使用。 * *

*连接池管理器通常也是语句池管理器,用于维护 * PreparedStatement 对象的池。 *当应用程序关闭准备好的语句时,它将调用 * PreparedStatement *方法 close 。完成 Statement 池后, *通知池管理器,因为它已将自己注册为 *使用 StatementEventListener 对象 * ConnectionPool 方法 addStatementEventListener 。 *因此,当应用程序关闭其 PreparedStatement 时, *基础的准备好的语句被回收而不是关闭。 *

* * @从1.4开始 * / ~~~ #### (5)对象池模式的优缺点 - **优点** **复用池中对象,没有分配内存和创建堆中对象的开销, 没有释放内存和销毁堆中对象的开销, 进而减少垃圾收集器的负担, 避免内存抖动;不必重复初始化对象状态, 对于比较耗时的constructor和finalize来说非常合适;** - **缺点** 1. **Java的对象分配操作不比c语言的malloc调用慢, 对于轻中量级的对象, 分配/释放对象的开销可以忽略不计;** 2. **并发环境中, 多个线程可能(同时)需要获取池中对象, 进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞, 这种开销要比创建销毁对象的开销高数百倍;** 3. **由于池中对象的数量有限, 势必成为一个可伸缩性瓶颈;** 4. **很难正确的设定对象池的大小, 如果太小则起不到作用, 如果过大, 则占用内存资源高, 可以起一个线程定期扫描分析, 将池压缩到一个合适的尺寸以节约内存,但为了获得不错的分析结果, 在扫描期间可能需要暂停复用以避免干扰(造成效率低下), 或者使用非常复杂的算法策略(增加维护难度);** 5. **设计和使用对象池容易出错, 设计上需要注意状态同步, 这是个难点, 使用上可能存在忘记归还(就像c语言编程忘记free一样), 重复归还(可能需要做个循环判断一下是否池中存在此对象, 这也是个开销), 归还后仍旧使用对象(可能造成多个线程并发使用一个对象的情况)等问题** ## 二、行为型模式 **一共十二种** ### 2.1 责任链模式 #### (1)概念 顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。 在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。 #### (2)适用场景 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。 **注意事项:**在 JAVA WEB 中遇到很多应用。 #### (3)代码示例 我们创建抽象类 *AbstractLogger*,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 *AbstractLogger*。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731221055.png) 创建抽象的记录器类。 ``` package com.alibaba.design.chainofresponsibilitypattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-21:50 */ public abstract class AbstractLogger { public static int INFO = 1; public static int DEBUG = 2; public static int ERROR = 3; protected int level; //责任链中的下一个元素 protected AbstractLogger nextLogger; public void setNextLogger(AbstractLogger nextLogger){ this.nextLogger = nextLogger; } public void logMessage(int level, String message){ if(this.level <= level){ write(message); } if(nextLogger !=null){ nextLogger.logMessage(level, message); } } abstract protected void write(String message); } ``` 创建扩展了该记录器类的实体类。 - ConsoleLogger ``` package com.alibaba.design.chainofresponsibilitypattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-21:50 */ public class ConsoleLogger extends AbstractLogger { public ConsoleLogger(int level){ this.level = level; } @Override protected void write(String message) { System.out.println("Standard Console::Logger: " + message); } } ``` - ErrorLogger ``` package com.alibaba.design.chainofresponsibilitypattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-21:51 */ public class ErrorLogger extends AbstractLogger { public ErrorLogger(int level){ this.level = level; } @Override protected void write(String message) { System.out.println("Error Console::Logger: " + message); } } ``` - FileLogger ``` package com.alibaba.design.chainofresponsibilitypattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-21:51 */ public class FileLogger extends AbstractLogger { public FileLogger(int level){ this.level = level; } @Override protected void write(String message) { System.out.println("File::Logger: " + message); } } ``` 客户端测试类 ``` package com.alibaba.design.chainofresponsibilitypattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/31-21:52 */ public class ChainPatternDemo { private static AbstractLogger getChainOfLoggers(){ AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR); AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG); AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO); errorLogger.setNextLogger(fileLogger); fileLogger.setNextLogger(consoleLogger); return errorLogger; } public static void main(String[] args) { AbstractLogger loggerChain = getChainOfLoggers(); loggerChain.logMessage(AbstractLogger.INFO, "This is an information."); loggerChain.logMessage(AbstractLogger.DEBUG, "This is a debug level information."); loggerChain.logMessage(AbstractLogger.ERROR, "This is an error information."); } } ``` ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731221219.png) #### (4)该模式在源码中的体现 ##### 责任链模式在Tomcat中的应用 参考 [https://www.cnblogs.com/java-my-life/archive/2012/05/28/2516865.html](https://www.cnblogs.com/java-my-life/archive/2012/05/28/2516865.html)   众所周知Tomcat中的Filter就是使用了责任链模式,创建一个Filter除了要在web.xml文件中做相应配置外,还需要实现javax.servlet.Filter接口。 ~~~Java public class TestFilter implements Filter{ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response); } public void destroy() { } public void init(FilterConfig filterConfig) throws ServletException { } } ~~~ 使用DEBUG模式所看到的结果如下 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731224208.png) 实在真正执行到TestFilter类之前,会经过很多Tomcat内部的类。顺带提一下其实Tomcat的容器设置也是责任链模式,注意被红色方框所圈中的类,从Engine到Host再到Context一直到Wrapper都是通过一个链传递请求。被绿色方框所圈中的地方有一个名为ApplicationFilterChain的类,ApplicationFilterChain类所扮演的就是抽象处理者角色,而具体处理者角色由各个Filter扮演。   第一个疑问是ApplicationFilterChain将所有的Filter存放在哪里?   答案是保存在ApplicationFilterChain类中的一个ApplicationFilterConfig对象的数组中。 ~~~Java /** * Filters. */ private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; ~~~ 那ApplicationFilterConfig对象又是什么呢?   ApplicationFilterConfig是一个Filter容器。以下是ApplicationFilterConfig类的声明: ~~~Java /** * Implementation of a javax.servlet.FilterConfig useful in * managing the filter instances instantiated when a web application * is first started. * * @author Craig R. McClanahan * @version $Id: ApplicationFilterConfig.java 1201569 2011-11-14 01:36:07Z kkolinko $ */ ~~~ 当一个web应用首次启动时ApplicationFilterConfig会自动实例化,它会从该web应用的web.xml文件中读取配置的Filter的信息,然后装进该容器。   刚刚看到在ApplicationFilterChain类中所创建的ApplicationFilterConfig数组长度为零,那它是在什么时候被重新赋值的呢? ~~~Java private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; ~~~  是在调用ApplicationFilterChain类的addFilter()方法时。 ~~~Java /** * The int which gives the current number of filters in the chain. */ private int n = 0; public static final int INCREMENT = 10; void addFilter(ApplicationFilterConfig filterConfig) { // Prevent the same filter being added multiple times for(ApplicationFilterConfig filter:filters) if(filter==filterConfig) return; if (n == filters.length) { ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + INCREMENT]; System.arraycopy(filters, 0, newFilters, 0, n); filters = newFilters; } filters[n++] = filterConfig; } ~~~ 变量n用来记录当前过滤器链里面拥有的过滤器数目,默认情况下n等于0,ApplicationFilterConfig对象数组的长度也等于0,所以当第一次调用addFilter()方法时,if (n == filters.length)的条件成立,ApplicationFilterConfig数组长度被改变。之后filters[n++] = filterConfig;将变量filterConfig放入ApplicationFilterConfig数组中并将当前过滤器链里面拥有的过滤器数目+1。   那ApplicationFilterChain的addFilter()方法又是在什么地方被调用的呢?   是在ApplicationFilterFactory类的createFilterChain()方法中。 ~~~Java public ApplicationFilterChain createFilterChain (ServletRequest request, Wrapper wrapper, Servlet servlet) { // get the dispatcher type DispatcherType dispatcher = null; if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) { dispatcher = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR); } String requestPath = null; Object attribute = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR); if (attribute != null){ requestPath = attribute.toString(); } // If there is no servlet to execute, return null if (servlet == null) return (null); boolean comet = false; // Create and initialize a filter chain object ApplicationFilterChain filterChain = null; if (request instanceof Request) { Request req = (Request) request; comet = req.isComet(); if (Globals.IS_SECURITY_ENABLED) { // Security: Do not recycle filterChain = new ApplicationFilterChain(); if (comet) { req.setFilterChain(filterChain); } } else { filterChain = (ApplicationFilterChain) req.getFilterChain(); if (filterChain == null) { filterChain = new ApplicationFilterChain(); req.setFilterChain(filterChain); } } } else { // Request dispatcher in use filterChain = new ApplicationFilterChain(); } filterChain.setServlet(servlet); filterChain.setSupport (((StandardWrapper)wrapper).getInstanceSupport()); // Acquire the filter mappings for this Context StandardContext context = (StandardContext) wrapper.getParent(); FilterMap filterMaps[] = context.findFilterMaps(); // If there are no filter mappings, we are done if ((filterMaps == null) || (filterMaps.length == 0)) return (filterChain); // Acquire the information we will need to match filter mappings String servletName = wrapper.getName(); // Add the relevant path-mapped filters to this filter chain for (int i = 0; i < filterMaps.length; i++) { if (!matchDispatcher(filterMaps[i] ,dispatcher)) { continue; } if (!matchFiltersURL(filterMaps[i], requestPath)) continue; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName()); if (filterConfig == null) { // FIXME - log configuration problem continue; } boolean isCometFilter = false; if (comet) { try { isCometFilter = filterConfig.getFilter() instanceof CometFilter; } catch (Exception e) { // Note: The try catch is there because getFilter has a lot of // declared exceptions. However, the filter is allocated much // earlier Throwable t = ExceptionUtils.unwrapInvocationTargetException(e); ExceptionUtils.handleThrowable(t); } if (isCometFilter) { filterChain.addFilter(filterConfig); } } else { filterChain.addFilter(filterConfig); } } // Add filters that match on servlet name second for (int i = 0; i < filterMaps.length; i++) { if (!matchDispatcher(filterMaps[i] ,dispatcher)) { continue; } if (!matchFiltersServlet(filterMaps[i], servletName)) continue; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName()); if (filterConfig == null) { // FIXME - log configuration problem continue; } boolean isCometFilter = false; if (comet) { try { isCometFilter = filterConfig.getFilter() instanceof CometFilter; } catch (Exception e) { // Note: The try catch is there because getFilter has a lot of // declared exceptions. However, the filter is allocated much // earlier } if (isCometFilter) { filterChain.addFilter(filterConfig); } } else { filterChain.addFilter(filterConfig); } } // Return the completed filter chain return (filterChain); } ~~~  可以将如上代码分为两段,51行之前为第一段,51行之后为第二段。   第一段的主要目的是创建ApplicationFilterChain对象以及一些参数设置。   第二段的主要目的是从上下文中获取所有Filter信息,之后使用for循环遍历并调用filterChain.addFilter(filterConfig);将filterConfig放入ApplicationFilterChain对象的ApplicationFilterConfig数组中。   那ApplicationFilterFactory类的createFilterChain()方法又是在什么地方被调用的呢?   是在StandardWrapperValue类的invoke()方法中被调用的。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731224740.png) 由于invoke()方法较长,所以将很多地方省略。 ~~~Java public final void invoke(Request request, Response response) throws IOException, ServletException { ...省略中间代码     // Create the filter chain for this request ApplicationFilterFactory factory = ApplicationFilterFactory.getInstance(); ApplicationFilterChain filterChain = factory.createFilterChain(request, wrapper, servlet);   ...省略中间代码 filterChain.doFilter(request.getRequest(), response.getResponse());   ...省略中间代码 } ~~~  那正常的流程应该是这样的:   在StandardWrapperValue类的invoke()方法中调用ApplicationFilterChai类的createFilterChain()方法———>在ApplicationFilterChai类的createFilterChain()方法中调用ApplicationFilterChain类的addFilter()方法———>在ApplicationFilterChain类的addFilter()方法中给ApplicationFilterConfig数组赋值。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731224807.png) 根据上面的代码可以看出StandardWrapperValue类的invoke()方法在执行完createFilterChain()方法后,会继续执行ApplicationFilterChain类的doFilter()方法,然后在doFilter()方法中会调用internalDoFilter()方法。   以下是internalDoFilter()方法的部分代码 ​ ```Java // Call the next filter if there is one if (pos < n) {       //拿到下一个Filter,将指针向下移动一位 //pos它来标识当前ApplicationFilterChain(当前过滤器链)执行到哪个过滤器 ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = null; try {          //获取当前指向的Filter的实例 filter = filterConfig.getFilter(); support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT, filter, request, response); if (request.isAsyncSupported() && "false".equalsIgnoreCase( filterConfig.getFilterDef().getAsyncSupported())) { request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE); } if( Globals.IS_SECURITY_ENABLED ) { final ServletRequest req = request; final ServletResponse res = response; Principal principal = ((HttpServletRequest) req).getUserPrincipal(); Object[] args = new Object[]{req, res, this}; SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal); } else {            //调用Filter的doFilter()方法 filter.doFilter(request, response, this); } ``` 这里的filter.doFilter(request, response, this);就是调用我们前面创建的TestFilter中的doFilter()方法。而TestFilter中的doFilter()方法会继续调用chain.doFilter(request, response);方法,而这个chain其实就是ApplicationFilterChain,所以调用过程又回到了上面调用dofilter和调用internalDoFilter方法,这样执行直到里面的过滤器全部执行。   如果定义两个过滤器,则Debug结果如下: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200731224957.png) ##### 责任链模式在 Android 中的体现 ###### ViewGroup 事件传递 还记得 Android 总的事件分发机制吗,主要有三个方法,`dispatchTouchEvent`,`onInterceptTouchEvent`,`onTouchEvent` 三个方法 - dispatchTouchEvent ,这个方法主要是用来分发事件的 - onInterceptTouchEvent,这个方法主要是用来拦截事件的(需要注意的是ViewGroup才有这个方法,View没有onInterceptTouchEvent这个方法 - onTouchEvent这个方法主要是用来处理事件的 - requestDisallowInterceptTouchEvent(true),这个方法能够影响父View是否拦截事件,true表示 不拦截事件,false表示拦截事件 下面引用[图解 Android 事件分发机制](http://www.jianshu.com/p/e99b5e8bd67b)这一篇博客的内容 当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View,TouchEvent最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发, 1. 如果dispatchTouchEvent返回true 消费事件,事件终结。 2. 如果dispatchTouchEvent返回 false ,则回传给父View的onTouchEvent事件处理; - onTouchEvent事件返回true,事件终结,返回false,交给父View的OnTouchEvent方法处理 3. 如果dispatchTouchEvent返回super的话,默认会调用自己的onInterceptTouchEvent方法 - 默认的情况下interceptTouchEvent回调用super方法,super方法默认返回false,所以会交给子View的onDispatchTouchEvent方法处理 - 如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理, - 如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。 通过这样链式的设计,确保了每一个 View 都有机会处理 touch 事件。如果中途有 View 处理了事件,就停止处理。 ##### 有序广播 Android 中的 BroastCast 分为两种,一种时普通广播,另一种是有序广播。普通广播是异步的,发出时可以被所有的接收者收到。而有序广播是根据优先级一次传播的,直到有接收者将其终止或者所有接收者都不终止它。有序广播的这一特性与我们的责任链模式很相近,我们可以轻松地实现一种全局的责任链事件处理。 #### (5)责任链模式的优缺点 - **优点:** 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。 - **缺点:** 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。 ### 2.2 命令模式 #### (1)概念 命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。 **主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。** #### (2)适用场景 认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。 在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。 **注意事项:**系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式,见命令模式的扩展。 #### (3)代码示例 **关键代码:定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口** 我们首先创建作为命令的接口 *Order*,然后创建作为请求的 *Stock* 类。实体命令类 *BuyStock* 和 *SellStock*,实现了 *Order* 接口,将执行实际的命令处理。创建作为调用对象的类 *Broker*,它接受订单并能下订单。 *Broker* 对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。*CommandPatternDemo*,我们的演示类使用 *Broker* 类来演示命令模式。 创建一个命令接口。 ``` package com.alibaba.design.commandpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-8:57 */ public interface Order { void execute(); } ``` 创建一个请求类。 ``` package com.alibaba.design.commandpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-8:57 */ public class Stock { private String name = "Tom"; private int quantity = 10; public void buy(){ System.out.println("Stock [ Name: "+name+", Quantity: " + quantity +" ] bought"); } public void sell(){ System.out.println("Stock [ Name: "+name+", Quantity: " + quantity +" ] sold"); } } ``` 创建实现了 *Order* 接口的实体类。 - BuyStock ``` package com.alibaba.design.commandpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-8:59 */ public class BuyStock implements Order { private Stock abcStock; public BuyStock(Stock abcStock){ this.abcStock = abcStock; } @Override public void execute() { abcStock.buy(); } } ``` - SellStock ``` package com.alibaba.design.commandpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-9:00 */ public class SellStock implements Order { private Stock abcStock; public SellStock(Stock abcStock){ this.abcStock = abcStock; } @Override public void execute() { abcStock.sell(); } } ``` 创建命令调用类。 ``` package com.alibaba.design.commandpattern; import java.util.ArrayList; import java.util.List; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-9:01 */ public class Broker { private List orderList = new ArrayList(); public void takeOrder(Order order){ orderList.add(order); } public void placeOrders(){ for (Order order : orderList) { order.execute(); } orderList.clear(); } } ``` ``` package com.alibaba.design.commandpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-9:02 */ public class CommandPatternDemo { public static void main(String[] args) { Stock abcStock = new Stock(); BuyStock buyStockOrder = new BuyStock(abcStock); SellStock sellStockOrder = new SellStock(abcStock); Broker broker = new Broker(); broker.takeOrder(buyStockOrder); broker.takeOrder(sellStockOrder); broker.placeOrders(); } } ``` 执行程序,输出结果: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801090714.png) #### (4)该模式在源码中的应用 典型的java.lang.Runable和线程池中都有用到命令模式 来看一下Runable的源码 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801092242.png) Runable:任务抽象,也就是“命令”;线程池通过submit和execute调用 java.util.concurrent.ThreadPoolExecutor线程池中的源码 ~~~java /** * Executes the given task sometime in the future. The task * may execute in a new thread or in an existing pooled thread. * * If the task cannot be submitted for execution, either because this * executor has been shutdown or because its capacity has been reached, * the task is handled by the current {@code RejectedExecutionHandler}. * * @param command the task to execute * @throws RejectedExecutionException at discretion of * {@code RejectedExecutionHandler}, if the task * cannot be accepted for execution * @throws NullPointerException if {@code command} is null */ public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); } /** * Initiates an orderly shutdown in which previously submitted * tasks are executed, but no new tasks will be accepted. * Invocation has no additional effect if already shut down. * *

This method does not wait for previously submitted tasks to * complete execution. Use {@link #awaitTermination awaitTermination} * to do that. * * @throws SecurityException {@inheritDoc} */ public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); advanceRunState(SHUTDOWN); interruptIdleWorkers(); onShutdown(); // hook for ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } tryTerminate(); } ~~~ #### (5)命令模式的优缺点 - **优点:** 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。 - **缺点:** 使用命令模式可能会导致某些系统有过多的具体命令类。 ### 2.3 解释器模式 #### (1)概念 解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。 **何时使用:**如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。 **如何解决:**构建语法树,定义终结符与非终结符。 #### (2)适用场景 1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。 2、一些重复出现的问题可以用一种简单的语言来进行表达。 3、一个简单语法需要解释的场景。 #### (3)代码示例 我们将创建一个接口 *Expression* 和实现了 *Expression* 接口的实体类。定义作为上下文中主要解释器的 *TerminalExpression* 类。其他的类 *OrExpression*、*AndExpression* 用于创建组合式表达式。 *InterpreterPatternDemo*,我们的演示类使用 *Expression* 类创建规则和演示表达式的解析。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801102432.png) 创建一个表达式接口。 ``` package com.alibaba.design.interpreterpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-10:10 */ public interface Expression { public boolean interpret(String context); } ``` 创建实现了上述接口的实体类。 - TerminalExpression ``` package com.alibaba.design.interpreterpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-10:10 */ public class TerminalExpression implements Expression { private String data; public TerminalExpression(String data){ this.data = data; } @Override public boolean interpret(String context) { if(context.contains(data)){ return true; } return false; } } ``` - OrExpression ``` package com.alibaba.design.interpreterpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-10:11 */ public class OrExpression implements Expression { private Expression expr1 = null; private Expression expr2 = null; public OrExpression(Expression expr1, Expression expr2) { this.expr1 = expr1; this.expr2 = expr2; } @Override public boolean interpret(String context) { return expr1.interpret(context) || expr2.interpret(context); } } ``` - AndExpression ``` package com.alibaba.design.interpreterpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-10:12 */ public class AndExpression implements Expression { private Expression expr1 = null; private Expression expr2 = null; public AndExpression(Expression expr1, Expression expr2) { this.expr1 = expr1; this.expr2 = expr2; } @Override public boolean interpret(String context) { return expr1.interpret(context) && expr2.interpret(context); } } ``` 客户端测试类 ``` package com.alibaba.design.interpreterpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-10:13 */ public class InterpreterPatternDemo { // 规则:Robert 和 John 是男性 public static Expression getMaleExpression(){ Expression robert = new TerminalExpression("Robert"); Expression john = new TerminalExpression("John"); return new OrExpression(robert, john); } // 规则:Julie 是一个已婚的女性 public static Expression getMarriedWomanExpression(){ Expression julie = new TerminalExpression("Julie"); Expression married = new TerminalExpression("Married"); return new AndExpression(julie, married); } public static void main(String[] args) { Expression isMale = getMaleExpression(); Expression isMarriedWoman = getMarriedWomanExpression(); System.out.println("John is male? " + isMale.interpret("John")); System.out.println("Julie is a married women? " + isMarriedWoman.interpret("Married Julie")); } } ``` 打印结果: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801101439.png) #### (4)该模式在源码中的体现 JDK中的应用: 类Pattern:正则表达式就是一个解释器模式的很好体现,因为正则表达式本身就是一个语法的封装。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801103009.png) **在Spring中的应用** org.springframework.expression.EpressionParser类EpressionParser它也封装了字符串表达式的语法,其内部采用的也是解释器模式 #### (5)解释器模式的优缺点 - **优点:** 1、可扩展性比较好,灵活。 2、增加了新的解释表达式的方式。 3、易于实现简单文法。 - **缺点:** 1、可利用场景比较少。 2、对于复杂的文法比较难维护。 3、解释器模式会引起类膨胀。 4、解释器模式采用递归调用方法。 **注意事项:**可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。 ### 2.4 迭代器模式 #### (1)概念 迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。 迭代器模式属于行为型模式。 #### (2)适用场景 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。 #### (3)代码示例 我们将创建一个叙述导航方法的 *Iterator* 接口和一个返回迭代器的 *Container* 接口。实现了 *Container* 接口的实体类将负责实现 *Iterator* 接口。 *IteratorPatternDemo*,我们的演示类使用实体类 *NamesRepository* 来打印 *NamesRepository* 中存储为集合的 *Names*。 创建接口: ``` package com.alibaba.design.iteratorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-21:16 */ public interface Iterator { public boolean hasNext(); public Object next(); } ``` ``` package com.alibaba.design.iteratorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-21:16 */ public interface Container { public Iterator getIterator(); } ``` 创建实现了 *Container* 接口的实体类。该类有实现了 *Iterator* 接口的内部类 *NameIterator*。 ``` package com.alibaba.design.iteratorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-21:22 */ public class NameRepository implements Container { public String []names = {"Robert" , "John" ,"Julie" , "Lora"}; @Override public Iterator getIterator() { return new NameIterator(); } private class NameIterator implements Iterator { int index; @Override public boolean hasNext() { if (index < names.length){ return true; }else { return false; } } @Override public Object next() { if(this.hasNext()){ return names[index++]; } return null; } } } ``` 客户端测试类 ``` package com.alibaba.design.iteratorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-21:23 */ public class IteratorPatternDemo { public static void main(String[] args) { NameRepository namesRepository = new NameRepository(); for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){ String name = (String)iter.next(); System.out.println("Name : " + name); } } } ``` 输出示例: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801212511.png) #### (4)该模式在源码中的体现 java.util.Iterator迭代器就是用到了迭代器模式 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801212858.png) #### (5)迭代器模式的优缺点 - **优点:** 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。 - **缺点:** 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。 ### 2.5 观察者模式 #### (1) 概念 观察者模式(Observer Pattern)定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新,属于行为型模式。观察者模式有时也叫做发布订阅模式。 #### (2)适用场景 观察者模式主要用于在关联行为之间建立一套触发机制的场景。观察者模式在现实生活应用也非常广泛, 比如:微信朋友圈动态通知、GPser 生态圈消息通知、邮件通知、广播通知、桌面程序的事件响应等(如下图)。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730094031.png) 如果有设置指定老师回答,对应的老师就会收到邮件通知,这就是观察者模式的一种应用场 景。我们可能会想到 MQ,异步队列等,其实 JDK 本身就提供这样的 API。 #### (3) 代码示例 我们用代码来还原一下这样一个应用场景,创建 GPer 类: ``` package com.alibaba.design.observerpattern.gperadvice; import java.util.Observable; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-9:42 */ public class GPer extends Observable { private String name = "GPer生态圈"; private static GPer gper = null; private GPer(){} public static GPer getInstance(){ if(null == gper){ gper = new GPer(); } return gper; } public String getName() { return name; } public void publishQuestion(Question question){ System.out.println(question.getUserName() + "在" + this.name + "上提交了一个问题。"); setChanged(); notifyObservers(question); } } ``` 创建问题 Question 类: ``` package com.alibaba.design.observerpattern.gperadvice; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-9:43 */ public class Question { private String userName; private String content; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } ``` 创建老师 Teacher 类: ``` package com.alibaba.design.observerpattern.gperadvice; import java.util.Observable; import java.util.Observer; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-9:43 */ public class Teacher implements Observer { private String name; public Teacher(String name){ this.name = name; } @Override public void update(Observable o, Object arg) { GPer gper = (GPer)o; Question question = (Question)arg; System.out.println("==============================="); System.out.println(name + "老师,你好!\n" + "您收到了一个来自“" + gper.getName() + "”的提问,希望您解答,问题内容如下:\n" + question.getContent() + "\n" + "提问者:" + question.getUserName()); } } ``` 客户端测试代码 ``` package com.alibaba.design.observerpattern.gperadvice; import java.util.Observer; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-9:44 */ public class ObserverTest { public static void main(String[] args) { GPer gper = GPer.getInstance(); Teacher tom = new Teacher("Tom"); Teacher mic = new Teacher("Jerry"); //这为没有@Tom老师 Question question = new Question(); question.setUserName("小明"); question.setContent("观察者设计模式适用于哪些场景?"); gper.addObserver(tom); gper.addObserver(mic); gper.publishQuestion(question); } } ``` ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730095230.png) #### (4) 模式在源码中的体现 **来看一下 Spring 中的 ContextLoaderListener 实现了 ServletContextListener 接口,ServletContextListener 接口又继承了 EventListener,在 JDK 中 EventListener 有非常广泛的应用。我们可以看一下源代码,ContextLoaderListener:** ~~~Java package org.springframework.web.context; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } @override public void contextInitialized(ServletContextEvent event) { this.initWebApplicationContext(event.getServletContext()); } @override public void contextDestroyed(ServletContextEvent event) { this.closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } } ~~~ **ServletContextListener** ~~~Java package javax.servlet; import java.util.EventListener; public interface ServletContextListener extends EventListener { public void contextInitialized(ServletContextEvent sce); public void contextDestroyed(ServletContextEvent sce); } ~~~ **EventListener** ~~~Java package java.util; public interface EventListener { } ~~~ #### (5)基于 Guava API 轻松落地观察者模式 Guava是一种基于开源的Java库,Google Guava源于2007年的"Google Collections Library"。这个库是为了方便编码,并减少编码错误。这个库用于提供集合,缓存,支持原语句,并发性,常见注解,字符串处理,I/O和验证的实用方法。 在这里,我还推荐给大家一个实现观察者模式非常好用的框架。API 使用也非常简单,举 个例子,先引入jar包:guava-20.jar ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730100626.png) 这里建议去maven下载这个 ~~~Java com.google.guava guava 20.0 ~~~ 创建侦听事件 GuavaEvent: ~~~Java package com.alibaba.design.observerpattern.guava; import com.google.common.eventbus.Subscribe; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-9:59 */ public class GuavaEvent { @Subscribe public void subscribe(String str){ System.out.println("执行subscribe方法,传入的参数是:" + str); } } ~~~ 客户端测试代码: ~~~Java package com.alibaba.design.observerpattern.guava; import com.google.common.eventbus.EventBus; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-9:59 */ public class GuavaEventTest { public static void main(String[] args) { //消息总线 EventBus eventBus = new EventBus(); GuavaEvent guavaEvent = new GuavaEvent(); eventBus.register(guavaEvent); eventBus.post("Tom"); } } ~~~ #### (6) 观察者模式的优缺点 - 优点: 1、观察者和被观察者之间建立了一个抽象的耦合。 2、观察者模式支持广播通信。 - 缺点: 1、观察者之间有过多的细节依赖、提高时间消耗及程序的复杂度。 2、使用要得当,要避免循环调用。 ### 2.6 中介者模式 #### (1)概念 中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。 图片来源:[https://blog.csdn.net/ZixiangLi/article/details/86228684](https://blog.csdn.net/ZixiangLi/article/details/86228684) ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801215959.png) #### (2)适用场景 1、系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。 2、想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。 **注意事项**:不应当在职责混乱的时候使用。 **主要解决:**对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。 **何时使用:**多个类相互耦合,形成了网状结构。 **如何解决:**将上述网状结构分离为星型结构。 **关键代码:**对象 Colleague 之间的通信封装到一个类中单独处理。 #### (3)代码示例 我们通过聊天室实例来演示中介者模式。实例中,多个用户可以向聊天室发送消息,聊天室向所有的用户显示消息。我们将创建两个类 *ChatRoom* 和 *User*。*User* 对象使用 *ChatRoom* 方法来分享他们的消息。 *MediatorPatternDemo*,我们的演示类使用 *User* 对象来显示他们之间的通信。 创建中介类。 ``` package com.alibaba.design.mediatorpattern; import java.util.Date; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-21:50 */ public class ChatRoom { public static void showMessage(User user, String message){ System.out.println(new Date().toString() + " [" + user.getName() +"] : " + message); } } ``` 创建 user 类。 ``` package com.alibaba.design.mediatorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-21:51 */ public class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public User(String name){ this.name = name; } public void sendMessage(String message){ ChatRoom.showMessage(this,message); } } ``` 使用 *User* 对象来显示他们之间的通信。 ``` package com.alibaba.design.mediatorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-21:53 */ public class MediatorPatternDemo { public static void main(String[] args) { User robert = new User("Robert"); User john = new User("John"); robert.sendMessage("Hi! John!"); john.sendMessage("Hello! Robert!"); } } ``` ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801215441.png) #### (4)该模式在源码中的体现 在java.util.Timer中 timer是一个中介者,它持有新创建到TimerTask的引用,timer负责执行TimerTask的定时任务。不同的TimerTask只知道自己的定时任务,而不清楚其他的TimerTask任务,但是都认识Timer这个中间者对象 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801221057.png) 在线程方面也用到了这个模式 java.util.concurrent.Executor#execute(Runnable command) java.util.concurrent.ExecutorService#submit(Runnable command) exec是一个中介者,它持有Runnable的引用,exec是一个异步任务执行者,它负责执行Runnable任务。 ~~~Java Executor exec = Executors.newFixedThreadPool(100); exec.execute(new Runnable() { @Override public void run() { System.out.println("nice to meet u"); } }); exec.execute(new Runnable() { @Override public void run() { System.out.println("nice to meet u too"); } }); ~~~ #### (5)中介者模式的优缺点 - **优点:** 1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则。 - **缺点:** 中介者会庞大,变得复杂难以维护。 ### 2.7 备忘录模式 #### (1)概念 备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 #### (2)适用场景 1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。 **注意事项**: 1、为了符合迪米特原则,还要增加一个管理备忘录的类。 2、为了节约内存,可使用原型模式+备忘录模式。 #### (3)代码示例 备忘录模式使用三个类 *Memento*、*Originator* 和 *CareTaker*。Memento 包含了要被恢复的对象的状态。Originator 创建并在 Memento 对象中存储状态。Caretaker 对象负责从 Memento 中恢复对象的状态。 *MementoPatternDemo*,我们的演示类使用 *CareTaker* 和 *Originator* 对象来显示对象的状态恢复。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801225456.jpg) 创建 Memento 类。 ``` package com.alibaba.design.mementopattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-22:49 */ public class Memento { private String state; public Memento(String state){ this.state = state; } public String getState(){ return state; } } ``` 创建 Originator 类。 ``` package com.alibaba.design.mementopattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-22:50 */ public class Originator { private String state; public void setState(String state){ this.state = state; } public String getState(){ return state; } public Memento saveStateToMemento(){ return new Memento(state); } public void getStateFromMemento(Memento Memento){ state = Memento.getState(); } } ``` 创建 CareTaker 类 ``` package com.alibaba.design.mementopattern; import java.util.ArrayList; import java.util.List; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-22:50 */ public class CareTaker { private List mementoList = new ArrayList(); public void add(Memento state){ mementoList.add(state); } public Memento get(int index){ return mementoList.get(index); } } ``` ``` package com.alibaba.design.mementopattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-22:51 */ public class MementoPatternDemo { public static void main(String[] args) { Originator originator = new Originator(); CareTaker careTaker = new CareTaker(); originator.setState("State #1"); originator.setState("State #2"); careTaker.add(originator.saveStateToMemento()); originator.setState("State #3"); careTaker.add(originator.saveStateToMemento()); originator.setState("State #4"); System.out.println("Current State: " + originator.getState()); originator.getStateFromMemento(careTaker.get(0)); System.out.println("First saved State: " + originator.getState()); originator.getStateFromMemento(careTaker.get(1)); System.out.println("Second saved State: " + originator.getState()); } } ``` ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801225221.png) #### (4)该模式在源码中的体现 可以联系Git,还有数据库事务处理等,它们都有版本记录,操作回滚的逻辑,这些都可以基于备忘录模式,搭配其他模式来优雅的实现。 在java.sql.Connection的源码中有 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801231126.png) #### (5)备忘录模式的优缺点 - **优点:** 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2、实现了信息的封装,使得用户不需要关心状态的保存细节。 - **缺点:** 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。 ### 2.8 状态模式 #### (1)概念 在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。 在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。 **主要解决**:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。 **何时使用**:代码中包含大量与对象状态有关的条件语句。 **如何解决**:将各种具体的状态类抽象出来。 **关键代码**:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。 #### (2)适用场景 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。 **注意事项**:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。 #### (3)代码示例 我们将创建一个 *State* 接口和实现了 *State* 接口的实体状态类。*Context* 是一个带有某个状态的类。 *StatePatternDemo*,我们的演示类使用 *Context* 和状态对象来演示 Context 在状态改变时的行为变化。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801233351.png) 创建一个接口。 ``` package com.alibaba.design.statepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-23:24 */ public interface State { public void doAction(Context context); } ``` 创建 *Context* 类。 ``` package com.alibaba.design.statepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-23:25 */ public class Context { private State state; public Context(){ state = null; } public void setState(State state){ this.state = state; } public State getState(){ return state; } } ``` 创建实现接口的实体类。 - StartState ``` package com.alibaba.design.statepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-23:25 */ public class StartState implements State { public void doAction(Context context) { System.out.println("Player is in start state"); context.setState(this); } @Override public String toString(){ return "Start State"; } } ``` - StopState ``` package com.alibaba.design.statepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-23:26 */ public class StopState implements State { public void doAction(Context context) { System.out.println("Player is in stop state"); context.setState(this); } @Override public String toString(){ return "Stop State"; } } ``` 使用 *Context* 来查看当状态 *State* 改变时的行为变化。 ``` package com.alibaba.design.statepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-23:26 */ public class StatePatternDemo { public static void main(String[] args) { Context context = new Context(); StartState startState = new StartState(); startState.doAction(context); System.out.println(context.getState().toString()); StopState stopState = new StopState(); stopState.doAction(context); System.out.println(context.getState().toString()); } } ``` 结果输出: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200801232752.png) #### (4)该模式在源码中的体现 **Spring State Machine示例**: 状态机(状态模式的一种应用)在工作流或游戏等各种系统中有大量使用,如各种工作流引擎,它几乎是状态机的子集和实现,封装状态的变化规则。Spring状态机帮助开发者简化状态机的开发过程,让状态机结构更加层次化。 参考链接 [https://cloud.tencent.com/developer/article/1487718](https://cloud.tencent.com/developer/article/1487718) #### (5)状态模式的优缺点 - **优点:** 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。 - **缺点:** 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。 #### (6) 与策略模式的比较 **后续:与策略模式的比较** **同:** 1、子类的使用:状态和策略模式都通过状态/策略的不同派生子类来更改具体实现。 2、模式类图:状态模式和策略模式之间最大的相似性之一是它们的类图,除了类名之外,它们看起来几乎相同。这两种模式都定义了状态/策略基类,子状态/子策略都继承基类。 3、两者都遵循开闭原则:状态模式的Context是对修改关闭的,即关于状态如何被访问和使用的逻辑是固定的。但是各个状态是开放的,也就是说,可以通过扩展可以添加更多的状态。类似地,策略模式的context是对修改关闭的,但是各个策略的子类是开放可扩展的。 **异:** 1、模式意图:策略模式的意图或目的是拥有一系列可互换的算法,这些算法可以根据context和/或客户需求进行选择。而状态模式的目的是管理对象的状态以及对象的行为,对象的行为会随着状态的变化而变化。 2、客户端对策略/状态的感知:在策略模式实现中,所选择的策略依赖于客户端,因此客户端知道使用的是哪种策略。而在状态模式实现中,客户端与context交互以对对象进行操作,但不决定选择哪种状态。对象本身似乎根据客户端通过context进行的交互来更改其状态类。 3、context的引用:状态模式中的每个状态都持有context的引用。但是,策略模式中每个策略并不持有context的引用。 4、状态/策略之间的关系:状态模式中的不同状态彼此相关,例如作为前一个或者后一个状态等。这是因为在状态之间像有限状态机有一个流动。然而,策略模式只是从多个可用策略中选择一个策略,策略之间没有后者/前者的关系。 5、怎样做/什么&何时做:多种策略定义了做某事的多种方式。而多个状态定义要做什么,并基于状态之间的关系定义何时做。 ### 2.9 策略模式 #### (1)概念 **策略模式(Strategy Pattern)是指定义了算法家族、分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的用户。** #### (2)策略模式的应用场景 > 1、假如系统中有很多类,而他们的区别仅仅在于他们的行为不同。 > 2、一个系统需要动态地在几种算法中选择一种。 #### (3)用策略模式实现选择支付方式的业务场景 大家都知道,很多电商平台经常会有优惠活动,优惠策略会有很多种可能如:领取优惠券抵扣、返现促销、拼团优惠。下面我们用代码来模拟。 整体的类图 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729114901.png) 首先我们创建一个促销策略的抽象 PromotionStrategy: ~~~java package com.alibaba.design.strategypattern.promotion; /** * 促销策略抽象 * @author zhouyanxiang * @create 2020-07-2020/7/29-11:01 */ public interface PromotionStrategy { public void doPromotion(); } ~~~ 然后分别创建优惠券抵扣策略 CouponStrategy 类、返现促销策略 CashbackStrategy类、拼团优惠策略 GroupbuyStrategy 类和无优惠策略 EmptyStrategy 类: - **CashbackStrategy类**(返现促销) ~~~Java package com.alibaba.design.strategypattern.promotion; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:03 */ public class CashbackStrategy implements PromotionStrategy { @Override public void doPromotion() { System.out.println("通过返现促销,消费额到达一定的额度后可以直接返现"); } } ~~~ - **CouponStrategy类**(优惠券促销) ~~~Java package com.alibaba.design.strategypattern.promotion; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:03 */ public class CouponStrategy implements PromotionStrategy { @Override public void doPromotion() { System.out.println("通过优惠券的形式来促销"); } } ~~~ - **EmptyStrategy类**(无促销) ~~~Java package com.alibaba.design.strategypattern.promotion; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:05 */ public class EmptyStrategy implements PromotionStrategy { @Override public void doPromotion() { System.out.println("无促销活动"); } } ~~~ - **GroupbuyStrategy类**(组团促销) ~~~Java、 package com.alibaba.design.strategypattern.promotion; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:05 */ public class GroupbuyStrategy implements PromotionStrategy { @Override public void doPromotion() { System.out.println("通过拼团促销"); } } ~~~ **然后创建促销活动方案 PromotionActivity 类** ``` package com.alibaba.design.strategypattern.promotion; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:06 */ public class PromotionActivity { private PromotionStrategy promotionStrategy; public PromotionActivity(PromotionStrategy promotionStrategy){ this.promotionStrategy = promotionStrategy; } public void execute(){ promotionStrategy.doPromotion(); } } ``` **编写客户端测试类PromotionActivityTest** ~~~Java package com.alibaba.design.strategypattern.test; import com.alibaba.design.strategypattern.promotion.CashbackStrategy; import com.alibaba.design.strategypattern.promotion.CouponStrategy; import com.alibaba.design.strategypattern.promotion.PromotionActivity; import com.alibaba.design.strategypattern.promotion.PromotionStrategyFactory; import org.apache.commons.lang3.StringUtils; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:23 */ public class PromotionActivityTest { public static void main(String[] args) { System.out.println("============No.1==========="); PromotionActivityTest.testPromotionActivity1(); System.out.println("============No.2==========="); PromotionActivityTest.testPromotionActivity2(); System.out.println("============No.3==========="); PromotionActivityTest.testPromotionActivity3(); } public static void testPromotionActivity1(){ PromotionActivity activity618 = new PromotionActivity(new CouponStrategy()); PromotionActivity activity1111 = new PromotionActivity(new CashbackStrategy()); activity618.execute(); activity1111.execute(); } public static void testPromotionActivity2() { PromotionActivity promotionActivity = null; String promotionKey = "COUPON"; if (StringUtils.equals(promotionKey, "COUPON")) { promotionActivity = new PromotionActivity(new CouponStrategy()); } else if (StringUtils.equals(promotionKey, "CASHBACK")) { promotionActivity = new PromotionActivity(new CashbackStrategy()); } promotionActivity.execute(); } public static void testPromotionActivity3() { String promotionKey = "GROUPBUY"; PromotionActivity promotionActivity = new PromotionActivity(PromotionStrategyFactory.getPromotionStrategy(promotionKey)); promotionActivity.execute(); } } ~~~ testPromotionActivity1()方法测试代码放到实际的业务场景其实并不实用,不能让客户自己选择不同的策略支付,而是由商家指定的方式,这样很不友好。 testPromotionActivity2()方法这样改造之后,满足了业务需求,客户可根据自己的需求选择不同的优惠策略了。不过经过一段时间的业务积累,我们的促销活动会越来越多代码将会需要更多判断逻辑可能也变得越来越复杂。这时候,我们是不需要思考代码是不是应该重构了?结合之前的单例模式和工厂模式,创建 PromotionStrategyFactory类 ~~~Java package com.alibaba.design.strategypattern.promotion; import java.util.HashMap; import java.util.Map; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:37 */ public class PromotionStrategyFactory { private static Map PROMOTION_STRATEGY_MAP = new HashMap(); static { PROMOTION_STRATEGY_MAP.put(PromotionKey.COUPON,new CouponStrategy()); PROMOTION_STRATEGY_MAP.put(PromotionKey.CASHBACK,new CashbackStrategy()); PROMOTION_STRATEGY_MAP.put(PromotionKey.GROUPBUY,new GroupbuyStrategy()); } private static final PromotionStrategy NON_PROMOTION = new EmptyStrategy(); private PromotionStrategyFactory(){} public static PromotionStrategy getPromotionStrategy(String promotionKey){ PromotionStrategy promotionStrategy = PROMOTION_STRATEGY_MAP.get(promotionKey); return promotionStrategy == null ? NON_PROMOTION : promotionStrategy; } private interface PromotionKey{ String COUPON = "COUPON"; String CASHBACK = "CASHBACK"; String GROUPBUY = "GROUPBUY"; } } ~~~ 代码优化之后,创建testPromotionActivity3()方法,每次上新活动,不影响原来的代码逻辑。 ==策略模式在 JDK 源码中的体现== 首先来看一个比较常用的比较器 Comparator 接口,我们看到的一个大家常用的compare()方法,就是一个策略抽象实现: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729171953.png) 最终根据什么去排序是由用户自己定义的。 比如,我们定义一个User类,里面包含姓名和分数两个属性 ~~~Java package com.alibaba.design.strategypattern.test; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-17:05 */ public class User { public String name; public int score; public User(String name, int score) { this.name =name; this.score = score; } } ~~~ 那么我们重写的compare方法如果是比较的分数的话,最终将会是按照分数进行排序 ~~~Java package com.alibaba.design.strategypattern.test; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-17:02 */ public class UserTest implements Comparator { @Override public int compare(User o1, User o2) { return o1.score-o2.score; } public static void main(String[] args) { ArrayList list = new ArrayList<>(); list.add(new User("Tom",98)); list.add(new User("Jerry",95)); list.add(new User("Jane",99)); list.add(new User("Mary",100)); Collections.sort(list, new UserTest()); for(User index:list) { System.out.println(" score: " + index.score + " name : " + index.name); } } } ~~~ ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729172505.png) 也可以按照name的首字母进行排序 ~~~Java package com.alibaba.design.strategypattern.test; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-17:02 */ public class UserTest implements Comparator { @Override public int compare(User u1, User u2) { return u1.name.compareTo(u2.name); } public static void main(String[] args) { ArrayList list = new ArrayList<>(); list.add(new User("Tom",98)); list.add(new User("Jerry",95)); list.add(new User("Jane",99)); list.add(new User("Mary",100)); Collections.sort(list, new UserTest()); for(User index:list) { System.out.println(" score: " + index.score + " name : " + index.name); } } } ~~~ ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729172546.png) 这就是策略模式在JDK中的应用之一。 还有一个非常典型的场景,Spring 的初始化也采用了策略模式,不同的类型的类采用不 同的初始化策略。首先有一个 InstantiationStrategy 接口,我们来看一下源码 ~~~Java package org.springframework.beans.factory.support; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.lang.Nullable; public interface InstantiationStrategy { Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3) throws BeansException; Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3, Constructor var4, @Nullable Object... var5) throws BeansException; Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3, @Nullable Object var4, Method var5, @Nullable Object... var6) throws BeansException; } ~~~ 顶层的策略抽象非常简单,但是它下面有两种策略 SimpleInstantiationStrategy 和 CglibSubclassingInstantiationStrategy,我们看一下类图: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729173510.png) 打开类图我们还发现 CglibSubclassingInstantiationStrategy 策略类还继承了SimpleInstantiationStrategy 类,说明在实际应用中多种策略之间还可以继承使用。我们可以作为一个参考,在实际业务场景中,可以根据需要来设计。 #### (4) 策略模式的优缺点 - 优点: 1、策略模式符合开闭原则。 2、避免使用多重条件转移语句,如 if...else...语句、switch 语句 3、使用策略模式可以提高算法的保密性和安全性。 - 缺点: 1、客户端必须知道所有的策略,并且自行决定使用哪一个策略类。 2、代码中会产生非常多策略类,增加维护难度。 ### 2.10 模板模式 #### (1)概念 **模板模式通常又叫模板方法模式(Template Method Pattern)是指定义一个算法的骨** **架,并允许子类为一个或者多个步骤提供实现。模板方法使得子类可以在不改变算法结** **构的情况下,重新定义算法的某些步骤,属于行为性设计模式。** #### (2)适用场景 **1、一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。** **2、各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。** **3、通常,在微服务架构中一般会建立一个common的module,里面抽取公共的类,然后其他的module来是这个module的子module** #### (3)代码示例 **整体的类图** ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729201055.png) 我们通常上网课看视频,通常网课的步骤是发布预习资料-->制作课件 PPT-->在线直播--> 提 交 课 堂 笔 记 --> 提 交 源 码 --> 布 置 作 业 --> 检 查 作 业 。 首先建立一个抽象类NetworkCourse - NetworkCourse 类 ~~~Java package com.alibaba.design.templatemethodpattern.course; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-19:50 */ public abstract class NetworkCourse { public final void createCourse(){ //1、发布预习资料 this.postPreResource(); //2、制作PPT课件 this.createPPT(); //3、在线直播 this.liveVideo(); //4、提交课件、课堂笔记 this.postNote(); //5、提交源码 this.postSource(); //6、布置作业,有些课是没有作业,有些课是有作业的 //如果有作业的话,检查作业,如果没作业,完成了 if(needHomeWork()){ checkHomeWork(); } } abstract void checkHomeWork(); //钩子方法:实现流程的微调 protected boolean needHomeWork(){return false;} final void postSource(){ System.out.println("提交源代码"); } final void postNote(){ System.out.println("提交课件和笔记"); } final void liveVideo(){ System.out.println("直播授课"); } final void createPPT(){ System.out.println("创建备课PPT"); } final void postPreResource(){ System.out.println("分发预习资料"); } } ~~~ 上述的钩子方法是设计可以进行流程的微调的,因为通常有些指标不是必须的,比如布置作业,有时候布置了有时候不用布置,因此可以这样来设计。 - JavaCourse类 ~~~java package com.alibaba.design.templatemethodpattern.course; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-19:51 */ public class JavaCourse extends NetworkCourse{ @Override void checkHomeWork() { System.out.println("检查java的作业"); } } ~~~ - PythonCourse类 ~~~java package com.alibaba.design.templatemethodpattern.course; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-19:52 */ public class PythonCourse extends NetworkCourse { private boolean needHomeworkFlag = false; public PythonCourse(boolean needHomeworkFlag){ this.needHomeworkFlag = needHomeworkFlag; } @Override void checkHomeWork() { System.out.println("检查Python的作业"); } @Override protected boolean needHomeWork() { return this.needHomeworkFlag; } } ~~~ - 客户端测试类 ~~~Java package com.alibaba.design.templatemethodpattern.test; import com.alibaba.design.templatemethodpattern.course.JavaCourse; import com.alibaba.design.templatemethodpattern.course.NetworkCourse; import com.alibaba.design.templatemethodpattern.course.PythonCourse; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-19:53 */ public class NetworkCourseTest { public static void main(String[] args) { System.out.println("---Java架构师课程---"); NetworkCourse javaCourse = new JavaCourse(); javaCourse.createCourse(); System.out.println("---Python课程---"); NetworkCourse pythonCourse = new PythonCourse(true); pythonCourse.createCourse(); } } ~~~ ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729201336.png) #### (4)模板模式在源码中的体现 在AbstractList类中的get方法就是一个抽象的方法 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729203411.png) 在AbstractList类的继承类中的ArrayList中对get方法进行了重写 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729203402.png) ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729203715.png) #### (5)模板模式的优缺点 - **优点:** 1、利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性。 2、将不同的代码不同的子类中,通过对子类的扩展增加新的行为,提高代码的扩展性。 3、把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台, 符合开闭原则。 - **缺点:** 1、类数目的增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加。 2、类数量的增加,间接地增加了系统实现的复杂度。 3、继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍。 ### 2.11 空对象模式 (不属于二十三中设计模式中的,这里是扩充一下) #### (1)概念 在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。 在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。 #### (2)适用场景 满足下列条件时可以使用空对象模式 - 一个对象需要一个协作对象,但并无具体的协作对象 - 协作对象不需要做任何事情 - 需要大量对空值进行判断的时候; #### (3)代码示例 我们将创建一个定义操作(在这里,是客户的名称)的 *AbstractCustomer* 抽象类,和扩展了 *AbstractCustomer* 类的实体类。工厂类 *CustomerFactory* 基于客户传递的名字来返回 *RealCustomer* 或 *NullCustomer* 对象。 *NullPatternDemo*,我们的演示类使用 *CustomerFactory* 来演示空对象模式的用法。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802081934.png) 创建一个抽象类。 ``` package com.alibaba.design.nullobjectpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-8:14 */ public abstract class AbstractCustomer { protected String name; public abstract boolean isNil(); public abstract String getName(); } ``` 创建扩展了上述类的实体类。 ``` package com.alibaba.design.nullobjectpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-8:16 */ public class NullCustomer extends AbstractCustomer { @Override public boolean isNil() { return true; } @Override public String getName() { return "Not Available in Customer Database"; } } ``` ``` package com.alibaba.design.nullobjectpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-8:15 */ public class RealCustomer extends AbstractCustomer { public RealCustomer(String name) { this.name = name; } @Override public boolean isNil() { return false; } @Override public String getName() { return name; } } ``` 创建 *CustomerFactory* 类。 ``` package com.alibaba.design.nullobjectpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-8:17 */ public class CustomerFactory { public static final String[] names = {"Rob", "Joe", "Julie"}; public static AbstractCustomer getCustomer(String name){ for (int i = 0; i < names.length; i++) { if (names[i].equalsIgnoreCase(name)){ return new RealCustomer(name); } } return new NullCustomer(); } } ``` 客户端测试类 ``` package com.alibaba.design.nullobjectpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-8:17 */ public class NullPatternDemo { public static void main(String[] args) { AbstractCustomer customer1 = CustomerFactory.getCustomer("Rob"); AbstractCustomer customer2 = CustomerFactory.getCustomer("Bob"); AbstractCustomer customer3 = CustomerFactory.getCustomer("Julie"); AbstractCustomer customer4 = CustomerFactory.getCustomer("Laura"); System.out.println("Customers"); System.out.println(customer1.getName()); System.out.println(customer2.getName()); System.out.println(customer3.getName()); System.out.println(customer4.getName()); } } ``` 输出结果: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802082439.png) #### (4)该模式在源码中的体现 commons-lang3 jar包里面的StringUtils 包含了isNotEmpty()就是对String类型的对象的空值判断,我们通常可以用他对字符串进行空值判断处理 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802083648.png) #### (5)空对象模式的优缺点 - 优点 可以加强系统的稳固性,能有效防止空指针报错对整个系统的影响; 不依赖客户端便可以保证系统的稳定性; - 缺点 需要编写较多的代码来实现空值的判断,从某种方面来说不划算; ### 2.12 访问者模式 #### (1)概念 在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。 **主要解决**:稳定的数据结构和易变的操作耦合问题。 **何时使用**:需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。 **如何解决**:在被访问的类里面加一个对外提供接待访问者的接口。 **关键代码**:在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。 #### (2)适用场景 1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。 2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。 **注意事项**:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。 #### (3)代码示例 我们将创建一个定义接受操作的 *ComputerPart* 接口。*Keyboard*、*Mouse*、*Monitor* 和 *Computer* 是实现了 *ComputerPart* 接口的实体类。我们将定义另一个接口 *ComputerPartVisitor*,它定义了访问者类的操作。*Computer* 使用实体访问者来执行相应的动作。 *VisitorPatternDemo*,我们的演示类使用 *Computer*、*ComputerPartVisitor* 类来演示访问者模式的用法。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802091025.png) 定义一个表示元素的接口。 - ComputerPart ``` package com.alibaba.design.visitorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-8:54 */ public interface ComputerPart { public void accept(ComputerPartVisitor computerPartVisitor); } ``` 定义一个表示访问者的接口。 - ComputerPartVisitor ``` package com.alibaba.design.visitorpattern; import com.alibaba.design.visitorpattern.Mouse; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-8:54 */ public interface ComputerPartVisitor { public void visit(Computer computer); public void visit(Mouse mouse); public void visit(Keyboard keyboard); public void visit(Monitor monitor); } ``` 创建扩展了ComputerPart 的实体类。 - Computer ``` package com.alibaba.design.visitorpattern; import com.alibaba.design.visitorpattern.Mouse; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-8:56 */ public class Computer implements ComputerPart { ComputerPart[] parts; public Computer(){ parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()}; } @Override public void accept(ComputerPartVisitor computerPartVisitor) { for (int i = 0; i < parts.length; i++) { parts[i].accept(computerPartVisitor); } computerPartVisitor.visit(this); } } ``` - Keyboard ``` package com.alibaba.design.visitorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-8:55 */ public class Keyboard implements ComputerPart { @Override public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } } ``` - Monitor ``` package com.alibaba.design.visitorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-9:05 */ public class Monitor implements ComputerPart { @Override public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } } ``` - Mouse ``` package com.alibaba.design.visitorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-9:05 */ public class Mouse implements ComputerPart { @Override public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } } ``` 创建实现了上述类的实体访问者。 - ComputerPartDisplayVisitor ``` package com.alibaba.design.visitorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-9:06 */ public class ComputerPartDisplayVisitor implements ComputerPartVisitor { @Override public void visit(Computer computer) { System.out.println("Displaying Computer."); } @Override public void visit(Mouse mouse) { System.out.println("Displaying Mouse."); } @Override public void visit(Keyboard keyboard) { System.out.println("Displaying Keyboard."); } @Override public void visit(Monitor monitor) { System.out.println("Displaying Monitor"); } } ``` 客户端测试类 ``` package com.alibaba.design.visitorpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-9:08 */ public class VisitorPatternDemo { public static void main(String[] args) { ComputerPart computer = new Computer(); computer.accept(new ComputerPartDisplayVisitor()); } } ``` 输出结果: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802091049.png) #### (4)该模式在源码中的体现 在Spring中的应用: 它的具体实现都交给了valueResolver来实现 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802092506.png) 方法visitBeanDefinition实现了不同的visi方法t来对相同的数据进行不同的处理 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802092724.png) #### (5)访问者模式的优缺点 **优点:** 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。 **缺点:** 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。 ## 三、结构型模式 **一共七种** ### 3.1 适配器模式 #### (1)概念 **适配器模式(Adapter Pattern)是指将一个类的接口转换成客户期望的另一个接口,使** **原本的接口不兼容的类可以一起工作,属于结构型设计模式。** #### (2)适用场景 - 1、已经存在的类,它的方法和需求不匹配(方法结果相同或相似)的情况。 - 2、适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。有点亡羊补牢的感觉。生活中也非常的应用场景,例如电源插转换头、手机充电转换头、显示器转接头。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729210147.png) 在中国民用电都是 220V 交流电,但我们手机使用的锂电池使用的 5V 直流电。因此,我 们给手机充电时就需要使用电源适配器来进行转换。 #### (3)代码示例 创建 AC220 类,表示 220V 交流电: - AC220 ~~~java package com.alibaba.design.adapterpattern.powerapapter; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-21:06 */ public class AC220 { public int outputAC220V(){ int output = 220; System.out.println("输出电流" + output + "V"); return output; } } ~~~ 创建 DC5 接口,表示 5V 直流电的标准: - DC5 ~~~java package com.alibaba.design.adapterpattern.powerapapter; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-21:07 */ public interface DC5 { public int outoupDC5V(); } ~~~ 创建电源适配器 PowerAdapter 类: - PowerAdapter ~~~java package com.alibaba.design.adapterpattern.powerapapter; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-21:07 */ public class PowerAdapter implements DC5 { private AC220 ac220; public PowerAdapter(AC220 ac220) { this.ac220 = ac220; } @Override public int outoupDC5V() { int adapterInput = ac220.outputAC220V(); int adapterOutput = adapterInput / 44; System.out.println("使用PowerAdapter输入AC:" + adapterInput + "V,输出DC:" + adapterOutput + "V"); return adapterOutput; } } ~~~ 客户端测试类PowerAdapterTest - PowerAdapterTest ~~~java package com.alibaba.design.adapterpattern.test; import com.alibaba.design.adapterpattern.powerapapter.AC220; import com.alibaba.design.adapterpattern.powerapapter.DC5; import com.alibaba.design.adapterpattern.powerapapter.PowerAdapter; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-21:10 */ public class PowerAdapterTest { public static void main(String[] args) { DC5 dc5 = new PowerAdapter(new AC220()); dc5.outoupDC5V(); } } ~~~ 上面的案例中,通过增加 PowerAdapter 电源适配器,实现了二者的兼容。 #### (4)重构第三登录自由适配的业务场景 下面我们来一个实际的业务场景,利用适配模式来解决实际问题。我们很早以前开发的老系统应该都有登录接口,但是随着业务的发展和社会的进步,单纯地依赖用户名密码登录显然不能满足用户需求了。现在,我们大部分系统都已经支持多种登录方式,如 QQ 登录、微信登录、手机登录、微博登录等等,同时保留用户名密码的登录方式。虽然登录形式丰富了,但是登录后的处理逻辑可以不必改,同样是将登录状态保存到 session,遵循开闭原则。首先创建统一的返回结果 ResultMsg 类: ~~~Java package com.alibaba.design.adapterpattern.loginadapter; /** * Created by Tom. */ public class ResultMsg { private int code; private String msg; private Object data; public ResultMsg(int code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } } ~~~ 假设老系统的登录逻辑 SiginService: ~~~Java package com.alibaba.design.adapterpattern.loginadapter.v1.service; import com.alibaba.design.adapterpattern.loginadapter.Member; import com.alibaba.design.adapterpattern.loginadapter.ResultMsg; /** * Created by Tom. */ public class SiginService { /** * 注册方法 * @param username * @param password * @return */ public ResultMsg regist(String username, String password){ return new ResultMsg(200,"注册成功",new Member()); } /** * 登录的方法 * @param username * @param password * @return */ public ResultMsg login(String username,String password){ return null; } } ~~~ 为了遵循开闭原则,老系统的代码我们不会去修改。那么下面开启代码重构之路,先创建 Member 类: ~~~Java package com.alibaba.design.adapterpattern.loginadapter; /** * Created by Tom. */ public class Member { private String username; private String password; private String mid; private String info; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getMid() { return mid; } public void setMid(String mid) { this.mid = mid; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } } ~~~ 创建一个新的类SinginForThirdService继承原来的逻辑,运行非常稳定的代码我们不去改动: ~~~java package com.alibaba.design.adapterpattern.loginadapter.v1.service; import com.alibaba.design.adapterpattern.loginadapter.ResultMsg; /** * Created by Tom on 2019/3/16. */ public class SinginForThirdService extends SiginService { public ResultMsg loginForQQ(String openId){ //1、openId是全局唯一,我们可以把它当做是一个用户名(加长) //2、密码默认为QQ_EMPTY //3、注册(在原有系统里面创建一个用户) //4、调用原来的登录方法 return loginForRegist(openId,null); } public ResultMsg loginForWechat(String openId){ return null; } public ResultMsg loginForToken(String token){ //通过token拿到用户信息,然后再重新登陆了一次 return null; } public ResultMsg loginForTelphone(String telphone,String code){ return null; } public ResultMsg loginForRegist(String username,String password){ super.regist(username,null); return super.login(username,null); } } ~~~ 客户端测试代码 ~~~java package com.alibaba.design.adapterpattern.loginadapter.v1; import com.alibaba.design.adapterpattern.loginadapter.v1.service.SinginForThirdService; /** * Created by Tom on 2019/3/16. */ public class SiginForThirdServiceTest { public static void main(String[] args) { SinginForThirdService service = new SinginForThirdService(); service.login("tom","123456"); service.loginForQQ("sdfasdfasf"); service.loginForWechat("sdfasfsa"); } } ~~~ 通过这么一个简单的适配,完成了代码兼容。当然,我们代码还可以更加优雅,根据不 同的登录方式,创建不同的 Adapter。首先,创建 LoginAdapter 接口: ~~~java package com.alibaba.design.adapterpattern.loginadapter.v2.adapters; import com.alibaba.design.adapterpattern.loginadapter.ResultMsg; /** * 在适配器里面,这个接口是可有可无,不要跟模板模式混淆 * 模板模式一定是抽象类,而这里仅仅只是一个接口 * Created by Tom */ public interface LoginAdapter { boolean support(Object adapter); ResultMsg login(String id, Object adapter); } ~~~ 分别实现不同的登录适配,QQ 登录 LoginForQQAdapter: ~~~java package com.alibaba.design.adapterpattern.loginadapter.v2.adapters; import com.alibaba.design.adapterpattern.loginadapter.ResultMsg; /** * Created by Tom on 2019/3/16. */ public class LoginForQQAdapter implements LoginAdapter { @Override public boolean support(Object adapter) { return adapter instanceof LoginForQQAdapter; } @Override public ResultMsg login(String id, Object adapter) { return null; } } ~~~ 新浪微博登录 LoginForSinaAdapter: ~~~java package com.alibaba.design.adapterpattern.loginadapter.v2.adapters; import com.alibaba.design.adapterpattern.loginadapter.ResultMsg; /** * Created by Tom. */ public class LoginForSinaAdapter implements LoginAdapter { @Override public boolean support(Object adapter) { return adapter instanceof LoginForSinaAdapter; } @Override public ResultMsg login(String id, Object adapter) { return null; } } ~~~ 手机号登录 LoginForTelAdapter: ~~~java package com.alibaba.design.adapterpattern.loginadapter.v2.adapters; import com.alibaba.design.adapterpattern.loginadapter.ResultMsg; /** * Created by Tom. */ public class LoginForTelAdapter implements LoginAdapter { @Override public boolean support(Object adapter) { return adapter instanceof LoginForTelAdapter; } @Override public ResultMsg login(String id, Object adapter) { return null; } } ~~~ Token 自动登录 LoginForTokenAdapter: ``` package com.alibaba.design.adapterpattern.loginadapter.v2.adapters; import com.alibaba.design.adapterpattern.loginadapter.ResultMsg; /** * Created by Tom. */ public class LoginForTokenAdapter implements LoginAdapter { @Override public boolean support(Object adapter) { return adapter instanceof LoginForTokenAdapter; } @Override public ResultMsg login(String id, Object adapter) { return null; } } ``` 微信登录 LoginForWechatAdapter: ``` package com.alibaba.design.adapterpattern.loginadapter.v2.adapters; import com.alibaba.design.adapterpattern.loginadapter.ResultMsg; /** * Created by Tom. */ public class LoginForWechatAdapter implements LoginAdapter { @Override public boolean support(Object adapter) { return adapter instanceof LoginForWechatAdapter; } @Override public ResultMsg login(String id, Object adapter) { return null; } } ``` 然后,创建第三方登录兼容接口 IPassportForThird: ``` package com.alibaba.design.adapterpattern.loginadapter.v2; import com.alibaba.design.adapterpattern.loginadapter.ResultMsg; /** * 只扩展 * Created by Tom on 2019/3/16. */ public interface IPassportForThird { /** * QQ登录 * @param id * @return */ ResultMsg loginForQQ(String id); /** * 微信登录 * @param id * @return */ ResultMsg loginForWechat(String id); /** * 记住登录状态后自动登录 * @param token * @return */ ResultMsg loginForToken(String token); /** * 手机号登录 * @param telphone * @param code * @return */ ResultMsg loginForTelphone(String telphone, String code); /** * 注册后自动登录 * @param username * @param passport * @return */ ResultMsg loginForRegist(String username, String passport); } ``` 实现兼容 PassportForThirdAdapter: ``` package com.alibaba.design.adapterpattern.loginadapter.v2; import com.alibaba.design.adapterpattern.loginadapter.ResultMsg; import com.alibaba.design.adapterpattern.loginadapter.v1.service.SiginService; import com.alibaba.design.adapterpattern.loginadapter.v2.adapters.*; /** * 结合策略模式、工厂模式、适配器模式 * Created by Tom on 2019/3/16. */ public class PassportForThirdAdapter extends SiginService implements IPassportForThird { @Override public ResultMsg loginForQQ(String id) { // return processLogin(id,RegistForQQAdapter.class); return processLogin(id, LoginForQQAdapter.class); } @Override public ResultMsg loginForWechat(String id) { return processLogin(id, LoginForWechatAdapter.class); } @Override public ResultMsg loginForToken(String token) { return processLogin(token, LoginForTokenAdapter.class); } @Override public ResultMsg loginForTelphone(String telphone, String code) { return processLogin(telphone, LoginForTelAdapter.class); } @Override public ResultMsg loginForRegist(String username, String passport) { super.regist(username,passport); return super.login(username,passport); } private ResultMsg processLogin(String key,Class clazz){ try{ //适配器不一定要实现接口 LoginAdapter adapter = clazz.newInstance(); //判断传过来的适配器是否能处理指定的逻辑 if(adapter.support(adapter)){ return adapter.login(key,adapter); } }catch (Exception e){ e.printStackTrace(); } return null; } //类图的快捷键 Ctrl + Alt + Shift + U } ``` 客户端测试代码: ``` package com.alibaba.design.adapterpattern.loginadapter.v2; /** * Created by Tom. */ public class PassportTest { public static void main(String[] args) { IPassportForThird passportForThird = new PassportForThirdAdapter(); passportForThird.loginForQQ(""); } } ``` 最后,来看一下类图: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729213335.png) ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729213344.png) 至此,我们在遵循开闭原则的前提下,完整地实现了一个兼容多平台登录的业务场景。 #### (5)模式在源码中的体现 Spring 中适配器模式也应用得非常广泛,例如:SpringAOP 中的 AdvisorAdapter 类,它有三个实现类 MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter 和ThrowsAdviceAdapter,先来看顶层接口 AdvisorAdapter 的源代码: ~~~java package org.springframework.aop.framework.adapter; import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.springframework.aop.Advisor; public interface AdvisorAdapter { boolean supportsAdvice(Advice var1); MethodInterceptor getInterceptor(Advisor var1); } ~~~ 再看 MethodBeforeAdviceAdapter 类: ~~~java package org.springframework.aop.framework.adapter; import java.io.Serializable; import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.springframework.aop.Advisor; import org.springframework.aop.MethodBeforeAdvice; class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable { MethodBeforeAdviceAdapter() { } public boolean supportsAdvice(Advice advice) { return advice instanceof MethodBeforeAdvice; } public MethodInterceptor getInterceptor(Advisor advisor) { MethodBeforeAdvice advice = (MethodBeforeAdvice)advisor.getAdvice(); return new MethodBeforeAdviceInterceptor(advice); } } ~~~ Spring 会根据不同的 AOP 配置来确定使用对应的 Advice,跟策略模式不同的一个方法可以同时拥有多个 Advice。 #### (6)适配器模式的优缺点 - **优点:** 1、能提高类的透明性和复用,现有的类复用但不需要改变。 2、目标类和适配器类解耦,提高程序的扩展性。 3、在很多业务场景中符合开闭原则。 - **缺点:** 1、适配器编写过程需要全面考虑,可能会增加系统的复杂性。 2、增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。 ### 3.2 代理模式 #### (1) 概念 代理模式(ProxyPattern)的定义也非常简单,是指为其他对象提供一种代理,以控制对这个对象的访问。 代理对象在客服端和目标对象之间起到中介作用,代理模式属于结构型设计模式。 #### (2)适用场景 使用代理模式主要有两个目的:一保护目标对象,二增强目标对象。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730110952.png) Subject 是顶层接口,RealSubject 是真实对象(被代理对象),Proxy 是代理对象,代理对象持有被代理对象的引用,客户端调用代理对象方法,同时也调用被代理对象的方法,但是在代理对象前后增加一些处理。在代码中,我们想到代理,就会理解为是代码增强,其实就是在原本逻辑前后增加一些逻辑,而调用者无感知。代理模式属于结构型模式,有静态代理和动态代理 #### (3)代码示例 ##### 1. 静态代理模式 举个例子:人到了适婚年龄,父母总是迫不及待希望早点抱孙子。而现在社会的人在各种压力之下,都选择晚婚晚育。于是着急的父母就开始到处为自己的子女相亲,比子女自己还着急。这个相亲的过程,就是一种我们人人都有份的代理。来看代码实现: 顶层接口 Person: ~~~Java package com.alibaba.design.proxypattern; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-10:54 */ public interface Person { public void findLove(); } ~~~ 儿子要找对象,实现 Son 类: ~~~Java package com.alibaba.design.proxypattern.staticproxy; import com.alibaba.design.proxypattern.Person; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-10:55 */ public class Son implements Person { @Override public void findLove() { System.out.println("儿子要求:肤白貌美大长腿"); } } ~~~ 父亲要帮儿子相亲,实现 Father 类: ~~~Java package com.alibaba.design.proxypattern.staticproxy; import com.alibaba.design.proxypattern.Person; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-10:55 */ public class Father implements Person { private Son son; public Father(Son son){ this.son = son; } @Override public void findLove(){ System.out.println("父亲物色对象"); this.son.findLove(); System.out.println("双方父母同意,确立关系"); } public void findJob(){ } } ~~~ 来看测试代码: - FatherTest ~~~Java package com.alibaba.design.proxypattern.staticproxy; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-10:58 */ public class FatherTest { public static void main(String[] args) { Father father = new Father(new Son()); father.findLove(); //农村,媒婆 //婚介所 //大家每天都在用的一种静态代理的形式 //对数据库进行分库分表 //ThreadLocal //进行数据源动态切换 } } ~~~ ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730110406.png) ##### 2. 动态代理模式 动态代理和静态对比基本思路是一致的,只不过动态代理功能更加强大,随着业务的扩展适应性更强。如果还以找对象为例,使用动态代理相当于是能够适应复杂的业务场景。不仅仅只是父亲给儿子找对象,如果找对象这项业务发展成了一个产业,进而出现了媒婆、婚介所等这样的形式。那么,此时用静态代理成本就更大了,需要一个更加通用的解决方案,要满足任何单身人士找对象的需求。我们升级一下代码,先来看 JDK 实现方式: 创建媒婆(婚介)JDKMeipo 类: ``` package com.alibaba.design.proxypattern.dynamicproxy.jdkproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-10:52 */ public class JDKMeipo implements InvocationHandler { private Object target; public Object getInstance(Object target) throws Exception{ this.target = target; Class clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object obj = method.invoke(this.target,args); after(); return obj; } private void before(){ System.out.println("我是媒婆,我要给你找对象,现在已经确认你的需求"); System.out.println("开始物色"); } private void after(){ System.out.println("OK的话,准备办事"); } } ``` 创建单身客户 Customer 类: ~~~Java package com.alibaba.design.proxypattern.dynamicproxy.jdkproxy; import com.alibaba.design.proxypattern.Person; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-11:11 */ public class Customer implements Person { @Override public void findLove() { System.out.println("高富帅"); System.out.println("身高180cm"); System.out.println("有6块腹肌"); } } ~~~ 测试代码 ~~~Java package com.alibaba.design.proxypattern.dynamicproxy.jdkproxy; import java.lang.reflect.Method; /** * @author zhouyanxiang * @create 2020-07-2020/7/30-11:11 */ public class JDKProxyTest { public static void main(String[] args) { try { Object obj = new JDKMeipo().getInstance(new Customer()); Method method = obj.getClass().getMethod("findLove",null); method.invoke(obj); }catch (Exception e){ e.printStackTrace(); } } } ~~~ ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730111329.png) #### (4) 模式在源码中的体现 代理模式在 Spring 源码中的应用,先看 ProxyFactoryBean 核心的方法就是 getObject()方法,我们来看一下源码: ~~~java public Object getObject() throws BeansException { initializeAdvisorChain(); if (isSingleton()) { return getSingletonInstance(); }else { if (this.targetName == null) { logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " + "Enable prototype proxies by setting the 'targetName' property."); } return newPrototypeInstance(); } } ~~~ 在 getObject()方法中,主要调用 getSingletonInstance()和 newPrototypeInstance();在 Spring 的配置中,如果不做任何设置,那么 Spring 代理生成的 Bean 都是单例对象。如果修改 scope 则每次创建一个新的原型对象。 Spring 利用动态代理实现 AOP 有两个非常重要的类,一个是 JdkDynamicAopProxy 类 和 CglibAopProxy 类,来看一下类图: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200730112303.png) Spring 中的代理选择原则 - 1、当 Bean 有实现接口时,Spring 就会用 JDK 的动态代理。 - 2、当 Bean 没有实现接口时,Spring 选择 CGLib。 - 3、Spring 可以通过配置强制使用 CGLib,只需在 Spring 的配置文件中加入如下代码: ~~~Java ~~~ 参考资料: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html #### (5)代理模式的优缺点 - **优点:** 1、代理模式能将代理对象与真实被调用的目标对象分离。 2、一定程度上降低了系统的耦合度,扩展性好。 3、可以起到保护目标对象的作用。 4、可以对目标对象的功能增强。 - **缺点:** 1、代理模式会造成系统设计中类的数量增加。 2、在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。 3、增加了系统的复杂度。 #### (6) 动态代理模式和静态代理模式的区别 - 1、静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步 新增,违背开闭原则。 - 2、动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开 闭原则。 - 3、若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成, 无需修改代理类的代码。 ### 3.3 装饰器模式 #### (1)概念 装饰者模式(Decorator Pattern)是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。 #### (2)适用场景 装饰者模式在我们生活中应用也比较多如给煎饼加鸡蛋;给蛋糕加上一些水果;给房子装修等,为对象扩展一些额外的职责。装饰者在代码程序中适用于以下场景: 1、用于扩展一个类的功能或给一个类添加附加职责。 2、动态的给一个对象添加功能,这些功能可以再动态的撤销。 #### (3)代码示例 生活中,我们在买手抓饼一般有时候会选择加鸡蛋或者加火腿,我们就用代码来实现这个流程 - 最基础的煎饼类 Battercake ~~~Java package com.alibaba.design.decoratorpattern.battercake.v1; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-22:13 */ public class Battercake { protected String getMsg(){ return "煎饼"; } public int getPrice(){ return 5; } } ~~~ - 煎饼加鸡蛋类 BattercakeWithEgg ``` package com.alibaba.design.decoratorpattern.battercake.v1; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-22:14 */ public class BattercakeWithEgg extends Battercake { @Override protected String getMsg() { return super.getMsg() + "+1个鸡蛋"; } @Override //加一个鸡蛋加1块钱 public int getPrice() { return super.getPrice() + 1; } } ``` - 煎饼加鸡蛋和火腿 BattercakeWithEggAndSausage ``` package com.alibaba.design.decoratorpattern.battercake.v1; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-22:15 */ public class BattercakeWithEggAndSausage extends Battercake { @Override protected String getMsg() { return super.getMsg() + "+1根香肠"; } @Override //加一个香肠加2块钱 public int getPrice() { return super.getPrice() + 2; } } ``` 客户端测试类 ``` package com.alibaba.design.decoratorpattern.battercake.v1; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-22:15 */ public class BattercakeTest { public static void main(String[] args) { Battercake battercake = new Battercake(); System.out.println(battercake.getMsg() + ",总价格:" + battercake.getPrice()); Battercake battercakeWithEgg = new BattercakeWithEgg(); System.out.println(battercakeWithEgg.getMsg() + ",总价格:" + battercakeWithEgg.getPrice()); Battercake battercakeWithEggAndSausage = new BattercakeWithEggAndSausage(); System.out.println(battercakeWithEggAndSausage.getMsg() + ",总价格:" + battercakeWithEggAndSausage.getPrice()); } } ``` ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729222214.png) 初步一看似乎没啥问题,但是,如果用户需要一个加 2 个鸡蛋加 1 根香肠的煎饼,那么用我们现在的类结构是创建不出来的,也无法自动计算出价格,除非再创建一个类做定制。如果需求再变,一直加定制显然是不科学的。那么下面我们就用装饰者模式来解决上面的问题。首先创建一个建煎饼的抽象 Battercake 类: ``` package com.alibaba.design.decoratorpattern.battercake.v2; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-22:23 */ public abstract class Battercake { protected abstract String getMsg(); protected abstract int getPrice(); } ``` - 创建一个基本的煎饼(或者叫基础套餐)BaseBattercake: ``` package com.alibaba.design.decoratorpattern.battercake.v2; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-22:24 */ public class BaseBattercake extends Battercake { @Override protected String getMsg(){ return "煎饼"; } @Override public int getPrice(){ return 5; } } ``` 然后,再创建一个扩展套餐的抽象装饰者 BattercakeDecotator 类: - BattercakeDecorator ``` package com.alibaba.design.decoratorpattern.battercake.v2; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-22:25 */ public abstract class BattercakeDecorator extends Battercake { //静态代理,委派 private Battercake battercake; public BattercakeDecorator(Battercake battercake) { this.battercake = battercake; } protected abstract void doSomething(); @Override protected String getMsg() { return this.battercake.getMsg(); } @Override protected int getPrice() { return this.battercake.getPrice(); } } ``` 然后,创建鸡蛋装饰者 EggDecorator 类: - EggDecorator ``` package com.alibaba.design.decoratorpattern.battercake.v2; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-22:26 */ public class EggDecorator extends BattercakeDecorator { public EggDecorator(Battercake battercake) { super(battercake); } @Override protected void doSomething() { } @Override protected String getMsg() { return super.getMsg() + "+1个鸡蛋"; } @Override protected int getPrice() { return super.getPrice() + 1; } } ``` 创建香肠装饰者 SausageDecorator 类: - SausageDecorator ``` package com.alibaba.design.decoratorpattern.battercake.v2; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-22:26 */ public class SausageDecorator extends BattercakeDecorator { public SausageDecorator(Battercake battercake) { super(battercake); } @Override protected void doSomething() { } @Override protected String getMsg() { return super.getMsg() + "+1根香肠"; } @Override protected int getPrice() { return super.getPrice() + 2; } } ``` - 客户端测试类 BattercakeTest ``` package com.alibaba.design.decoratorpattern.battercake.v2; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-22:27 */ public class BattercakeTest { public static void main(String[] args) { Battercake battercake; //路边摊买一个煎饼 battercake = new BaseBattercake(); //煎饼有点小,想再加一个鸡蛋 battercake = new EggDecorator(battercake); //再加一个鸡蛋 // battercake = new EggDecorator(battercake); //很饿,再加根香肠 battercake = new SausageDecorator(battercake); battercake = new SausageDecorator(battercake); battercake = new SausageDecorator(battercake); battercake = new SausageDecorator(battercake); battercake = new SausageDecorator(battercake); //跟静态代理最大区别就是职责不同 //静态代理不一定要满足is-a的关系 //静态代理会做功能增强,同一个职责变得不一样 //装饰器更多考虑是扩展 System.out.println(battercake.getMsg() + ",总价:" + battercake.getPrice()); } } ``` ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729223221.png) 整个类图 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729223328.png) 装饰者模式最本质的特征是讲原有类的附加功能抽离出来,简化原有类的逻辑。通过这样两个案例,我们可以总结出来,其实抽象的装饰者是可有可无的,具体可以根据业务模型来选择。 ###### 装饰者模式和适配器模式对比 装饰者和适配器模式都是包装模式(Wrapper Pattern),装饰者也是一种特殊的代理模式 | | 装饰者模式 | 适配器模式 | | ---- | ------------------------------------------------------------ | ------------------------------------------------------------ | | 形式 | 是一种非常特别的适配器模式 | 没有层级关系,装饰器模式有层级关系 | | 定义 | 装饰者和被装饰者都实现同一个接口,主要目的是为了扩展之后依旧保留 OOP 关系 | 适配器和被适配者没有必然的联系,通常是采用继承或代理的形式进行包装 | | 关系 | 满足 is-a 的关系 | 满足 has-a 的关系 | | 功能 | 注重覆盖、扩展 | 注重兼容、转换 | | 设计 | 前置考虑 | 后置考虑 | #### (4)模式在源码中的体现 装饰器模式在源码中也应用得非常多,在 JDK 中体现最明显的类就是 IO 相关的类,如BufferedReader、InputStream、OutputStream,看一下常用的 InputStream 的类结构图 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729224033.png) 在 Spring 中的 TransactionAwareCacheDecorator 类我们也可以来尝试理解一下,这个类主要是用来处理事务缓存的,来看一下代码: ~~~Java public class TransactionAwareCacheDecorator implements Cache { private final Cache targetCache; public TransactionAwareCacheDecorator(Cache targetCache) { Assert.notNull(targetCache, "Target Cache must not be null"); this.targetCache = targetCache; } public Cache getTargetCache() { return this.targetCache; } ... } ~~~ TransactionAwareCacheDecorator 就是对 Cache 的一个包装。再来看一个 MVC 中的装饰者模式 HttpHeadResponseDecorator 类: ~~~Java public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator { public HttpHeadResponseDecorator(ServerHttpResponse delegate) { super(delegate); } ... } ~~~ 最后,看看 MyBatis 中的一段处理缓存的设计 org.apache.ibatis.cache.Cache 类,找到它的包定位: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200729224358.png) 从名字上来看其实更容易理解了。比如 FIFO Cache 先入先出算法的缓存;LRU Cache 最近最少使用的缓存;TransactionlCache 事务相关的缓存,都是采用装饰者模式。 #### (5)装饰器模式的优缺点 - **优点:** 1、装饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象 扩展功能,即插即用。 2、通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。 3、装饰者完全遵守开闭原则。 - **缺点:** 1、会出现更多的代码,更多的类,增加程序复杂性。 2、动态装饰时,多层装饰时会更复杂。 那么装饰者模式我们就讲解到这里,希望小伙伴们认真体会,加深理解。 ### 3.4 桥接模式 #### (1)概念 桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。 这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。 #### (2)适用场景 1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。 2、对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。 3、一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。 **注意事项**:对于两个独立变化的维度,使用桥接模式再适合不过了。 #### (3)代码示例 我们有一个作为桥接实现的 *DrawAPI* 接口和实现了 *DrawAPI* 接口的实体类 *RedCircle*、*GreenCircle*。*Shape* 是一个抽象类,将使用 *DrawAPI* 的对象。*BridgePatternDemo*,我们的演示类使用 *Shape* 类来画出不同颜色的圆。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802174245.png) 创建桥接实现接口。 ``` package com.alibaba.design.bridgepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-17:37 */ public interface DrawAPI { public void drawCircle(int radius, int x, int y); } ``` 创建实现了 *DrawAPI* 接口的实体桥接实现类。 - GreenCircle ``` package com.alibaba.design.bridgepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-17:38 */ public class GreenCircle implements DrawAPI { @Override public void drawCircle(int radius, int x, int y) { System.out.println("Drawing Circle[ color: green, radius: " + radius +", x: " +x+", "+ y +"]"); } } ``` - RedCircle ``` package com.alibaba.design.bridgepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-17:38 */ public class RedCircle implements DrawAPI { @Override public void drawCircle(int radius, int x, int y) { System.out.println("Drawing Circle[ color: red, radius: " + radius +", x: " +x+", "+ y +"]"); } } ``` 使用 *DrawAPI* 接口创建抽象类 *Shape*。 ``` package com.alibaba.design.bridgepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-17:39 */ public abstract class Shape { protected DrawAPI drawAPI; protected Shape(DrawAPI drawAPI){ this.drawAPI = drawAPI; } public abstract void draw(); } ``` 创建实现了 *Shape* 接口的实体类。 ``` package com.alibaba.design.bridgepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-17:39 */ public class Circle extends Shape { private int x, y, radius; public Circle(int x, int y, int radius, DrawAPI drawAPI) { super(drawAPI); this.x = x; this.y = y; this.radius = radius; } @Override public void draw() { drawAPI.drawCircle(radius,x,y); } } ``` 使用 *Shape* 和 *DrawAPI* 类画出不同颜色的圆。 ``` package com.alibaba.design.bridgepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-17:40 */ public class BridgePatternDemo { public static void main(String[] args) { Shape redCircle = new Circle(100,100, 10, new RedCircle()); Shape greenCircle = new Circle(100,100, 10, new GreenCircle()); redCircle.draw(); greenCircle.draw(); } } ``` 输出结果: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802174154.png) #### (4)该模式在源码中的体现 如果从桥接模式来看,java.sql.Driver就是一个接口 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802175143.png) 下面可以有MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类。那么我们现在来看看MySQL中的Driver类 ~~~java public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } } ~~~ 特别简短的代码,其实只调用了DriverManager中的registerDriver方法来注册驱动。当驱动注册完成后,我们就会开始调用DriverManager中的getConnection方法了 ~~~Java public class DriverManager { public static Connection getConnection(String url, String user, String password) throws SQLException { java.util.Properties info = new java.util.Properties(); if (user != null) { info.put("user", user); } if (password != null) { info.put("password", password); } return (getConnection(url, info, Reflection.getCallerClass())); } private static Connection getConnection( String url, java.util.Properties info, Class caller) throws SQLException { /* * When callerCl is null, we should check the application's * (which is invoking this class indirectly) * classloader, so that the JDBC driver class outside rt.jar * can be loaded from here. */ ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { // synchronize loading of the correct classloader. if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } } if(url == null) { throw new SQLException("The url cannot be null", "08001"); } println("DriverManager.getConnection(\"" + url + "\")"); // Walk through the loaded registeredDrivers attempting to make a connection. // Remember the first exception that gets raised so we can reraise it. SQLException reason = null; for(DriverInfo aDriver : registeredDrivers) { // If the caller does not have permission to load the driver then // skip it. if(isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); Connection con = aDriver.driver.connect(url, info); if (con != null) { // Success! println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } // if we got here nobody could connect. if (reason != null) { println("getConnection failed: " + reason); throw reason; } println("getConnection: no suitable driver found for "+ url); throw new SQLException("No suitable driver found for "+ url, "08001"); } } } ~~~ ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802175415.png) 上面是简化的代码,可以看到需要返回的是Connection对象。在Java中通过Connection提供给各个数据库一样的操作接口,这里的Connection可以看作抽象类。可以说我们用来操作不同数据库的方法都是相同的,不过MySQL有自己的ConnectionImpl类,同样Oracle也有对应的实现类。这里Driver和Connection之间是通过DriverManager类进行桥接的,不是像我们上面说的那样用组合关系来进行桥接。 #### (5)桥接模式的优缺点 - **优点:** 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。 - **缺点:**桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。 ### 3.5 组合模式 #### (1)概念 组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。 这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。 #### (2)适用场景 部分、整体场景,如树形菜单,文件、文件夹的管理。 1、表示对象的部分-整体层次结构(树形结构)。 2、希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。 #### (3)代码示例 创建 *Employee* 类,该类带有 *Employee* 对象的列表。 ``` package com.alibaba.design.compositepattern; import java.util.ArrayList; import java.util.List; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-17:59 */ public class Employee { private String name; private String dept; private int salary; private List subordinates; //构造函数 public Employee(String name,String dept, int sal) { this.name = name; this.dept = dept; this.salary = sal; subordinates = new ArrayList(); } public void add(Employee e) { subordinates.add(e); } public void remove(Employee e) { subordinates.remove(e); } public List getSubordinates(){ return subordinates; } @Override public String toString(){ return ("Employee :[ Name : "+ name +", dept : "+ dept + ", salary :" + salary+" ]"); } } ``` 使用 *Employee* 类来创建和打印员工的层次结构。 ``` package com.alibaba.design.compositepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-18:00 */ public class CompositePatternDemo { public static void main(String[] args) { Employee CEO = new Employee("John","CEO", 30000); Employee headSales = new Employee("Robert","Head Sales", 20000); Employee headMarketing = new Employee("Michel","Head Marketing", 20000); Employee clerk1 = new Employee("Laura","Marketing", 10000); Employee clerk2 = new Employee("Bob","Marketing", 10000); Employee salesExecutive1 = new Employee("Richard","Sales", 10000); Employee salesExecutive2 = new Employee("Rob","Sales", 10000); CEO.add(headSales); CEO.add(headMarketing); headSales.add(salesExecutive1); headSales.add(salesExecutive2); headMarketing.add(clerk1); headMarketing.add(clerk2); //打印该组织的所有员工 System.out.println(CEO); for (Employee headEmployee : CEO.getSubordinates()) { System.out.println(headEmployee); for (Employee employee : headEmployee.getSubordinates()) { System.out.println(employe); } } } } ``` ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802180222.png) #### (4)该模式在源码中的体现 JDK源码中的应用: Container类 ![img](https://img-blog.csdnimg.cn/20190628091705393.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3OTA5NTA4,size_16,color_FFFFFF,t_70) 通过看它的行为方法add()可以看出,它添加的是它的父类,符合组合模式的设计 ![img](https://img-blog.csdnimg.cn/20190629102410139.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3OTA5NTA4,size_16,color_FFFFFF,t_70) HashMap类 ![img](https://img-blog.csdnimg.cn/20190628091846697.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3OTA5NTA4,size_16,color_FFFFFF,t_70) add()方法中放入的是map,这也是组合模式的体现 ![img](https://img-blog.csdnimg.cn/20190628091926528.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3OTA5NTA4,size_16,color_FFFFFF,t_70) ArrayList类 ![img](https://img-blog.csdnimg.cn/20190628092007235.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3OTA5NTA4,size_16,color_FFFFFF,t_70) ![img](https://img-blog.csdnimg.cn/2019062809203286.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3OTA5NTA4,size_16,color_FFFFFF,t_70) Mybatis中的应用: MixedSqlNode类 ![img](https://img-blog.csdnimg.cn/20190628092227771.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3OTA5NTA4,size_16,color_FFFFFF,t_70) ![img](https://img-blog.csdnimg.cn/20190628092127822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3OTA5NTA4,size_16,color_FFFFFF,t_70) #### (5)组合模式的优缺点 - **优点:** 1、高层模块调用简单。 2、节点自由增加。 - **缺点:**在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。 ### 3.6 外观模式 #### (1)概念 外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。 这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。 #### (2)适用场景 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。 **注意事项**:在层次化结构中,可以使用外观模式定义系统中每一层的入口。 #### (3)代码示例 我们将创建一个 *Shape* 接口和实现了 *Shape* 接口的实体类。下一步是定义一个外观类 *ShapeMaker*。 *ShapeMaker* 类使用实体类来代表用户对这些类的调用。*FacadePatternDemo*,我们的演示类使用 *ShapeMaker* 类来显示结果。 ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802182349.png) 创建一个接口。 - Shape ``` package com.alibaba.design.facadepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-18:18 */ public interface Shape { void draw(); } ``` 创建实现接口的实体类。 - Rectangle ``` package com.alibaba.design.facadepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-18:18 */ public class Rectangle implements Shape { @Override public void draw() { System.out.println("Rectangle::draw()"); } } ``` - Circle ``` package com.alibaba.design.facadepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-18:20 */ public class Circle implements Shape { @Override public void draw() { System.out.println("Circle::draw()"); } } ``` - Square ``` package com.alibaba.design.facadepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-18:19 */ public class Square implements Shape { @Override public void draw() { System.out.println("Square::draw()"); } } ``` 创建一个外观类。 - ShapeMaker ``` package com.alibaba.design.facadepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-18:20 */ public class ShapeMaker { private Shape circle; private Shape rectangle; private Shape square; public ShapeMaker() { circle = new Circle(); rectangle = new Rectangle(); square = new Square(); } public void drawCircle(){ circle.draw(); } public void drawRectangle(){ rectangle.draw(); } public void drawSquare(){ square.draw(); } } ``` 客户端测试类 ``` package com.alibaba.design.facadepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-18:20 */ public class FacadePatternDemo { public static void main(String[] args) { ShapeMaker shapeMaker = new ShapeMaker(); shapeMaker.drawCircle(); shapeMaker.drawRectangle(); shapeMaker.drawSquare(); } } ``` 输出结果: ![](https://gitee.com/zyxscuec/image/raw/master//img/20200802182409.png) #### (4)该模式在源码中的体现 JDK类库中的外观模式 > java.lang.Class > > javax.faces.webapp.FacesServlet Class中的forName(String name, boolean initialize ,ClassLoader loader)方法调用了ClassLoader、System接口。 ~~~Java @CallerSensitive public static Class forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException { Class caller = null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { // Reflective call to get caller class is only needed if a security manager // is present. Avoid the overhead of making this call otherwise. caller = Reflection.getCallerClass(); if (sun.misc.VM.isSystemDomainLoader(loader)) { ClassLoader ccl = ClassLoader.getClassLoader(caller); if (!sun.misc.VM.isSystemDomainLoader(ccl)) { sm.checkPermission( SecurityConstants.GET_CLASSLOADER_PERMISSION); } } } return forName0(name, initialize, loader, caller); } ~~~ FacesServlet实现了Servlet接口,在实现方法service()中调用了HttpServletRequest、HttpServletResponse、ApplicationContext接口的方法。 #### (5)外观模式的优缺点 - **优点:** 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。 - **缺点:**不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。 ### 3.7 享元模式 #### (1)概念 享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。 享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。 **主要解决:**在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。 #### (2)适用场景 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。 **注意事项**: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。 #### (3)代码示例 创建一个接口。 ``` package com.alibaba.design.flyweightpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-18:48 */ public interface Shape { void draw(); } ``` 创建实现接口的实体类。 ``` package com.alibaba.design.flyweightpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-18:49 */ public class Circle implements Shape { private String color; private int x; private int y; private int radius; public Circle(String color){ this.color = color; } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public void setRadius(int radius) { this.radius = radius; } @Override public void draw() { System.out.println("Circle: Draw() [Color : " + color +", x : " + x +", y :" + y +", radius :" + radius); } } ``` 创建一个工厂,生成基于给定信息的实体类的对象。 ``` package com.alibaba.design.flyweightpattern; import java.util.HashMap; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-18:49 */ public class ShapeFactory { private static final HashMap circleMap = new HashMap<>(); public static Shape getCircle(String color) { Circle circle = (Circle)circleMap.get(color); if(circle == null) { circle = new Circle(color); circleMap.put(color, circle); System.out.println("Creating circle of color : " + color); } return circle; } } ``` 使用该工厂,通过传递颜色信息来获取实体类的对象。 ``` package com.alibaba.design.flyweightpattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/2-18:50 */ public class FlyweightPatternDemo { private static final String colors[] = { "Red", "Green", "Blue", "White", "Black" }; public static void main(String[] args) { for(int i=0; i < 20; ++i) { Circle circle = (Circle)ShapeFactory.getCircle(getRandomColor()); circle.setX(getRandomX()); circle.setY(getRandomY()); circle.setRadius(100); circle.draw(); } } private static String getRandomColor() { return colors[(int)(Math.random()*colors.length)]; } private static int getRandomX() { return (int)(Math.random()*100 ); } private static int getRandomY() { return (int)(Math.random()*100); } } ``` 输出结果: ~~~Java Creating circle of color : Black Circle: Draw() [Color : Black, x : 36, y :71, radius :100 Creating circle of color : Green Circle: Draw() [Color : Green, x : 27, y :27, radius :100 Creating circle of color : White Circle: Draw() [Color : White, x : 64, y :10, radius :100 Creating circle of color : Red Circle: Draw() [Color : Red, x : 15, y :44, radius :100 Circle: Draw() [Color : Green, x : 19, y :10, radius :100 Circle: Draw() [Color : Green, x : 94, y :32, radius :100 Circle: Draw() [Color : White, x : 69, y :98, radius :100 Creating circle of color : Blue Circle: Draw() [Color : Blue, x : 13, y :4, radius :100 Circle: Draw() [Color : Green, x : 21, y :21, radius :100 Circle: Draw() [Color : Blue, x : 55, y :86, radius :100 Circle: Draw() [Color : White, x : 90, y :70, radius :100 Circle: Draw() [Color : Green, x : 78, y :3, radius :100 Circle: Draw() [Color : Green, x : 64, y :89, radius :100 Circle: Draw() [Color : Blue, x : 3, y :91, radius :100 Circle: Draw() [Color : Blue, x : 62, y :82, radius :100 Circle: Draw() [Color : Green, x : 97, y :61, radius :100 Circle: Draw() [Color : Green, x : 86, y :12, radius :100 Circle: Draw() [Color : Green, x : 38, y :93, radius :100 Circle: Draw() [Color : Red, x : 76, y :82, radius :100 Circle: Draw() [Color : Blue, x : 95, y :82, radius :100 ~~~ #### (4)该模式在源码中的体现 享元模式在编辑器系统中大量使用,一个文本编辑器往往会提供很多种字体,而通常的做法就是将每一个字母做成一个享元对象。享元对象的内蕴状态就是 这个字母,而字母在文本中的位置和字体风等其他信息则是外蕴状态,比如字母a可能出现在文本的很多地方,虽然这些字母a的位置和字体风格不同,但是所有这 些地方使用的都是同个字母对象,这样一来,字母对象就可以在整个系统中共享。 **在Java语言中,String类型就使用了享元模式.String对象是不变对象,一旦创建出来就不能改变,如果需要改变一个字符串的值,就只 好创建一个新的String对象,在JVM内部, String对象都是共享的。如果一个系统中有两个String对象所包含的字符串相同的话,JVM实际上只创建一个String对象提供给两个引用,从 而实现String对象的共享,String的inern()方法给出这个字符串在共享池中的唯一实例.** #### (5)享元模式的优缺点 - **优点**:大大减少对象的创建,降低系统的内存,使效率提高。 - **缺点**:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。