(Java)设计模式:结构型

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

前言

这篇博文续接的是 UML建模、设计原则创建型设计模式行为型设计模式,有兴趣的可以看一下


3.3、结构型

这些设计模式关注类和对象的组合。将类和对象组合在一起,从而形成更大的结构


* 3.3.1、proxy 代理模式

定义:为某对象提供一种代理以控制对该对象的访问。即:客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性

适用场景:想在访问某个类时做一些操作

代理模式分为静态代理和动态代理


3.3.1.1、静态代理

定义:静态的定义代理类,编译前定义好


静态代理的角色:

  • 抽象角色:真实角色的抽象化,抽象类或接口均可
  • 真实角色:被代理者,也是真正完成业务服务功能的地方
  • 代理角色:代理真实角色,间接访问真实角色,从而限制、增强、修改真实角色的一些特性。也可以有自己的一些附属操作,即:做真实角色做不了的事情
  • 客户角色:使用代理角色对真实角色进行一些操作

静态代理逻辑草图(用租房举例)

image-20221130174537544


3.3.1.1.1、简单逻辑

先看没有代理的情况,举例:使用前面的租房来理解逻辑


1、抽象角色

View Code

package com.zixieqing.o1staticproxy;

/**
 * <p>@description  : 该类功能  抽象角色
 * </p>
 * <p>@package      : com.zixieqing.o1staticproxy</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface IHouse {

    void rent();
}


2、真实角色:房东

View Code

package com.zixieqing.o1staticproxy.impl;

import com.zixieqing.o1staticproxy.IHouse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 该类功能  真实角色:房东
 * </p>
 * <p>@package      : com.zixieqing.o1staticproxy.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Landlord implements IHouse {

    private Logger logger = LoggerFactory.getLogger(Landlord.class);

    @Override
    public void rent() {
        logger.info("{}有房子出售",this.getClass().getSimpleName());
    }
}


3、测试

View Code

package com.zixieqing;

import com.zixieqing.o1staticproxy.impl.Landlord;
import com.zixieqing.o1staticproxy.impl.ProxyLandlord;

/**
 * <p>@description  : 该类功能  测试
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ApiTest {
    public static void main(String[] args) {

        // 无代理的情况
        Landlord landlord = new Landlord();
        landlord.rent();
    }
}


结果现在领导来一个需求:说要统计执行rent()方法的耗时时间,从而弄到监控系统中去

要实现这个事情不可能说去修改源代码,改变房东类中的rent()方法的逻辑吧,就算这个很简单,那要是后面再加需求说什么在方法执行前做什么操作.........,那再改源代码不得裂开了

所以:使用静态代理模式即可,实现如下:

1、抽象角色

View Code

package com.zixieqing.o1staticproxy;

/**
 * <p>@description  : 该类功能  抽象角色
 * </p>
 * <p>@package      : com.zixieqing.o1staticproxy</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface IHouse {

    void rent();
}


  • 真实角色:房东
View Code

package com.zixieqing.o1staticproxy.impl;

import com.zixieqing.o1staticproxy.IHouse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 该类功能  真实角色:房东
 * </p>
 * <p>@package      : com.zixieqing.o1staticproxy.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Landlord implements IHouse {

    private Logger logger = LoggerFactory.getLogger(Landlord.class);

    @Override
    public void rent() {
        logger.info("{}有房子出售",this.getClass().getSimpleName());
    }
}


  • 代理角色:中介
View Code

package com.zixieqing.o1staticproxy.impl;

import com.zixieqing.o1staticproxy.IHouse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 该类功能  代理角色:
 *                      代理真实角色,间接访问真实角色,从而限制、增强、修改真是角色的一些特性
 * </p>
 * <p>@package      : com.zixieqing.o1staticproxy.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ProxyLandlord implements IHouse {

    private Logger logger = LoggerFactory.getLogger(ProxyLandlord.class);

    /**
     * 目标:真实角色
     */
    private IHouse target;

    public ProxyLandlord(IHouse target) {
        this.target = target;
    }

    /**
     * 需求:在执行真实角色方法执行,统计耗时时长,从而提交到监控系统中让其使用
     */
    @Override
    public void rent() {
        long startTime = System.currentTimeMillis();

        // 执行真实角色中的方法
        target.rent();

        long endTime = System.currentTimeMillis();

        logger.info("耗时:{} 毫秒", (endTime - startTime));

        foo();
    }

    // 以下即为代理角色特有的一些操作:看房子、签合同、收费、交钱给房东........


    private void foo() {
        logger.info("中介者还可以吃喝嫖赌............");
    }
}


2、测试

View Code

package com.zixieqing;

import com.zixieqing.o1staticproxy.impl.Landlord;
import com.zixieqing.o1staticproxy.impl.ProxyLandlord;

/**
 * <p>@description  : 该类功能  测试
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ApiTest {
    public static void main(String[] args) {

        // 静态代理测试

        Landlord landlord = new Landlord();
        ProxyLandlord proxyLandlord = new ProxyLandlord(landlord);

        // 使用代理角色访问真实角色
        proxyLandlord.rent();
    }
}


  • 结果
View Code

09:51:06.346 [main] INFO  c.z.o1staticproxy.impl.Landlord - Landlord有房子出售
09:51:06.347 [main] INFO  c.z.o1staticproxy.impl.ProxyLandlord - 耗时:0 毫秒
09:51:06.347 [main] INFO  c.z.o1staticproxy.impl.ProxyLandlord - 中介者还可以吃喝嫖赌............



3.3.1.1.2、分析静态代理

首先:和行为型中的责任链模式对比一下

image-20221201102945582

image-20221201103532796

从上面也就可以看出:静态代理模式其实就是责任链模式的一种变体


静态代理的优点:

  • 首先第一点就是一眼可看出的:在不修改真实对象源代码的前提下,通过代理角色就对真实角色进行了限制、增强、修改等操作
  • 其次就是因为真实角色+代理角色都是实现自同一个接口,换言之就是真实角色+代理角色的公共部分均在实现的接口中,而此公共部分被抽离出来之后方便了管理,需要相应部分时就直接在接口中添加,那真实角色+代理角色就能进行更新(当然:这是优点,也是缺点,因需要动真实角色的源代码,违背开闭原则)
  • 最后就是业务分工明确。真实角色做自己该做的事情,代理角色根据后续需要对其做限制、增强、修改(即:真实角色做不了的事情代理对象可以帮它做)

静态代理的缺点:

  • 每有一个真实角色,一般就会有一个代理角色,因此:会造成代码量的扩大,甚至真实角色中的方法很多的时候,那构建代理角色的代码量也会很大

3.3.1.2、动态代理

定义:动态的生成代理

动态代理一般有两种:JDK动态代理和CGLIB动态代理


3.3.1.2.1、JDK动态代理

这种方式是基于java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler这两个反射东西来搞的


3.3.1.2.1.1、认识Proxy

image-20221201164453269

认识这个类中所谓的静态方法

View Code

/**
 * 为指定的接口创建代理类,返回代理类的Class对象
 * @param loader	代理类的类加载器
 * param interfaces	指定需要实现的接口列表,创建的代理默认会按顺序实现interfaces指定的接口
 */
public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces){}


/**
 * 创建代理类的实例对象(先为指定的接口创建代理类,然后会生成代理类的一个实例)
 * @param loader	代理类的类加载器
 * @param interfaces	指定需要实现的接口列表,创建的代理默认会按顺序实现interfaces指定的接口
 * @param invocationHandler 是个接口,会返回一个代理对象
 */
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler invocationHandler){}
/**
 * InvocationHandler接口:返回一个代理对象,当调用代理对象的任何方法的时候,会就被InvocationHandler 接口的 invoke 方法处理
 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;



/**
 * 判断指定的类是否是一个代理类
 */
public static boolean isProxyClass(Class<?> cl){}


/**
 * 获取代理对象的InvocationHandler调用处理程序
 */
public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException



3.3.1.2.1.2、认识InvocationHandler

image-20221202170309605

上面第一点说实现InvocationHandler接口,那就去瞄一眼

image-20221202171334907

  • 上图中的invoke()中的方法是Method,翻译问题,具体的信息直接看jdk_1.8_api文档

3.3.1.2.1.3、方式一:jdk动态代理

步骤
	1、调用 Proxy.getProxyClass 方法获取代理类的Class对象
	2、使用 InvocationHandler接口 创建代理类的处理器
	3、通过 代理类和InvocationHandler 创建代理对象
	4、上面已经创建好代理对象了,接着我们就可以使用代理对象了


View Code

@Test
public void o1jdkProxy() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

  // 1、获取接口的代理类Class对象
  Class<IHouse> proxyClass = (Class<IHouse>) Proxy.getProxyClass(IHouse.class.getClassLoader(), IHouse.class);

  // 2、创建代理类的处理器
  InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             * InvocationHandler接口下的invoke方法,处理代理实例上的方法调用
             * @param proxy 调用该方法的代理实例
             * @param method 调用代理实例上的接口方法的实例
             * @param args 代理实例的参数值的对象
             * @return
             * @throws Throwable
             */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      logger.info("这里是InvocationHandler,被调用的方法是:{}",method.getName());

      return null;
    }
  };

  // 3、创建代理实例
  IHouse iHouse = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);

  // 4、调用代理实例上的方法
  iHouse.rent();
}


  • 结果

com.zixieqing.ApiTest - 这里是InvocationHandler,被调用的方法是:rent


3.3.1.2.1.4、方式二:jdk动态代理
步骤:
	1、使用 InvocationHandler接口 创建代理类的处理器
	2、使用 Proxy类的静态方法newProxyInstance 直接创建代理对象
	3、使用代理对象


View Code

@Test
public void simpleWayProxy() {
  // 1、使用invocationHandler接口创建代理类的处理器
  InvocationHandler invocationHandler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      logger.info("这是InvocationHandler,被调用的方法是:{}", method.getName());
      return null;
    }
  };

  // 2、直接调用Proxy的静态方法newProxyInstance()创建代理对象
  IHouse ihouse = (IHouse) Proxy.newProxyInstance(IHouse.class.getClassLoader(), new Class[]{IHouse.class}, invocationHandler);

  // 3、使用代理对象操作真实对象
  ihouse.rent();
}


  • 结果

com.zixieqing.ApiTest - 这是InvocationHandler,被调用的方法是:rent


3.3.1.2.1.5、JDK代理示例
View Code

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * <p>@description  : 该类功能  jdk动态代理示例:接口耗时统计
 * </p>
 * <p>@package      : com.zixieqing.o1staticproxy</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

// 1、实现InvocationHandler接口   每个代理实例都有一个关联的调用处理程序对象,它实现了接口InvocationHandler
public class CostTimeInvocationHandler implements InvocationHandler {

    private Logger logger = LoggerFactory.getLogger(CostTimeInvocationHandler.class);

    /**
     * 2、聚合真实对象
     */
    private Object target;

    public CostTimeInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        long startTime = System.nanoTime();

        Object result = method.invoke(this.target, args);

        logger.info("{} 的 {}方法 耗时 {} 纳秒", this.target.getClass(), method.getName(), (System.nanoTime() - startTime));

        return result;
    }

    /**
     * 创建代理对象
     * @param target 真实角色
     * @param targetInterface 这个真实角色实现的接口
     * @param <T>
     * @return
     */
    public static <T> T createProxy(Object target, Class<T> targetInterface) {
        if (!targetInterface.isInterface())
            throw new IllegalStateException("targetInterface必须是一个接口类型");

        if (!targetInterface.isAssignableFrom(target.getClass()))
            throw new IllegalStateException("target必须是targetInterface的实现类");

        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                                          target.getClass().getInterfaces(),
                                          new CostTimeInvocationHandler(target));
    }
}


测试

View Code

@Test
public void jdkProxyDemo() {
  // 获取代理对象
  IHouse house = CostTimeInvocationHandler.createProxy(new Landlord(), IHouse.class);

  // 使用代理对象调用真实对象中的方法,自然会进入InvocationHandler接口的invoke()中
  house.rent();
}


  • 结果

INFO  c.z.o1staticproxy.impl.Landlord - Landlord有房子出售
INFO  c.z.o.CostTimeInvocationHandler - class com.zixieqing.o1staticproxy.impl.Landlord 的 rent方法 耗时 4229600 纳秒


从上面这一系列的操作之后可以了解到:JDK动态代理最大的不足就是只能对接口生成代理类,要是想为具体某个类生成代理的话,那JDK动态代理就做不到

通过Proxy创建代理对象,当调用代理对象任意方法时候,会被InvocationHandler接口中的invoke()进行处理,这个接口内容是关键


3.3.1.2.2、cglib动态代理

jdk动态代理只能为接口创建代理,使用上有局限性。实际的场景中我们的类不一定有接口,此时如果我
们想为普通的类也实现代理功能,我们就需要用到cglib来实现

cglib是一个强大、高性能的字节码生成库,它用于在运行时扩展Java类和实现接口;本质上它是通过动
态的生成一个子类去覆盖所要代理的类(非final修饰的类和方法)

Enhancer可能是CGLIB中最常用的一个类,和jdk中的Proxy不同的是:Enhancer既能够代理普通的class,也能够代理接口

Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString()hashCode()

Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对final类进行代理操作

CGLIB官网:https://github.com/cglib/cglib


3.3.1.2.2.1、CGLIB组成结构

image-20221205141550571

CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy和BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能

spring已将第三方cglib jar包中所有的类集成到spring自己的jar包中


3.3.1.2.2.2、MethodInterceptor 拦截所有方法

1、先决条件:假如有如下的逻辑的代码

View Code

package com.zixieqing.o2dynamicproxy;

/**
 * <p>@description  : 该类功能  假设有一个业务,这里面有两个方法m1、m2
 * </p>
 * <p>@package      : com.zixieqing.o2dynamicproxy</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface IService {

    void m1();

    void m2();
}


View Code
  package com.zixieqing.o2dynamicproxy.impl;

  import com.zixieqing.o2dynamicproxy.IService;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  /**
   * <p>@description  : 该类功能  业务A
   * </p>
   * <p>@package      : com.zixieqing.o2dynamicproxy.impl</p>
   * <p>@author       : ZiXieqing</p>
   * <p>@version      : V1.0.0</p>
   */

  public class ServiceA implements IService {

      private Logger logger = LoggerFactory.getLogger(ServiceA.class);

      @Override
      public void m1() {
          logger.info("这是{}类的 m1 方法",this.getClass().getSimpleName());
      }

      @Override
      public void m2() {
          logger.info("这是{}类的 m2 方法",this.getClass().getSimpleName());
      }
  }


View Code
  package com.zixieqing.o2dynamicproxy.impl;

  import com.zixieqing.o2dynamicproxy.IService;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  /**
   * <p>@description  : 该类功能  业务B
   * </p>
   * <p>@package      : com.zixieqing.o2dynamicproxy.impl</p>
   * <p>@author       : ZiXieqing</p>
   * <p>@version      : V1.0.0</p>
   */

  public class ServiceB implements IService {

      private Logger logger = LoggerFactory.getLogger(ServiceB.class);

      @Override
      public void m1() {
          logger.info("这是{}类的 m1 方法",this.getClass().getSimpleName());
      }

      @Override
      public void m2() {
          logger.info("这是{}类的 m2 方法",this.getClass().getSimpleName());
      }
  }


2、使用enhancer + callback子类MethodInterceptor实现拦截所有方法

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.15.RELEASE</version>
        </dependency>


View Code

package com.zixieqing;

import com.zixieqing.o2dynamicproxy.impl.ServiceA;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * <p>@description  : 该类功能  动态代理之CGLIB动态代理的MethodInterceptor拦截所有方法测试
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class CglibProxyTest {

    private Logger logger = LoggerFactory.getLogger(CglibProxyTest.class);

    @Test
    public void methodInterceptorTest() {
        // 1、创建enhancer
        Enhancer enhancer = new Enhancer();

        // 2、设置给谁做代理:Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(非final修饰的类或方法)
        enhancer.setSuperclass(ServiceA.class);

        /*3、设置回调:现org.springframework.cglib.proxy.Callback接口,MethodInterceptor接口也是其子接口
        当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
        enhancer.setCallback(new MethodInterceptor() {
            /**
             * 代理对象的方法拦截器
             * @param proxy 代理对象的实例
             * @param method 真实角色的方法,即:ServiceA中的方法
             * @param objects 调用方法传递的参数
             * @param methodProxy 方法的代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

                logger.info("这是Enhancer的方法拦截器MethodInterceptor中的invoke,即将调用;{}",method.getName());
                return methodProxy.invokeSuper(proxy, objects);

            }
        });

        // 4、创建代理对象 强转
        ServiceA service = (ServiceA) enhancer.create();

        // 5、通过代理对象访问真实对象
        service.m1();

        System.out.println("================华丽的分隔符=============");

        service.m2();
    }

  • 结果

INFO  com.zixieqing.CglibProxyTest - 这是Enhancer的方法拦截器MethodInterceptor中的invoke,即将调用;m1
INFO  c.z.o2dynamicproxy.impl.ServiceA - 这是ServiceA$$EnhancerByCGLIB$$931fd35c类的 m1 方法
================华丽的分隔符=============
INFO  com.zixieqing.CglibProxyTest - 这是Enhancer的方法拦截器MethodInterceptor中的invoke,即将调用;m2
INFO  c.z.o2dynamicproxy.impl.ServiceA - 这是ServiceA$$EnhancerByCGLIB$$931fd35c类的 m2 方法

  • 从上面结果可以看出:m1和m2的方法都被拦截了,当然:也可以在ServiceA的m1中调用m2进行测试,m2还是会被拦截的。这种方式是Spring解析@configuration+@Bean的一个点(保证多个@Bean中用到的同一个实例是一样的,这里就用了cglib)

3.3.1.2.2.3、FixedValue 拦截所有方法并返回固定值

这个直接可以猜到咋个用的了,前面用的是MethodInterceptor,那换成FixedValue,然后加上要返回的内容即可


View Code

package com.zixieqing.o2dynamicproxy.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 该类功能  业务C
 * </p>
 * <p>@package      : com.zixieqing.o2dynamicproxy.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ServiceC {

    private Logger logger = LoggerFactory.getLogger(ServiceC.class);

    public String m1() {
        logger.info("这是普通类{}的 m1 方法",this.getClass().getSimpleName());

        return "tall is cheap";
    }

    public String m2() {
        logger.info("这是普通类{}的 m1 方法",this.getClass().getSimpleName());

        return "show me the code";
    }
}


View Code

@Test
public void fixedValueTest() {
  // 1、创建enhancer
  Enhancer enhancer = new Enhancer();

  // 2、设置给谁做代理:Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(非final修饰的类或方法)
  enhancer.setSuperclass(ServiceC.class);

  /*3、设置回调:现org.springframework.cglib.proxy.Callback接口,FixedValue接口也是其子接口
        当调用代理对象的任何方法的时候,都会被FixedValue接口的loadObject方法处理*/
  enhancer.setCallback(new FixedValue() {
    /**
	 * 代理对象:拦截所有方法 并 返回固定的内容
	 * @return 要返回的内容
	 * @throws Exception
	 */
    @Override
    public Object loadObject() throws Exception {
      logger.info("这是Enhancer的FixedValue拦截所有方法 并 返回给定内容");

      return "代理对象的FixedValue已经执行完毕";
    }
  });

  // 4、创建代理对象 强转
  ServiceC service = (ServiceC) enhancer.create();

  // 5、通过代理对象访问真实对象
  logger.info(service.m1());


  logger.info(service.m2());
}

3.3.1.2.2.3、NoOp.INSTANCE 不做任何处理

见名知意,就是直接放行,不做任何处理


    @Test
    public void noOP_INSTANCETest() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ServiceC.class);
        // 不做任何处理,直接放行
        enhancer.setCallback(NoOp.INSTANCE);
        ServiceC serviceC = (ServiceC) enhancer.create();

        System.out.println(serviceC.m1());
        System.out.println(serviceC.m2());
    }


  • 结果

INFO  c.z.o2dynamicproxy.impl.ServiceC - 这是普通类ServiceC$$EnhancerByCGLIB$$8fecfa56的 m1 方法
tall is cheap
INFO  c.z.o2dynamicproxy.impl.ServiceC - 这是普通类ServiceC$$EnhancerByCGLIB$$8fecfa56的 m1 方法
show me the code


3.3.1.2.2.4、CallbackFilter 不同方法用不同拦截器
View Code

package com.zixieqing.o2dynamicproxy.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 该类功能  业务D
 * </p>
 * <p>@package      : com.zixieqing.o2dynamicproxy.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ServiceD {

    private Logger logger = LoggerFactory.getLogger(ServiceD.class);

    public void insertA() {
        logger.info("{}的insertA方法插入了一条数据", this.getClass().getSimpleName());
    }

    public String getB() {
        logger.info("这是{}的方法getB", this.getClass().getSimpleName());
        return "紫邪情";
    }
}


View Code

/**
 * 需求:
 * insert开头的方法统计耗时时间
 * get开头的方法直接返回固定内容
 */
@Test
public void callbackFilterTest() {
  Enhancer enhancer = new Enhancer();
  enhancer.setSuperclass(ServiceD.class);
  // 数组写法
  Callback[] callbacks = {
    new MethodInterceptor() {
      /**
       * 对象代理:insert开头的方法应该做的逻辑:统计耗时时间
	   * @param o 代理对象
	   * @param method 真实对象中的方法
	   * @param objects 调用方法传递的参数
	   * @param methodProxy 方法代理
	   * @return
	   * @throws Throwable
	   */
      @Override
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long startTime = System.nanoTime();
        Object result = methodProxy.invokeSuper(o, objects);
        logger.info("执行时间为:{}纳秒", (System.nanoTime()) - startTime);
        return result;
      }
    },
    new FixedValue() {
      /**
	   * get开头的方法应该做的逻辑:返回固定字符串
	   * @return
	   * @throws Exception
	   */
      @Override
      public Object loadObject() throws Exception {
        return "当前库正在进行迁移,无法访问!!";
      }
    }
  };

  // 第二个变化的地方:调用setCallbacks,将上一步的数组写法Callback[] callbacks传进去
  enhancer.setCallbacks(callbacks);

  // 多一步:设置过滤逻辑
  enhancer.setCallbackFilter(new CallbackFilter() {
    /**
	 * 方法过滤
	 * @param method 真实对象中的方法
	 * @return 索引值
     */
    @Override
    public int accept(Method method) {
      // 如果方法是以insert开头,那就去找callbacks[0] 即:上面的MethodInterceptor,否则就是FixedValue
      return method.getName().startsWith("insert") ? 0 : 1;
    }
  });

  ServiceD serviceD = (ServiceD) enhancer.create();

  // insert统计耗时
  serviceD.insertA();
  // get返回固定字符串
  System.out.println(serviceD.getB());
}

  • 结果

INFO  c.z.o2dynamicproxy.impl.ServiceD - ServiceD$$EnhancerByCGLIB$$7d40391b的insertA方法插入了一条数据
INFO  com.zixieqing.CglibProxyTest - 执行时间为:10712800纳秒
当前库正在进行迁移,无法访问!!


3.3.1.2.2.5、CallbackHelper 封装

这个封装其实就是对MethodInterceptor和FixedValue进行了一些封装,所以:做的就是对前面CallbackFilter的一些简化而已


View Code

@Test
public void callbackHelperTest() {

  Enhancer enhancer = new Enhancer();
  enhancer.setSuperclass(ServiceD.class);

  // 方法是insert开头时要做的事情
  Callback costTime = (MethodInterceptor) (Object o,
                                           Method method,
                                           Object[] objects,
                                           MethodProxy methodProxy) -> {
    long startTime = System.currentTimeMillis();
    Object result = methodProxy.invokeSuper(o, objects);
    System.out.println("此任务花费了:" + (System.currentTimeMillis() - startTime) + "毫秒");
    return result;
  };

  // 以get开头的方法要做的事情
  Callback returnConstStr = (FixedValue) () -> "紫邪情";

  // CallbackHelper(Class superclass, Class[] interfaces)
  CallbackHelper callbackHelper = new CallbackHelper(ServiceD.class, null) {
    /**
	 * 判断业务该怎么走
	 * @param method 方法
	 * @return
	 */
    @Override
    protected Object getCallback(Method method) {
      return method.getName().startsWith("insert") ? costTime : returnConstStr;
    }
  };

  // 给enhancer添加Callbacks数组
  enhancer.setCallbacks(callbackHelper.getCallbacks());
  // 让CallbackHelper成为enhancer的过滤对象
  enhancer.setCallbackFilter(callbackHelper);

  /*
		测试
	*/
  ServiceD serviceD = (ServiceD) enhancer.create();
  serviceD.insertA();

  System.out.println("================华丽的分隔符=============");

  System.out.println(serviceD.getB());
}

  • 结果

INFO  c.z.o2dynamicproxy.impl.ServiceD - ServiceD$$EnhancerByCGLIB$$cf1c3128的insertA方法插入了一条数据
此任务花费了:11纳秒
================华丽的分隔符=============
紫邪情


3.3.1.2.3、jdk动态代理 VS cglib动态代理
  1. JDK动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类
  2. JDK动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效

* 3.3.2、bridge 桥接模式

这个模式一直都在用

定义:将抽象部分和实现部分分离,把多种可匹配的使用进行组合,使得抽象和实现都可以独立变化

场景理解: A类中有一个B类接口,然后创建A类时通过有参构造传入一个B类接口[实现类],这里的B类就是设计的,也就是所谓的聚合,重构继承的一种方式(组合、聚合、关联关系都可以使用此模式进行重构,但此模式缺点也在这几个关系的选择上)

适用场景:多角度多模式问题。 如:微信+密码=支付;微信+指纹=支付;微信+人脸=支付,相应的支付宝也是密码、指纹、人脸等等,这就是多角度[微信、支付宝]多模式[密码、指纹、人脸]。换一个话来说:不希望使用继承或多层次继承导致类的个数急剧增加的系统就可以使用此模式

开发场景:

  • 1、银行转账:转账方式(网上银行、柜台、ATM)、转账用户类型(普通用户、银卡用户、金卡用户)
  • 2、消息管理:消息分类(即时消息、延时消息)、消息种类(QQ消息、微信消息、邮件消息、钉钉消息)

关键点:选择的桥接拆分点(谁聚合谁的关系)。 如上面的微信和支付宝支付,这种就可以将支付方式[微信、支付宝]和支付模式[密码、指纹、人脸]进行拆分,通过抽象类依赖实现类进行桥接,而支付和模式这两个也是可以独立使用和变化的,只需要在需要时把支付模式传递给支付方式即可。如果业务中能找到这种类似的相互组合就可用此模式,否则:不一定非要用它


桥接模式的角色:

  1. 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用
  2. 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
  3. 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用
  4. 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现

示例就用前面的微信、支付宝支付

1、实现化、具体实现化角色

View Code

package com.zixieqing.mode;

/**
 * <p>@description  : 该类功能  实现化角色:支付模式
 * </p>
 * <p>@package      : com.zixieqing.mode</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface IPayMode {

    boolean check(String uId);
}


View Code
  package com.zixieqing.mode;

  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  /**
   * <p>@description  : 该类功能  具体实现化角色:密码校验
   * </p>
   * <p>@package      : com.zixieqing.mode</p>
   * <p>@author       : ZiXieqing</p>
   * <p>@version      : V1.0.0</p>
   */

  public class PwdCheckImpl implements IPayMode{

      private Logger logger = LoggerFactory.getLogger(PwdCheckImpl.class);

      @Override
      public boolean check(String uId) {
          logger.info("{} 正在进行风控校验,校验用户为:{}", this.getClass().getSimpleName(), uId);

          return true;
      }
  }


View Code
  package com.zixieqing.mode;

  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  /**
   * <p>@description  : 该类功能  具体实现化角色:指纹校验
   * </p>
   * <p>@package      : com.zixieqing.mode</p>
   * <p>@author       : ZiXieqing</p>
   * <p>@version      : V1.0.0</p>
   */

  public class FingerprintCheckImpl implements IPayMode{

      private Logger logger = LoggerFactory.getLogger(FingerprintCheckImpl.class);

      @Override
      public boolean check(String uId) {
          logger.info("{} 正在进行风控校验,校验用户为:{}", this.getClass().getSimpleName(), uId);

          return true;
      }
  }


2、抽象化、扩展抽象化角色

View Code
package com.zixieqing.channel;

import com.zixieqing.mode.IPayMode;

import java.math.BigDecimal;

/**
 * <p>@description  : 该类功能  抽象化角色:聚合一个具体化对象引用
 * </p>
 * <p>@package      : com.zixieqing.channel</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public abstract class AbstractPayWay {

    /**
     * 聚合支付模式
     */
    protected IPayMode iPayMode;

    protected AbstractPayWay(IPayMode iPayMode) {
        this.iPayMode = iPayMode;
    }

    /**
     * 支付方式
     *      如果直接采用if-else,那实现类中这里直接if判断是支付宝还是微信,里面又继续判断是密码、人脸、指纹...,之后做逻辑即可
     *      而:采用桥接模式进行桥街点拆分之后,抽象(AbstractPayWay)依赖实现(IPayMode),想要什么组合按照自己需要即可
     *      这样就让抽象和实现都可以独立使用和变化
     * @param uId 用户ID
     * @param tradeId 交易ID
     * @param amount 金额
     * @return 受理情况
     */
    public abstract String transfer(String uId, String tradeId, BigDecimal amount);
}


View Code
  package com.zixieqing.channel;

  import com.zixieqing.mode.IPayMode;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  import java.math.BigDecimal;

  /**
   * <p>@description  : 该类功能  扩展抽象化角色
   * </p>
   * <p>@package      : com.zixieqing.channel</p>
   * <p>@author       : ZiXieqing</p>
   * <p>@version      : V1.0.0</p>
   */

  public class WeChatPay extends AbstractPayWay{

      private Logger logger = LoggerFactory.getLogger(WeChatPay.class);

      public WeChatPay(IPayMode iPayMode) {
          super(iPayMode);
      }

      @Override
      public String transfer(String uId, String tradeId, BigDecimal amount) {
          logger.info("模拟微信渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
          boolean check = iPayMode.check(uId);
          logger.info("模拟微信渠道支付风控校验。uId:{} tradeId:{} security:{}", uId, tradeId, check);
          if (!check) {
              logger.info("模拟微信渠道支付划账拦截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
              return "0001";
          }
          logger.info("模拟微信渠道支付划账成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
          return "0000";
      }
  }


View Code
  package com.zixieqing.channel;

  import com.zixieqing.mode.IPayMode;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  import java.math.BigDecimal;

  /**
   * <p>@description  : 该类功能  扩展抽象化角色
   * </p>
   * <p>@package      : com.zixieqing.channel</p>
   * <p>@author       : ZiXieqing</p>
   * <p>@version      : V1.0.0</p>
   */

  public class AliPay extends AbstractPayWay{

      private Logger logger = LoggerFactory.getLogger(AliPay.class);

      public AliPay(IPayMode iPayMode) {
          super(iPayMode);
      }

      @Override
      public String transfer(String uId, String tradeId, BigDecimal amount) {
          logger.info("模拟支付宝渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
          boolean check = iPayMode.check(uId);
          logger.info("模拟支付宝渠道支付风控校验。uId:{} tradeId:{} security:{}", uId, tradeId, check);
          if (!check) {
              logger.info("模拟支付宝渠道支付划账拦截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
              return "0001";
          }
          logger.info("模拟支付宝渠道支付划账成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
          return "0000";
      }
  }


3、测试

View Code

package com.zixieqing;

import com.zixieqing.channel.AliPay;
import com.zixieqing.channel.WeChatPay;
import com.zixieqing.mode.FingerprintCheckImpl;
import com.zixieqing.mode.PwdCheckImpl;
import org.junit.Test;

import java.math.BigDecimal;
import java.util.UUID;

/**
 * <p>@description  : 该类功能  测试
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ApiTest {

    @Test
    public void MyTest() {

        // 1、微信+密码=支付
        WeChatPay weChatPay = new WeChatPay(new PwdCheckImpl());
        weChatPay.transfer("weixin-".concat(UUID.randomUUID().toString()),
                    System.nanoTime() + "",
                            new BigDecimal(1000));

        System.out.println();

        // 2、支付宝+指纹=支付
        AliPay aliPay = new AliPay(new FingerprintCheckImpl());
        aliPay.transfer("zfb-".concat(UUID.randomUUID().toString()),
                System.nanoTime() + "",
                new BigDecimal(1000));
    }
}


  • 结果
View Code

14:58:42.349 [main] INFO  com.zixieqing.channel.WeChatPay - 模拟微信渠道支付划账开始。uId:weixin-872c5f47-c529-44ca-949f-3dd772197b6e tradeId:20617275744700 amount:1000
14:58:42.351 [main] INFO  com.zixieqing.mode.PwdCheckImpl - PwdCheckImpl 正在进行风控校验,校验用户为:weixin-872c5f47-c529-44ca-949f-3dd772197b6e
14:58:42.351 [main] INFO  com.zixieqing.channel.WeChatPay - 模拟微信渠道支付风控校验。uId:weixin-872c5f47-c529-44ca-949f-3dd772197b6e tradeId:20617275744700 security:true
14:58:42.351 [main] INFO  com.zixieqing.channel.WeChatPay - 模拟微信渠道支付划账成功。uId:weixin-872c5f47-c529-44ca-949f-3dd772197b6e tradeId:20617275744700 amount:1000

14:58:42.351 [main] INFO  com.zixieqing.channel.AliPay - 模拟支付宝渠道支付划账开始。uId:zfb-65f4f190-6f63-4a1c-bde9-0c10a950d4d4 tradeId:20617281349700 amount:1000
14:58:42.352 [main] INFO  c.z.mode.FingerprintCheckImpl - FingerprintCheckImpl 正在进行风控校验,校验用户为:zfb-65f4f190-6f63-4a1c-bde9-0c10a950d4d4
14:58:42.352 [main] INFO  com.zixieqing.channel.AliPay - 模拟支付宝渠道支付风控校验。uId:zfb-65f4f190-6f63-4a1c-bde9-0c10a950d4d4 tradeId:20617281349700 security:true
14:58:42.352 [main] INFO  com.zixieqing.channel.AliPay - 模拟支付宝渠道支付划账成功。uId:zfb-65f4f190-6f63-4a1c-bde9-0c10a950d4d4 tradeId:20617281349700 amount:1000


3.3.2.1、分析桥接模式

桥接模式的优点:

  • 第一点就在定义中:将抽象和实现进行了分离
  • 其次就是抽象化和具体化是抽象类和接口,扩展能力好咯

image-20221206155410345

  • 抽象化里面聚合了实现化的引用,所以在调用时WeChatPay weChatPay = new WeChatPay(new PwdCheckImpl());就需要传入具体实现化,即:对于调用者来说细节是透明的

桥接模式的缺点:

  • 官方话:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程

3.3.3、adapter 适配器模式

定义:也可以叫转换器模式,指的是:把原本不兼容的接口,通过修改适配做到统一(兼容接口)

场景理解: 二孔插座 弄为需要的 三孔插座;电脑网线转接器;物理中学的电压转换(美国119V,中国220V,在需要二者链接时就需要转换,即:适配)...........

解决的问题: 解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的

适用场景:

  • 系统需要使用现有的类,而此类的接口不符合系统的需要
  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口
  • 通过接口转换,将一个类插入另一个类系中(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口)

适配器模式的角色:

  • 源角色(Adapee): 就是项目中已有的对象,即:想要进行转换的东西
  • 目标角色(target): 就是想要最终转换出来的东西是什么样的
  • 适配器角色(Adapter): 此模式的核心。通过操作将源角色 转成 目标角色

3.3.3.1、简单逻辑

首先这个模式我个人认为并没有固定的套路,很随心所欲,重点为:利用系统中已有的东西 转换成 适应当前环境的东西即可。 所以想要玩成这么一个需求是可以通过很多方式做到的。以下的示例是用来体会的


3.3.3.1.1、类适配 / 接口适配

定义:适配器类继承或依赖源角色(一般是多重继承),从而来实现目标角色


场景:

  • 有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。

  • 还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件

  • 想要让 AudioPlayer 播放其他格式的音频文件


按照定义来进行拆分,类图如下:

image-20221212110601673


1、高级媒体播放器

View Code

package com.zixieqing;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 该类功能  高级媒体播放器
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface AdvanceMediaPlayer {

    Logger LOGGER = LoggerFactory.getLogger(AdvanceMediaPlayer.class);

    /**
     * 播放vlc格式
     * @param fileName 文件名
     */
    void playVlc(String fileName);

    /**
     * 播放mp4格式
     * @param fileName 文件名
     */
    void playMp4(String fileName);
}


View Code
  package com.zixieqing.impl;

  import com.zixieqing.AdvanceMediaPlayer;

  /**
   * <p>@description  : 该类功能  播放vlc格式的播放器
   * </p>
   * <p>@package      : com.zixieqing.impl</p>
   * <p>@author       : ZiXieqing</p>
   * <p>@version      : V1.0.0</p>
   */

  public class VlcPlayer implements AdvanceMediaPlayer {
      /**
       * 播放vlc格式
       *
       * @param fileName 文件名
       */
      @Override
      public void playVlc(String fileName) {
          LOGGER.info("进行vlc播放的一些逻辑处理");
      }

      /**
       * 播放mp4格式
       *
       * @param fileName 文件名
       */
      @Override
      public void playMp4(String fileName) {
          // 不做事
      }
  }


View Code
  package com.zixieqing.impl;

  import com.zixieqing.AdvanceMediaPlayer;

  /**
   * <p>@description  : 该类功能  mp4格式播放器
   * </p>
   * <p>@package      : com.zixieqing.impl</p>
   * <p>@author       : ZiXieqing</p>
   * <p>@version      : V1.0.0</p>
   */

  public class Mp4Player implements AdvanceMediaPlayer {
      /**
       * 播放vlc格式
       *
       * @param fileName 文件名
       */
      @Override
      public void playVlc(String fileName) {
          // 不做事
      }

      /**
       * 播放mp4格式
       *
       * @param fileName 文件名
       */
      @Override
      public void playMp4(String fileName) {
          LOGGER.info("mp4格式播放器该做的逻辑处理");
      }
  }


2、媒体播放器

View Code
package com.zixieqing;

/**
 * <p>@description  : 该类功能  媒体播放器
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface MediaPlayer {

    /**
     * 媒体播放
     * @param audioType 音频类型
     * @param fileName 文件名
     */
    void play(String audioType, String fileName);
}


View Code
  package com.zixieqing.impl;

  import com.zixieqing.MediaPlayer;
  import com.zixieqing.MediaPlayerAdapter;
  import com.zixieqing.TypeEnum;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  /**
   * <p>@description  : 该类功能  音频播放器
   * </p>
   * <p>@package      : com.zixieqing.impl</p>
   * <p>@author       : ZiXieqing</p>
   * <p>@version      : V1.0.0</p>
   */

  public class AudioPlayer implements MediaPlayer {

      private Logger logger = LoggerFactory.getLogger(AudioPlayer.class);

      private MediaPlayerAdapter mediaPlayerAdapter;

      /**
       * 媒体播放
       * @param audioType 音频类型
       * @param fileName 文件名
       */
      @Override
      public void play(String audioType, String fileName) {
          // 支持原有的格式 mp3
          if ("mp3".equalsIgnoreCase(audioType)) {
              logger.info("原有格式mp3播放的一系列逻辑");

          // 让其支持其他格式的音频播放
          } else if (TypeEnum.VLC.toString().equalsIgnoreCase(audioType)
                  || TypeEnum.MP4.toString().equalsIgnoreCase(audioType)) {
              // 找适配器转
              mediaPlayerAdapter = new MediaPlayerAdapter(audioType);
              mediaPlayerAdapter.play(audioType, fileName);
          } else {
              throw new IllegalStateException("音频格式不对,请切换符合的格式");
          }
      }
  }


  • 涉及到的枚举类
View Code
package com.zixieqing;

/**
 * <p>@description  : 该类功能  音频格式类型枚举
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public enum TypeEnum {

    VLC,
    MP4,
    MP3,
}


3、适配器类

View Code

package com.zixieqing;

import com.zixieqing.impl.Mp4Player;
import com.zixieqing.impl.VlcPlayer;

/**
 * <p>@description  : 该类功能  适配器:
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class MediaPlayerAdapter implements MediaPlayer{

    private AdvanceMediaPlayer advanceMediaPlayer;

    public MediaPlayerAdapter(String audioType) {
        if (TypeEnum.VLC.toString().equalsIgnoreCase(audioType))
            advanceMediaPlayer = new VlcPlayer();

        if (TypeEnum.MP4.toString().equalsIgnoreCase(audioType))
            advanceMediaPlayer = new Mp4Player();
    }

    /**
     * 媒体播放
     *
     * @param audioType 音频类型
     * @param fileName 文件名
     */
    @Override
    public void play(String audioType, String fileName) {
        if (TypeEnum.VLC.toString().equalsIgnoreCase(audioType))
            advanceMediaPlayer.playVlc(fileName);

        if (TypeEnum.MP4.toString().equalsIgnoreCase(audioType))
            advanceMediaPlayer.playMp4(fileName);

    }
}


4、测试

View Code

package com.zixieqing;

import com.zixieqing.o1classadapter.impl.AudioPlayer;
import org.junit.Test;

/**
 * <p>@description  : 该类功能  测试
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ApiTest {

    @Test
    public void test() {

        AudioPlayer audioPlayer = new AudioPlayer();
        audioPlayer.play("mp3","大悲咒");
        audioPlayer.play("mp4","画江湖");

        audioPlayer.play("flac","葫芦娃");
    }
}


View Code

15:21:13.769 [main] INFO  c.z.o1classadapter.impl.AudioPlayer - 原有格式mp3播放的一系列逻辑
15:21:13.772 [main] INFO  c.z.o.AdvanceMediaPlayer - mp4格式播放器该做的逻辑处理

java.lang.IllegalStateException: 音频格式不对,请切换符合的格式

	at com.zixieqing.o1classadapter.impl.AudioPlayer.play(AudioPlayer.java:42)
	at com.zixieqing.ApiTest.test(ApiTest.java:23)

	.....................


上面这种方式是不友好的,是用if来进行条件判断的,换言之:要是还有另外的音频格式,那就是继续判断+实现类


3.3.3.1.2、对象适配

场景:消息的转换。假如:系统中已有了两种消息体格式(内部订单和第三方订单),现在将像内部订单、第三方订单等等这些各种消息转成一个通用消息体


假如系统已有的两种消息体格式是如下的样子,一般是从其他地方来的,中台做整合

View Code

package com.zixieqing.o2instanceadapter.msg;

import java.util.Date;

/**
 * <p>@description  : 该类功能  源角色:系统已有对象  内部订单消息体
 * </p>
 * <p>@package      : com.zixieqing.o2instanceadapter</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class InternalOrder {

    /**
     * 用户ID
     */
    private String uid;
    /**
     * 商品
     */
    private String sku;
    /**
     * 订单ID
     */
    private String orderId;
    /**
     * 下单时间
     */
    private Date createOrderTime;

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getSku() {
        return sku;
    }

    public void setSku(String sku) {
        this.sku = sku;
    }

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public Date getCreateOrderTime() {
        return createOrderTime;
    }

    public void setCreateOrderTime(Date createOrderTime) {
        this.createOrderTime = createOrderTime;
    }

    @Override
    public String toString() {
        return "InternalOrder{" +
                "uid='" + uid + '\'' +
                ", sku='" + sku + '\'' +
                ", orderId='" + orderId + '\'' +
                ", createOrderTime=" + createOrderTime +
                '}';
    }
}


View Code

package com.zixieqing.o2instanceadapter.msg;

import java.math.BigDecimal;
import java.util.Date;

/**
 * <p>@description  : 该类功能  源角色:系统已有对象  第三方订单消息体
 * </p>
 * <p>@package      : com.zixieqing.o2instanceadapter</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class POPOrder {

    /**
     * 用户ID
     */
    private String uId;
    /**
     * 订单号
     */
    private String orderId;
    /**
     * 下单时间
     */
    private Date orderTime;
    /**
     * 商品
     */
    private Date sku;
    /**
     * 商品名称
     */
    private Date skuName;
    /**
     * 金额
     */
    private BigDecimal decimal;

    public String getuId() {
        return uId;
    }

    public void setuId(String uId) {
        this.uId = uId;
    }

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public Date getOrderTime() {
        return orderTime;
    }

    public void setOrderTime(Date orderTime) {
        this.orderTime = orderTime;
    }

    public Date getSku() {
        return sku;
    }

    public void setSku(Date sku) {
        this.sku = sku;
    }

    public Date getSkuName() {
        return skuName;
    }

    public void setSkuName(Date skuName) {
        this.skuName = skuName;
    }

    public BigDecimal getDecimal() {
        return decimal;
    }

    public void setDecimal(BigDecimal decimal) {
        this.decimal = decimal;
    }

    @Override
    public String toString() {
        return "POPOrder{" +
                "uId='" + uId + '\'' +
                ", orderId='" + orderId + '\'' +
                ", orderTime=" + orderTime +
                ", sku=" + sku +
                ", skuName=" + skuName +
                ", decimal=" + decimal +
                '}';
    }
}


现在需要转换成的统一消息体如下(注意下面setter方法做了一个小动作,关系到后面进行适配的事):

View Code

package com.zixieqing.o2instanceadapter;

import java.util.Date;

/**
 * <p>@description  : 该类功能  目标角色:统一消息体
 * </p>
 * <p>@package      : com.zixieqing.o2instanceadapter</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Message {

    /**
     * 用户ID
     */
    private String userId;
    /**
     * 业务ID
     */
    private String bizId;
    /**
     * 业务时间
     */
    private Date bizTime;
    /**
     * 业务描述
     */
    private String desc;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getBizId() {
        return bizId;
    }

    public void setBizId(String bizId) {
        this.bizId = bizId;
    }

    public Date getBizTime() {
        return bizTime;
    }

    public void setBizTime(Date bizTime) {
        this.bizTime = bizTime;
    }

  /**
   * 注意这里做了一个小动作
   */
    public void setBizTime(String bizTime) {
        this.bizTime = new Date(Long.parseLong(bizTime));
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}


适配器编写如下:

View Code

package com.zixieqing.o2instanceadapter;

import com.alibaba.fastjson.JSON;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;

/**
 * <p>@description  : 该类功能  消息适配器
 * </p>
 * <p>@package      : com.zixieqing.o2instanceadapter</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class MessageAdapter {

    /**
     * 消息适配
     * @param jsonStr 要适配的对象字符串 如:InternalOrder
     * @param filedMap 字段映射关系   如:userId ——> uId
     * @return 转换成的统一消息体
     */
    public static Message msgAdapter(String jsonStr, Map<String, String> filedMap) throws NoSuchMethodException,
            IllegalAccessException, InvocationTargetException {

        Map instance = JSON.parseObject(jsonStr, Map.class);

        Message message = new Message();

        for (String key : filedMap.keySet()) {
            Object val = instance.get(filedMap.get(key));
            // 给消息通用体Message赋值
            Message.class.getMethod("set" + key.substring(0, 1).toUpperCase() + key.substring(1), String.class)
                    .invoke(message, val.toString());
        }
        return message;
    }
}


测试

View Code

package com.zixieqing;

import com.alibaba.fastjson.JSON;
import com.zixieqing.o2instanceadapter.msg.InternalOrder;
import com.zixieqing.o2instanceadapter.Message;
import com.zixieqing.o2instanceadapter.MessageAdapter;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;

/**
 * <p>@description  : 该类功能  测试
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void instanceAdapterTest() throws ParseException, NoSuchMethodException,
            IllegalAccessException, InvocationTargetException {

        Date data = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2022-12-13 10:39:40");

        InternalOrder internalOrder = new InternalOrder();
        internalOrder.setOrderId(System.nanoTime() + "");
        internalOrder.setUid(System.currentTimeMillis() + "");
        internalOrder.setCreateOrderTime(data);
        internalOrder.setSku("10888787897898798");

        HashMap<String, String> filedMap = new HashMap<>();
        filedMap.put("userId", "uid");
        filedMap.put("bizId", "orderId");
        filedMap.put("bizTime", "createOrderTime");

        // 进行转换
        Message msgAdapter = MessageAdapter.msgAdapter(JSON.toJSONString(internalOrder), filedMap);

        logger.info("适配前:{}",JSON.toJSONString(internalOrder));
        logger.info("适配后:{}", JSON.toJSONString(msgAdapter));

    }
}



11:26:45.453 [main] INFO  com.zixieqing.ApiTest - 适配前:{"createOrderTime":1670899180000,"orderId":"4803316410300","sku":"10888787897898798","uid":"1670902005394"}
11:26:45.457 [main] INFO  com.zixieqing.ApiTest - 适配后:{"bizId":"4803316410300","bizTime":1670899180000,"userId":"1670902005394"}


* 3.3.4、decorator 装饰器模式

定义:在不改变原有类的前提下,新增功能(特征+行为)。换言之就是开闭原则的体现

重点:不改变原有类(结构+功能/方法)

联想到的东西: AOP切面编程,即代理模式;继承。但继承会增加子类,AOP会增加复杂性,而装饰器模式会更灵活。所以装饰器模式其实就是继承的一种替代方案

场景理解: 最常见的就是这样的代码:new BufferedReader(new FileReader("")); ,俗称套娃,这就是装饰器模式的一种体现

适用场景:在继承不适合的情况下。如孙猴子72变,可以变化N种东西(就有了猴子+变的那东西的特征和行为),不可能这N个东西都搞继承,用子类实现吧?

  • 扩展一个类的功能
  • 动态增加功能,动态撤销

装饰器模式的角色:

  • 抽象组件(Component): 规定被装饰的对象的行为。可以是接口或抽象类
  • 具体组件(ConcreteComponent): 即 要被装饰的对象(你用抽象装饰器+具体装饰器装饰的就是它)。是抽象组件的子类
  • 抽象装饰器(Decorator): 对具体组件进行装饰。其内部必然有一个Component抽象组件的引用(可是属性+构造传入;可以是方法参数传入);其一般是一个抽象类
  • 具体装饰器(ConcreteDecorator): 抽象装饰器的子类。理论上,每个具体装饰器都扩展了Component对象的一种功能

装饰器模式的大概草图

image-20230105171053109


3.3.4.1、简单逻辑

1、抽象组件

View Code

package com.zixieqing;

/**
 * <p>@description  : 该类功能  抽象组件:电话
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 */

public interface AbstractPhone {

    /**
     * 基本功能:通话
     * @return 通话内容
     */
    String call();

    /**
     * 基本功能:发短信
     * @return 短信内容
     */
    String sendMessage();
}


  • 具体组件
View Code

package com.zixieqing.impl;

import com.zixieqing.AbstractPhone;

/**
 * <p>@description  : 该类功能  具体组件:实现抽象组件的行为
 * </p>
 * <p>@package      : com.zixieqing.impl</p>
 * <p>@author       : ZiXieqing</p>
 */

public class PhoneImpl implements AbstractPhone {
    /**
     * 基本功能:通话
     *
     * @return 通话内容
     */
    @Override
    public String call() {
        return "电话基本功能:进行通话";
    }

    /**
     * 基本功能:发短信
     *
     * @return 短信内容
     */
    @Override
    public String sendMessage() {
        return "电话基本功能:发短信";
    }
}


2、抽象装饰器

View Code

package com.zixieqing.decorator;

import com.zixieqing.AbstractPhone;

/**
 * <p>@description  : 该类功能  抽象装饰器:对具体组件进行扩展
 * </p>
 * <p>@package      : com.zixieqing.decorator</p>
 * <p>@author       : ZiXieqing</p>
 */

public abstract class AbstractPhoneDecorator implements AbstractPhone{

    /**
     * 聚合抽象组件
     *    如果这个类中只有一个方法的话,可以直接把抽象组件引用做参数传递,参考:Spring的BeanDefinitionDecorator
     *    也可以根据情况选择耦合度更强一点的组合,看实际情况即可,只要这个抽象装饰器中有抽象组件的引用即可,设计模式不是一层不变的
     */
    protected AbstractPhone abstractPhone;

    public AbstractPhoneDecorator(AbstractPhone abstractPhone) {
        this.abstractPhone = abstractPhone;
    }


    /**
     * 基本功能:通话
     *
     * @return 通话内容
     */
    @Override
    public String call() {
        return this.abstractPhone.call();
    }

    /**
     * 基本功能:发短信
     *
     * @return 短信内容
     */
    @Override
    public String sendMessage() {
        return this.abstractPhone.sendMessage();
    }

    /**
     * 扩展功能:看电视
     */
    public abstract void watchTV();
}


  • 具体装饰器
View Code

package com.zixieqing.decorator.impl;

import com.zixieqing.AbstractPhone;
import com.zixieqing.decorator.AbstractPhoneDecorator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 该类功能  具体装饰器:不改变原有类(具体组件)的前提下,扩展其功能
 * </p>
 * <p>@package      : com.zixieqing.decorator.impl</p>
 * <p>@author       : ZiXieqing</p>
 */

public class PhoneImplDecorator extends AbstractPhoneDecorator {

    private Logger logger = LoggerFactory.getLogger(PhoneImplDecorator.class);

    public PhoneImplDecorator(AbstractPhone abstractPhone) {
        super(abstractPhone);
    }

    /**
     * 扩展功能:看电视
     */
    @Override
    public void watchTV() {
        logger.info("正在观看:精钢葫芦娃");
    }
}


3、测试


package com.zixieqing;

import com.zixieqing.decorator.impl.PhoneImplDecorator;
import com.zixieqing.impl.PhoneImpl;
import org.junit.Test;


/**
 * <p>@description  : 该类功能  测试
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 */

public class ApiTest {

    @Test
    public void decoratorTest() {
        // 没有装饰器的情况下
        PhoneImpl phone = new PhoneImpl();
        System.out.println(phone.call());
        System.out.println(phone.sendMessage());

        System.out.println("==============华丽的分隔符==============");

        PhoneImplDecorator phoneImplDecorator = new PhoneImplDecorator(phone);
        System.out.println(phoneImplDecorator.call());
        System.out.println(phoneImplDecorator.sendMessage());
        // 扩展
        phoneImplDecorator.watchTV();
    }
}

  • 结果

电话基本功能:进行通话
电话基本功能:发短信
==============华丽的分隔符==============
电话基本功能:进行通话
电话基本功能:发短信
正在观看:精钢葫芦娃


3.3.5、facade 外观模式

定义:外观模式又名门面模式(即:单词facade),指的是:向客户端提供一个客户端可以访问系统的接口,即:向现有的系统提供一个接口,从而隐藏系统的复杂性

场景理解:controller ——> service,前端进行访问的就是我们暴露出去的这个controller接口层,但在Java里面这是类——类,将这个东西进行放大,controller为门面,service直接变为一个子系统(这子系统中就是N多类),而客户端想要访问子系统,就避免不了和子系统中各个类进行打交道,而现在提供了门面facade,然后由facade去和子系统进行打交道,然后客户端只需要和facade进行交互即可,这样就隐藏系统的复杂性了,图示如下:

image-20221225213937383

适用场景:

  • 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可
  • 2、定义系统的入口时
  • 3、子系统相对独立时
  • 4、预防低水平人员带来的风险时

外观模式的角色:

  • 门面角色(facade):客户端可以调用这个角色的方法。此角色知晓相关的(一个或多个)子系统的功能和职责。在正常情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去
  • 子系统(subSystem):可以同时有一个或多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。每一个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另一个客户端而已

一个系统可以有几个门面类?

  • 每一个子系统有一个门面类,并且此门面类只有一个实例(即:单例类),整个系统可以有数个门面类

外观模式的优点:

  • 减少系统相互依赖
  • 提高灵活性
  • 提高了安全性

外观模式的缺点:

  • 不符合开闭原则。因为如果子系统中添加了新行为时,需要让门面类中囊括进去这个新行为,从而让客户端进行调用,此时一是修改门面类(不可取);二是继承门面类,加入新行为(但继承能少用则少用);三是在设计时就考虑这点,然后配合装饰器模式,从而方便后续扩展
  • 因为把子系统中的很多东西都糅合到门面类中了,所以可能会操作不当,从而带来另外未知风险,因此:这点也是慎用这个模式的原因

3.3.5、简单逻辑

场景:使用一个文件加载、简单加密的示例


1、读取文件

View Code

package com.zixieqing;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

/**
 * <p>@description  : 该类功能  充当子系统功能之一:文件加载
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 */

public class FileLoad {
    private Logger logger = LoggerFactory.getLogger(FileLoad.class);

    /**
     * 加载文件
     * @param filePath 文件路径
     * @return 加载出来的内容
     */
    public String loadFile(String filePath) {
        logger.info("即将读取文件内容!");

        BufferedReader br = null;
        StringBuffer result = new StringBuffer();
        try {
            br = new BufferedReader(new FileReader(filePath));
            String data = "";
            while (null != (data = br.readLine())) {
                result.append("\n").append(data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != br) {
                try {
                    br.close();
                    logger.info("文件读取完毕!");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return result.toString();
    }
}


2、文件加密

View code

package com.zixieqing;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 该类功能  充当子系统功能之一:文件加密
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 */

public class FileEncrypt {
    private Logger logger = LoggerFactory.getLogger(FileEncrypt.class);

    /**
     * <p>@description  : 该方法功能 加密文件
     * </p>
     * <p>@methodName   : encryptFile</p>
     * <p>@author: ZiXieqing</p>
     * @param text  要加密的内容
     * @return java.lang.String 加密后的内容
     */
    public String encryptFile(String text) {
        logger.info("即将对文件内容进行加密!");
        // 简单地对文本内容进行反转
        String result = new StringBuffer(text).reverse().toString();

        logger.info("文件内容加密成功");

        return result;
    }
}


3、写入文件

View Code

package com.zixieqing;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * <p>@description  : 该类功能  充当子系统功能之一:写文件
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 */

public class FileWrite {
    private Logger logger = LoggerFactory.getLogger(FileWrite.class);

    /**
     * <p>@description  : 该方法功能 写文件
     * </p>
     * <p>@methodName   : writeFile</p>
     * <p>@author: ZiXieqing</p>
     * @param filePath 要写入的路径
     * @param text  要写入的内容
     * @return boolean 是否写入成功
     */
    public boolean writeFile(String filePath, String text) {
        FileOutputStream fos = null;
        try {
            logger.info("正在进行文件写入!");
            fos = new FileOutputStream(filePath);
            fos.write(text.getBytes(StandardCharsets.UTF_8));
            logger.info("文件写入完毕!");
            return true;
        } catch (IOException e) {
            logger.info("文件写入失败!");
            e.printStackTrace();
            return false;
        } finally {
            if (null != fos) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


4、门面角色

View Code

package com.zixieqing;

import java.util.concurrent.atomic.AtomicReference;

/**
 * <p>@description  : 该类功能  门面角色:单例的
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 */

public class FileFacade {
    private static final AtomicReference<FileFacade> INSTANCE = new AtomicReference<>();
    /**
     * 加载文件
     */
    private FileLoad fileLoad;
    /**
     * 写入文件
     */
    private FileWrite fileWrite;
    /**
     * 文件加密
     */
    private FileEncrypt fileEncrypt;

    private FileFacade() {
        fileLoad = new FileLoad();
        fileWrite = new FileWrite();
        fileEncrypt = new FileEncrypt();
    }

    /**
     * <p>@description  : 该方法功能 加载文件
     * </p>
     * <p>@methodName   : fileLoad</p>
     * <p>@author: ZiXieqing</p>
     * @param filePath  文件路径
     * @return java.lang.String 加载出来的文件
     */
    public String fileLoad(String filePath) {
        return fileLoad.loadFile(filePath);
    }

    /**
     * <p>@description  : 该方法功能 加密文件
     * </p>
     * <p>@methodName   : fileEncrypt</p>
     * <p>@author: ZiXieqing</p>
     * @param text  要加密的内容
     * @return java.lang.String 加密后的内容
     */
    public String fileEncrypt(String text) {
        return fileEncrypt.encryptFile(text);
    }

    /**
     * <p>@description  : 该方法功能 写文件
     * </p>
     * <p>@methodName   : fileWrite</p>
     * <p>@author: ZiXieqing</p>
     * @param filePath 要写入的路径
     * @param text  要写入的内容
     * @return boolean 是否写入成功
     */
    public boolean fileWrite(String filePath, String text) {
        return fileWrite.writeFile(filePath, text);
    }

    public static FileFacade getInstance() {
        while (true) {
            FileFacade FILEFACADE_INSTANCE = INSTANCE.get();

            if (null != FILEFACADE_INSTANCE) return FILEFACADE_INSTANCE;

            INSTANCE.compareAndSet(null, new FileFacade());

            return INSTANCE.get();
        }
    }
}


5、测试

View Code

package com.zixieqing;

import org.junit.Test;

/**
 * <p>@description  : 该类功能  测试
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 */

public class ApiTest {

    @Test
    public void facadeTest() {
        FileFacade fileFacade = FileFacade.getInstance();
        boolean result = fileFacade.fileWrite("src/main/resources/encryptText.txt",
                fileFacade.fileEncrypt(fileFacade.fileLoad("src/main/resources/originalText.txt")));

        System.out.println("result = " + result);
    }
}


  • 结果

23:22:54.968 [main] INFO  com.zixieqing.FileLoad - 即将读取文件内容!
23:22:54.971 [main] INFO  com.zixieqing.FileLoad - 文件读取完毕!
23:22:54.971 [main] INFO  com.zixieqing.FileEncrypt - 即将对文件内容进行加密!
23:22:54.971 [main] INFO  com.zixieqing.FileEncrypt - 文件内容加密成功
23:22:54.971 [main] INFO  com.zixieqing.FileWrite - 正在进行文件写入!
23:22:54.971 [main] INFO  com.zixieqing.FileWrite - 文件写入完毕!
result = true


* 3.3.6、composite 组合模式

定义:按照单词composite翻译叫做合成模式也行,有时也会叫部分-整体模式(part-whole)。指的是:将一组相似的对象(也可以称为方法)组合成一组可被调用的结构树对象(或叫组成一个单一的对象)

意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性

适用场景:

  1. 想表示对象的部分-整体层次结构(树形结构)
  2. 希望客户端忽略组合对象(树枝对象)与个体对象(树叶对象)的不同
    1. 忽略组合对象与单个对象的不同:客户端可以把一个个单独的个体对象和由它们组成的复合对象同等看待(像操作个体对象一样操作复合对象),从而使得客户端与复杂对象的内部结构解耦

场景理解:

  1. 树形菜单
  2. 文件夹、文件的管理

3.3.6.1、结构树

上面组合模式的定义中说到了“结构树”,所以这里就顺便提一下“结构树”这个玩意儿


3.3.6.1.1、从上到下结构树

image-20221229230814299

每个树枝节点都有箭头指向它的所有的子节点,从而一个客户端可以要求一个树枝节点给出所有的子节点,而一个子节点却并不知道它的父节点。在这样的树结构上,信息可以按照箭头所指的方向从上到下传播


3.3.6.1.2、从下到上结构树

image-20221229231111973

每一个子节点都有指向它的父节点,但是一个父节点却不知道其子节点。在这样的树结构上,信息可以按照箭头所指的方向从下到上传播


3.3.6.1.3、双向结构树

image-20221229231253900

每一个子节点都同时知道它的父节点和所有的子节点,在这样的树结构上,信息可以按照箭头所指的方向向两个方向传播


3.3.6.2、树结构的节点

一个树结构由两种节点组成:树枝节点和树叶节点。树枝节点可以有子节点,而一个树叶节点不可以有子节点

  • 一个树枝节点可以不带有任何叶子,但是它因为有带有叶子的能力,因此仍然是树枝节点,而不会称为叶子结点,一个树叶节点则永远不可能带有子节点

根节点

  • 一个树结构中总有至少一个节点是特殊的节点,称为根节点
  • 一个根节点没有父节点,因为它是树结构的根
  • 一个树的根节点一般是树枝节点

3.3.6.3、组合模式的角色

抽象组件角色(component):是一个接口或抽象类。给参加组合的对象指定规约,给出共有的接口及其默认行为。这个角色尽量多“重”才好,这里的多“重”其实就是下面的要说的安全式组合模式 和 透明式组合模式,但各有各的好处和缺点,根据实际情况选择即可

树叶组件角色(leaf):代表参加组合的树叶对象,它没有下级的子对象,定义出参加组合的原始对象的行为。相当于部门中的某个人,如:小张

树枝组件角色(composite):代表参加组合的有子对象的对象,即 定义的是具有子节点的组件的行为。相当于整个部门,整个部门就是像小张这样一个个的对象组成

组合模式逻辑草图

image-20230102161552351

根据上面这种结构,可以搞出两种设计方式:透明式 和 安全式,至于具体用哪一种,根据实际情况选择适合的即可


透明式

  • 即在Component中声明所有用来管理子类对象的方法,包括add()、remove()、getChild()
  • 好处:所有的组件类都有相同的接口,在客户端看来,树叶类对象与合成类对象的区别起码在接口层次上消失了,客户端可以同等地对待所有的对象
  • 缺点:不够安全。因为树叶类对象和合成类对象在本质上是有区别的。树叶类对象不可能有下一个层次的对象,因此 add()、remove()、getChild()没有意义,但在编译时期不会出错,只会在运行时期才会出错
  • 类图结构如下:

image-20230104122811382

  • 这种设计有时会面临一个问题:对于组合对象(Composite)来说 有管理方法add()添加子组件、remove()删除子组件、getChildren()很正常,因为它会有下级的子对象,但是对于单体对象(Leaf)来说,它没有下级子对象,它是组成组合对象的单个对象,所以它关注的是“原始对象”的行为。因此:这里需要变一下,让其成为安全式组合模式

安全式

  • 即在 Composite 类中声明所有的用来管理子类对象的方法
  • 好处:安全。因为树叶类对象根本就没有管理子类对象的方法,因此,如果客户端对树叶类对象使用这些方法时,程序会在编译时就出错,编译通不过就不会出现运行时错误
  • 缺点:不够透明。因为树叶类和合成类将具有不同的即可欧
  • 类图结构如下:

image-20230104122904404


3.3.6.4、示例

image-20230106154602476


1、抽象组件:MenuComponent

View Code

package com.zixieqing.o1transparent;

/**
 * <p>@description  : 该类功能  透明式组合模式:抽象组件
 * 定义出树叶组件和数纸组件共同遵守的约定
 * </p>
 * <p>@package      : com.zixieqing.o1transparent</p>
 * <p>@author       : ZiXieqing</p>
 */

public abstract class MenuComponent {
    /**
     * 菜单或菜单项的名字
     * @return String
     */
    public abstract String getName();

    /**
     * 添加下级子菜单
     * @param menuComponent 要添加的子菜单组件
     * @return boolean
     */
    public boolean add(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    /**
     * 删除下级子菜单项
     * @param menuComponent 要删除的子菜单组件
     * @return boolean
     */
    public boolean remove(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    /**
     * 获取子菜单项
     * @param i 第i个子菜单
     * @return MenuComponent
     */
    public MenuComponent getChild(int i) {
        throw new UnsupportedOperationException();
    }

    /**
     * 打印菜单
     */
    public abstract void print();
}


2、树叶组件角色:MenuItem

View Code

package com.zixieqing.o1transparent.impl;

import com.zixieqing.o1transparent.MenuComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 该类功能  透明式组合模式:树叶角色 个体对象
 * 一个个的个体对象 组成 组合对象
 * 定义组合对象的原始对象的行为
 * 此对象没有下级的子对象,因此:add、remove、getChild管理下级子对象的方法不支持
 * </p>
 * <p>@package      : com.zixieqing.o1transparent.impl</p>
 * <p>@author       : ZiXieqing</p>
 */

public class MenuItem extends MenuComponent {

    private Logger logger = LoggerFactory.getLogger(MenuItem.class);

    /**
     * 菜单项的名字
     */
    private String name;

    public MenuItem(String name) {
        this.name = name;
    }

    /**
     * 菜单或菜单项的名字
     *
     * @return String
     */
    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void print() {
        logger.info("{}",getName());
    }
}


3、树枝组件角色:Menu

View Code

package com.zixieqing.o1transparent.impl;

import com.zixieqing.o1transparent.MenuComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * <p>@description  : 该类功能  透明式组合模式:树枝角色
 * 此角色有下级的子对象,因此:有管理下级子对象的add、remove、getChild方法
 * </p>
 * <p>@package      : com.zixieqing.o1transparent.impl</p>
 * <p>@author       : ZiXieqing</p>
 */

public class Menu extends MenuComponent {

    private Logger logger = LoggerFactory.getLogger(Menu.class);

    private List<MenuComponent> menuComponents = new ArrayList<>();

    /**
     * 菜单的名字
     */
    private String name;

    public Menu(String name) {
        this.name = name;
    }

    /**
     * 菜单或菜单项的名字
     *
     * @return String
     */
    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public boolean add(MenuComponent menuComponent) {
        return this.menuComponents.add(menuComponent);
    }

    @Override
    public boolean remove(MenuComponent menuComponent) {
        return this.menuComponents.remove(menuComponent);
    }

    @Override
    public MenuComponent getChild(int i) {
        return this.menuComponents.get(i);
    }

    @Override
    public void print() {
        logger.info("{}", getName());

        Iterator<MenuComponent> componentIterator = this.menuComponents.iterator();
        while (componentIterator.hasNext()) {
            MenuComponent menuComponent = (MenuComponent)componentIterator.next();
            menuComponent.print();
        }
    }
}


4、测试

View Code

package com.zixieqing;

import com.zixieqing.o1transparent.MenuComponent;
import com.zixieqing.o1transparent.impl.Menu;
import com.zixieqing.o1transparent.impl.MenuItem;
import org.junit.Test;

/**
 * <p>@description  : 该类功能  测试
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 */

public class ApiTest {

    @Test
    public void transparentTest() {
        /*
        * 构建树形结构
        *            开发用户手册
        *              /   \
        *        管理             运维服务
        *         |                 |
        *      日志记录         数据同步    DB维护    资产申报    DB主机容器管理
        * */

        MenuComponent userManualMenu = new Menu("开发用户手册");
        MenuComponent manageMenu = new Menu("管理");
        MenuComponent devOpsServiceMenu = new Menu("运维服务");

        userManualMenu.add(manageMenu);
        userManualMenu.add(devOpsServiceMenu);
        manageMenu.add(new MenuItem("日志记录"));
        devOpsServiceMenu.add(new MenuItem("数据同步"));
        devOpsServiceMenu.add(new MenuItem("DB维护"));
        devOpsServiceMenu.add(new MenuItem("资产申报"));
        devOpsServiceMenu.add(new MenuItem("DB主机容器管理"));

        userManualMenu.print();
    }
}


  • 结果

14:34:10.142 [main] INFO  c.zixieqing.o1transparent.impl.Menu - 开发用户手册
14:34:10.144 [main] INFO  c.zixieqing.o1transparent.impl.Menu - 管理
14:34:10.144 [main] INFO  c.z.o1transparent.impl.MenuItem - 日志记录
14:34:10.144 [main] INFO  c.zixieqing.o1transparent.impl.Menu - 运维服务
14:34:10.144 [main] INFO  c.z.o1transparent.impl.MenuItem - 数据同步
14:34:10.144 [main] INFO  c.z.o1transparent.impl.MenuItem - DB维护
14:34:10.144 [main] INFO  c.z.o1transparent.impl.MenuItem - 资产申报
14:34:10.144 [main] INFO  c.z.o1transparent.impl.MenuItem - DB主机容器管理

上面只是简单示例,为了便于理解罢了,也可以弄文件夹+文件,或者将树叶、树枝组件角色是另外的组合进来的对象,这些对象有什么节点名字、节点类型(树枝、树叶节点)、节点ID、节点值等等属性这些,发散思维随便扩展即可,还是那句话:设计模式是思想,形只是便于理解,按照思想可以随意发挥,不要拘泥于本内容所弄的那些类图形式


3.3.6.5、组合模式与命令模式的关系

这两个模式可以说是很要好的“基友”,二者一起使用的情况很多,因为当命令/请求很多的时候,接收者不断接收不太好,所以可以使用组合模式将命令进行组合之后,让接收者一次性接收

场景理解:饭店点菜,点一个厨师炒一个 和 将所有的菜都弄在一张纸上全部给厨师

image-20230105153329355


3.3.6.6、组合模式与责任链模式的关系

image-20230105161743073

image-20230105162141452

上图就是这两个模式结合在一起的切入点,在组合模式的树枝节点中切入责任链模式也好;将责任链模式的处理链弄为组合模式也罢,都可以

另外:上面代码截图框起来那里,用了迭代器,即:合成模式的组合对象遍历子对象就用的是 迭代器模式


3.3.6.7、组合模式与装饰器模式的关系

image-20230106155713012

类似地,在组合模式中,后期需要加新的行为时,既要保证组合对象身上可以执行新的行为也要保证原始对象也有,这时就可以利用“装饰器模式”的思想进行新行为添加,装饰器模式类图如下(和组合模式很像):

image-20230106155959101


3.3.7、flyweight 亨元模式

定义:共享通用对象,减少内存的使用,提高系统访问率 这部分共享对象通常很耗费内存 或 需要查询大量接口 亦或 使用数据库资源,因此将这种对象进行抽离,进行共享(这也是此模式的适用场景

亨元模式能够进行共享的关键:内蕴状态和外蕴状态

  1. 内蕴状态: 保存在亨元对象内部,不会随着环境改变而改变。因此一个亨元对象可以具有内蕴状态并可进行共享
  2. 外蕴状态: 必须保存在客户端,会随着环境的改变而改变。在亨元对象被创建后,在需要时再传入到亨元对象内部
  3. 注: 外蕴状态不可以影响内蕴状态,彼此是独立的

场景理解: 一个文本编辑器,有很多字体,可将每个字母弄为一个亨元对象。内蕴状态就是这个字母,而外蕴状态就是字母在文本中的位置和字模风格等其他信息(如:字母Z可出现在文本很多地方,虽然这些字母Z的位置和字模风格不同,但这些所有地方使用的都是同一个字母对象)

Java中的应用: String中便用到了此模式;还有数据库的数据池

  • String是不可变的,一旦创建处理就不能改变,需要改变就只能新建一个String。在JVM内部,String是共享的,如果两个String包含的字符串是一样的,那么JVM就会只创建一个String对象给两个引用,从而实现String共享。即:熟知的有则返回,如果没有则创建一个字符串保存在字符串缓存池里面
  • String的intern()可以获取这个字符串在共享池中的唯一实例

优点:

  • 大大减少对象的创建,降低系统的内存,使效率提高

缺点:

  • 提高了系统的复杂度。需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱

由亨元对象的内部表象的不同,亨元模式分为单纯亨元模式 和 复合亨元模式


3.3.7.1、单纯亨元模式

逻辑草图如下:

image-20230110155240139


单纯亨元模式的角色:

  • 抽象亨元角色(Flyweight): 制订出需要实现的公共姐。那些需要外蕴状态(External State)的操作可通过调用业务方法(operation)以参数传入
  • 具体亨元角色(ConcreteFlyweight): 抽象亨元角色的子类,实现公共接口。若这个具体亨元角色有内蕴状态的话,必须负责为内蕴状态提供存储空间。亨元对象的内蕴状态必须与对象所处的环境无关,从而使得亨元对象可在系统内共享
  • 亨元工厂(Flyweight): 负责创建于管理亨元角色。此角色必须保证亨元对象可以被系统适当地共享。当一个客户端对象调用一个亨元对象时,亨元工厂会检查系统中是否已经有一个符合要求的亨元对象,若有则提供这个已有的亨元对象,若无则亨元工厂应创建一个合适的亨元对象
  • 客户端(Client): 此角色需要维护一个对所有亨元对象的引用(即:抽象亨元角色),本角色需要存储所有亨元对象的外蕴状态

示例代码

  • 抽象亨元角色
View Code

package com.zixieqing.o1simple;

/**
 * <p>@description  : 该类功能  单纯亨元模式:抽象亨元角色
 *                    规定出需要实现的公共接口
 *                    外蕴状态以参数的形式传入
 *    外蕴状态:随环境的改变而改变,由客户端保存,在亨元对象创建之后,在需要时传入到亨元对象内部
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 */

public abstract class AbstractFlyweight {

    /**
     * 业务操作方法
     * @param externalState 外蕴状态
     */
    public abstract void operation(Character externalState);
}



  • 具体亨元角色
View Code


package com.zixieqing.o1simple.impl;

import com.zixieqing.o1simple.AbstractFlyweight;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 该类功能  单纯亨元模式:具体亨元角色
 * </p>
 * <p>@package      : com.zixieqing.o1simple.impl</p>
 * <p>@author       : ZiXieqing</p>
 */

public class ConcreteFlyweight extends AbstractFlyweight {

    private Logger logger = LoggerFactory.getLogger(ConcreteFlyweight.class);

    /**
     * 内蕴状态:存储在亨元对象内部,不会随着外部环境改变而改变
     * 可以进行共享,由客户端调用时以参数传入
     */
    private Character internalState;

    public ConcreteFlyweight(Character internalState) {
        this.internalState = internalState;
    }

    /**
     * 业务操作方法
     *
     * @param externalState 外蕴状态,不会随环境改变而改变,必须由客户端保存,客户端以参数形式传入
     */
    @Override
    public void operation(Character externalState) {
        logger.info("进入了 {} 类", this.getClass().getSimpleName());
        logger.info("{} 的内蕴状态为:{},hashcode为:{}",
                this.getClass().getSimpleName(),
                this.internalState,
                this.internalState.hashCode());
        logger.info("{} 的外蕴状态为:{},hashcode为:{}",
                this.getClass().getSimpleName(),
                externalState,
                externalState.hashCode());
    }
}



  • 亨元工厂
View Code

package com.zixieqing.o1simple;

import com.zixieqing.o1simple.impl.ConcreteFlyweight;

import java.util.HashMap;
import java.util.Map;

/**
 * <p>@description  : 该类功能  单纯亨元模式:亨元工厂 负责创建和管理亨元角色
 *                      客户端调用此工厂进行亨元对象的创建
 *                      若是亨元对象需要外蕴状态的话,则由客户端调用此工厂时传入
 *                      会检查系统中是否已经有了符合要求的亨元对象,有则返回,没有则创建
 *                      此工厂在整个系统中是唯一的,因此:此工厂为单例模式
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 */

public class FlyweightFactory {

    /**
     * 聚合抽象亨元角色
     */
    private AbstractFlyweight flyweight;

    /**
     * 用来存储亨元对象,可以采用其他的存储机制
     */
    private Map<Character, AbstractFlyweight> flyweightHashMap = new HashMap<>();

    private static volatile FlyweightFactory INSTANCE;

    /**
     * 创建亨元对象
     * @param internalState 内蕴状态:由客户端调用时以形参传入
     * @return 抽象亨元角色
     */
    public AbstractFlyweight factory(Character internalState) {

        if (flyweightHashMap.containsKey(internalState)) return flyweightHashMap.get(internalState);

        ConcreteFlyweight concreteFlyweight = new ConcreteFlyweight(internalState);
        flyweightHashMap.put(internalState, concreteFlyweight);
        return concreteFlyweight;
    }

    private FlyweightFactory() {
    }

    public static FlyweightFactory getInstance() {
        if (null != INSTANCE) return INSTANCE;

        synchronized (FlyweightFactory.class) {
            if (null == INSTANCE) return new FlyweightFactory();
        }

        return INSTANCE;
    }
}


  • 测试+客户端
View Code

package com.zixieqing;

import com.zixieqing.o1simple.AbstractFlyweight;
import com.zixieqing.o1simple.FlyweightFactory;
import org.junit.Test;

/**
 * <p>@description  : 该类功能  测试
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 */

public class ApiTest {

    @Test
    public void simpleFlyweightTest() {
        FlyweightFactory flyweightFactory = FlyweightFactory.getInstance();
        AbstractFlyweight abstractFlyweight_a = flyweightFactory.factory(new Character('a'));
        System.out.println(abstractFlyweight_a.hashCode());
        // 由客户端以参数的形式传入外蕴状态
        abstractFlyweight_a.operation('紫');

        AbstractFlyweight abstractFlyweight_b = flyweightFactory.factory(new Character('b'));
        System.out.println(abstractFlyweight_b.hashCode());
        abstractFlyweight_b.operation('邪');

        AbstractFlyweight abstractFlyweight_c = flyweightFactory.factory(new Character('a'));
        System.out.println(abstractFlyweight_c.hashCode());
        abstractFlyweight_c.operation('紫');
    }
}


  • 结果
View Code

1929600551
16:06:34.253 [main] INFO  c.z.o1simple.impl.ConcreteFlyweight - 进入了 ConcreteFlyweight 类
16:06:34.255 [main] INFO  c.z.o1simple.impl.ConcreteFlyweight - ConcreteFlyweight 的内蕴状态为:a,hashcode为:97
16:06:34.255 [main] INFO  c.z.o1simple.impl.ConcreteFlyweight - ConcreteFlyweight 的外蕴状态为:紫,hashcode为:32043
1879492184
16:06:34.255 [main] INFO  c.z.o1simple.impl.ConcreteFlyweight - 进入了 ConcreteFlyweight 类
16:06:34.255 [main] INFO  c.z.o1simple.impl.ConcreteFlyweight - ConcreteFlyweight 的内蕴状态为:b,hashcode为:98
16:06:34.255 [main] INFO  c.z.o1simple.impl.ConcreteFlyweight - ConcreteFlyweight 的外蕴状态为:邪,hashcode为:37034
1929600551
16:06:34.255 [main] INFO  c.z.o1simple.impl.ConcreteFlyweight - 进入了 ConcreteFlyweight 类
16:06:34.255 [main] INFO  c.z.o1simple.impl.ConcreteFlyweight - ConcreteFlyweight 的内蕴状态为:a,hashcode为:97
16:06:34.255 [main] INFO  c.z.o1simple.impl.ConcreteFlyweight - ConcreteFlyweight 的外蕴状态为:紫,hashcode为:32043


3.3.7.2、复合亨元模式

image-20230111173438968

由上图就可以看出来,复合亨元模式其实就是 单纯亨元模式+组合模式


复合亨元模式的角色

  • 抽象亨元角色: 制定出需要实现的公共接口,需要外蕴状态的操作可以通过调用业务方法以参数传入
  • 具体亨元角色: 又叫单纯具体亨元角色,复合亨元角色就是由此复合而来(参考组合模式的树叶、树枝角色),是抽象亨元角色的子类,实现公共接口。若这个具体亨元角色有内蕴状态的话,必须负责为内蕴状态提供存储空间。亨元对象的内蕴状态必须与对象所处的环境无关,从而使得亨元对象可在系统内共享
  • 复合亨元角色(UnsharableFlyweight): 又叫不可共享亨元对象, 此角色所代表的的对象时不可以共享的,但一个复合亨元对象可以分解为多个单纯亨元对象
    • 复合亨元对象是由单纯亨元对象(FlyweightImpl)组合而来,所以复合亨元对象可以也具有add、remove方法
    • 一个复合亨元对象中的所有单纯亨元对象的外蕴状态 和 复合亨元对象的外蕴状态是相等的
    • 但:一个复合亨元对象中的单纯亨元对象(FlyweightImpl)彼此之间的内蕴状态不同,不然没意义了
  • 亨元工厂: 负责创建于管理亨元角色。此角色必须保证亨元对象可以被系统适当地共享。当一个客户端对象调用一个亨元对象时,亨元工厂会检查系统中是否已经有一个符合要求的亨元对象,若有则提供这个已有的亨元对象,若无则亨元工厂应创建一个合适的亨元对象
  • 客户端(Client): 此角色需要维护一个对所有亨元对象的引用(即:抽象亨元角色),**本角色需要存储所有亨元对象的外蕴状

示例代码如下

  • 抽象亨元角色
View Code

package com.zixieqing.o2composite;

/**
 * <p>@description  : 该类功能  复合亨元模式:抽象亨元角色
 *  制定出需要实现的公共接口
 *  需要外蕴状态的操作可以通过调用业务方法以参数传入
 *  可以做到:并不是所有的亨元对象都是可以共享的,在具体亨元类中做处理即可
 * </p>
 * <p>@package      : com.zixieqing.o2composite</p>
 * <p>@author       : ZiXieqing</p>
 */

public abstract class Flyweight {
    /**
     * 业务操作方法
     * @param externalState 外蕴状态
     */
    public abstract void operation(String externalState);
}


  • 具体亨元角色
View Code

package com.zixieqing.o2composite.impl;

import com.zixieqing.o2composite.Flyweight;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 该类功能  复合亨元模式:具体亨元角色  又叫单纯具体亨元角色 相当于合成模式中的树叶角色
 * </p>
 * <p>@package      : com.zixieqing.o2composite.impl</p>
 * <p>@author       : ZiXieqing</p>
 */

public class FlyweightImpl extends Flyweight {

    private Logger logger = LoggerFactory.getLogger(FlyweightImpl.class);

    /**
     * 内蕴状态
     */
    private String internalState;

    public FlyweightImpl(String internalState) {
        this.internalState = internalState;
    }

    /**
     * 业务操作方法
     *
     * @param externalState 外蕴状态
     */
    @Override
    public void operation(String externalState) {
        logger.info("进入了 {} 类", this.getClass().getSimpleName());
        logger.info("{} 的内蕴状态为:{},hashcode为:{}",
                this.getClass().getSimpleName(),
                this.internalState,
                this.internalState.hashCode());
        logger.info("{} 的外蕴状态为:{},hashcode为:{}",
                this.getClass().getSimpleName(),
                externalState,
                externalState.hashCode());
    }
}



  • 复合亨元角色
View Code

package com.zixieqing.o2composite.impl;

import com.zixieqing.o2composite.Flyweight;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * <p>@description  : 该类功能  复合亨元模式:复合亨元角色 类似组合模式中的树枝角色
 *  又叫不可共享亨元对象, 此角色所代表的的对象时不可以共享的,但一个复合亨元对象可以分解为多个单纯亨元对象
 *  复合亨元对象是由单纯亨元对象(FlyweightImpl)组合而来,所以复合亨元对象可以也具有add、remove方法
 *  一个复合亨元对象中的所有单纯亨元对象的外蕴状态 和 复合亨元对象的外蕴状态是相等的
 *  但:一个复合亨元对象中的单纯亨元对象(FlyweightImpl)彼此之间的内蕴状态不同,不然没意义了
 * </p>
 * <p>@package      : com.zixieqing.o2composite.impl</p>
 * <p>@author       : ZiXieqing</p>
 */

public class CompositeFlyweightImpl extends Flyweight {

    private Logger logger = LoggerFactory.getLogger(CompositeFlyweightImpl.class);

    /**
     * Flyweight实例容器
     */
    private Map<String, Flyweight> flyweightMap = new HashMap<>();

    /**
     * 添加Flyweight实例
     * @param key 键
     * @param value 值
     */
    public void addFlyweight(String key, Flyweight value) {
        flyweightMap.put(key, value);
    }

    /**
     * 业务操作方法
     *
     * @param externalState 外蕴状态
     */
    @Override
    public void operation(String externalState) {
        logger.info("进入了 {} ", this.getClass().getSimpleName());
        Flyweight flyweightInstance;
        for (Map.Entry<String, Flyweight> characterFlyweightEntry : flyweightMap.entrySet()) {
            flyweightInstance = characterFlyweightEntry.getValue();
            flyweightInstance.operation(externalState);
        }
    }
}



  • 亨元工厂
View Code

package com.zixieqing.o2composite;

import com.zixieqing.o2composite.impl.CompositeFlyweightImpl;
import com.zixieqing.o2composite.impl.FlyweightImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * <p>@description  : 该类功能  复合亨元模式:亨元工厂  一样还是单例 负责创建于管理亨元角色
 * 此角色必须保证亨元对象可以被系统适当地共享
 * 当一个客户端对象调用一个亨元对象时,亨元工厂会检查系统中是否已经有一个符合要求的亨元对象,
 *     若有则提供这个已有的亨元对象,若无则亨元工厂应创建一个合适的亨元对象
 * </p>
 * <p>@package      : com.zixieqing.o2composite</p>
 * <p>@author       : ZiXieqing</p>
 */

public class CompositeFlyweightFactory {

    private Logger logger = LoggerFactory.getLogger(CompositeFlyweightFactory.class);

    private static volatile CompositeFlyweightFactory INSTANCE;

    /**
     * 装单纯亨元对象 和 复合亨元对象的容器
     */
    private Map<String, Flyweight> flyweightMap = new HashMap<>();

    /**
     * 获取单纯亨元对象
     * @param externalState 外蕴状态
     * @return Flyweight
     */
    public Flyweight simpleFlyweightFactory(String externalState) {
        logger.info("进入 {} 的 compositeFlyweightFactory 方法", this.getClass().getSimpleName());

        if (flyweightMap.containsKey(externalState)) return flyweightMap.get(externalState);

        FlyweightImpl flyweight = new FlyweightImpl(externalState);
        flyweightMap.put(externalState, flyweight);
        return flyweight;
    }

    /**
     * 获取复合亨元对象
     * @param externalState 外蕴状态
     * @return Flyweight
     */
    public Flyweight compositeFlyweightFactory(String externalState) {
        logger.info("进入 {} 的 compositeFlyweightFactory 方法", this.getClass().getSimpleName());

        CompositeFlyweightImpl compositeFlyweight = new CompositeFlyweightImpl();
        compositeFlyweight.addFlyweight(externalState, this.simpleFlyweightFactory(externalState));
        return compositeFlyweight;
    }

    private CompositeFlyweightFactory() {
    }

    /**
     * 获取工厂实例
     * @return CompositeFlyweightFactory
     */
    public static CompositeFlyweightFactory getInstance() {
        if (null != INSTANCE) return INSTANCE;

        synchronized (CompositeFlyweightFactory.class) {
            if (null == INSTANCE) {
                INSTANCE = new CompositeFlyweightFactory();
            }
        }

        return INSTANCE;
    }
}



3.3.7.3、备忘录模式与亨元工厂的关系

image-20230112103155584

上图中的代码位置是亨元工厂中的一个属性,即:状态,代表的是一个容器,而这个容器里面存放的就是系统全局相同的实例,所以:要存放的地方除了上面搞的hashMap,还可以用数据库,因此拆出关键信息:一个代表容器的状态、存放、数据库,结构就可以变为如下的样子:

image-20230112104111767

这样的结构不就是:备忘录模式

image-20230112104157449


其实亨元模式中的亨元对象在大多数情况下是被整成不变的,因此亨元对象可以借助另一个模式来进行设计,即:不变模式(inmmutable),但目前还没玩,这个模式没在23种设计模式中,后续会进行拓展其他模式


3.4、扩展

3.4.1、immutable 不变模式

这个设计模式是Mark Grand在1998年出版的[GRAND98]中首次提出的,但他那里面是弱不变模式

说明一下“不变(immutable)”与“只读(read only)”的区别: 当一个变量是“只读”时,变量的值不能直接改变,但是可以在其他变量发生改变时被改变,即:只读的值是不能直接被改变,但可被间接地改变

  • 举个例子:一个人的出生年月日是“不变”属性,而这个人的年龄就是“只读”属性(不是“不变”属性),随着时间的推移,这个人的年龄会随之发生变化,但这个人的出生年月日则不会变化,这就是“不变”和“只读”的区别

定义:指的是一个对象的状态在对象被创建之后就不再发生变化。 此模式缺少改变自身状态的行为,因此:它是关于行为的,属于行为型设计模式

不变模式只涉及一个类,一个类的内部状态创建后,在整个生命期间都不会发生改变,所以这样的类就叫不变类,使用不变类的做法当然就成不变模式了

不变模式可增强对象的健壮性,不变模式允许多个对象共享某一对象,降低对该对象进行并发访问时的同步化开销,如果需要修改一个不变对象的状态,则需要新建一个对象,并在创建时将这个新的状态存储在新对象中

不变模式分为两种:弱不变模式 和 强不变模式

适用场景:

  1. 对象会被多线程访问,而此对象又是一个需要共享的对象。即:多线程对同一个对象进行操作时,为了保证对象数据的一致性和准确性,需要做相应的同步,来保证原子性、有序性以及可见性

3.4.1.1、weakly immutable 弱不变模式

定义:指的是一个类的实例的状态是不可变化的,但这个类的子类的实例具有可能会变化的状态,这种类必须满足如下的条件:

  1. 所考虑的对象没有任何方法会修改对象的状态,这样一来,当对象的构造函数将对象的状态初始化之后,对象的状态便不再改变
  2. 所有的属性都应当是私有的(不要声明任何的public的属性,是为了以防客户端对象直接修改任何的内部状态)
  3. 这个对象所引用到的其他对象如果是可变对象的话,则必须设法限制外界对这些可变对象的访问,以防止外界修改这些对象。如果可能,应当尽量在不变对象内部初始化这些被引用到的对象,而不要在客户端初始化,然后再传入到不变对象内部中;如果某个可变对象必须在客户端初始化,然后再传入到不变对象中的话,就应当考虑在不变对象初始化时将这个可变对象复制一份,而不要使用原来的拷贝

弱不变模式的缺点:

  1. 一个弱不变对象的子对象可以是可变对象,即:一个若不变对象的子对象可能是可变的
  2. 这个可变的子对象可能可以修改父对象的状态,从而可能会允许外界修改父对象的状态

3.4.1.2、strong immutable 强不变模式

定义:指的是一个类的实例的状态不会改变,同时它的子类的实例也具有不可变化的状态

强不变类需要满足的条件:

  1. 满足若不变类的所有条件
  2. 所考虑的类所有的方法都应当是final,这样这个类的子类就不能置换掉此类的方法
  3. 这个咧本身就是final修饰的,那么这个类就不可能会有子类,从而也就不可能有被子类修改的问题

强不变模式在Java中的应用最典型的例子就是:java.lang.String,如下面的代码:

String s = "紫邪情";
String b = "紫邪情";

Java 虚拟机其实只会创建这样--个字符串的实例,而这三个 String 对象都在共享这个值。如果程序所处理的字符串有频繁的内容变化时,就不宜使用 String 类型,所以使用 StringBufferStringBuilder

Java中还有 Integer、Float、Double、Byte、Long、Short、Boolean 和 Character,使用包装类的作用之一是: Long 类型的对象所起的作用在于它把--个 long 原始类型的值包装在--个对象里。比如:存放在 Vector 对象里面的必须是对象,而不可以是原始类型。有了封装类,就可以把原始数据类型包装起来作为对象处理。如果要将一个long 类型的值存放到一个 Vector 对象里面,就可以把这个 long 类型的值包装到 Long对象里面,然后再存放到 Vector 对象里,例子如下:

Vector y = new Vector();
v.addElement(new Long(100L));
v.addElement(new Long(101L));

这些封装类实际上都是强不变类,因为在这些类都是 final 的,而且在对象被创建时它们所蕴含的值(也就是它们的状态)就确定了,即:根本没有提供修改内部值的方法


3.4.1.3、不变模式的缺点

  1. 因为不能修改一个不变对象的状态,所以可以避免由此引起的不必要的程序错误。换言之,·一个不变的对象要比可变的对象更加容易维护
  2. 因为没有任何一个线程能够修改不变对象的内部状态,一个不变对象自动就是线程安全的 (Thread Safe),这样就可以省掉处理同步化的开销。一个不变对象可以自由地被不同的客户端共享。不变模式惟一的缺点是,一旦需要修改一个不变对象的状态,就只好创建·个新的同类对象。在需要频繁修改不变对象的环境里,会有大量的不变对象作为中间结果被创建出来,再被 Java 语言的垃圾收集器收集走。这是--种资源上的浪费

3.4.1.4、不变模式与亨元模式的关系

image-20230113170023117

享元模式以共享方式支持大量的实例。享元模式中的享元对象可以是不变对象。实际上,大多数的享元对象是不变对象

注意:享元模式并不要求享元对象是不变对象。享元模式要求享元对象的状态不随环境变化而变化,这是使享元对象可以共享的条件。当然如果享元对象成为不变对象的话,是满足享元模式要求的

享元模式对享元对象的要求是它的内蕴状态与环境无关。这就意味着如果享元对象具有某个可变的状态,但是只要不会影响享元对象的共享,也是允许的

不变模式对不变对象的约束较强,而享元模式对享元对象的约束较弱。只要系统允许,可以使用不变模式实现享元对象,但是享元对象不一定非得是不变对象不可


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java

“(Java)设计模式:结构型” 的相关文章