(Java)設計模式:結構型

紫邪情發表於2023-01-14

前言

這篇博文續接的是 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

享元模式以共享方式支援大量的例項。享元模式中的享元物件可以是不變物件。實際上,大多數的享元物件是不變物件

注意:享元模式並不要求享元物件是不變物件。享元模式要求享元物件的狀態不隨環境變化而變化,這是使享元物件可以共享的條件。當然如果享元物件成為不變物件的話,是滿足享元模式要求的

享元模式對享元物件的要求是它的內蘊狀態與環境無關。這就意味著如果享元物件具有某個可變的狀態,但是隻要不會影響享元物件的共享,也是允許的

不變模式對不變物件的約束較強,而享元模式對享元物件的約束較弱。只要系統允許,可以使用不變模式實現享元物件,但是享元物件不一定非得是不變物件不可


相關文章