主頁 > 軟體設計 > (Java)設計模式:行為型

(Java)設計模式:行為型

2022-11-29 08:01:00 軟體設計

前言

  • 此篇博文內容續接的是 UML建模語言、設計原則、創建型設計模式 的內容,有興趣的可以點前面的鏈接去看一下

3.2、行為型

這類設計模式是專門用于:物件間的高效溝通和職責委派


* 3.2.1、責任鏈模式

定義:責任鏈模式又名職責鏈模式,指的是:對某個請求的所有處理構成一條鏈,如果鏈上的某一處理者可以處理,則處理后回傳,如果不能處理則將請求傳遞給鏈上的下一個處理者

廢話文學:所謂責任鏈模式就是為了避免請求發送者與多個請求處理者耦合在一起,于是將所有請求的處理者通過前一物件記住其下一個物件的參考而連成一條鏈;當有請求發生時,可將請求沿著這條鏈傳遞,直到有物件處理它為止

場景理解:開發中的捕獲例外,只有遇到對應的某一個 或 某一類例外時,才會有對應的處理機制;還有Servlet編程中的Filter過濾器;以及SpringMVC執行原理(請求經過DispatcherServlet,然后經其轉到HandletMapping,然后通過HandlerExcuttionChain這條執行鏈將結果回傳給DispatcherServlet.....,)

圖示理解:使用的是Servlet的Filter舉例

image-20221101173549848

生活中的例子:跳槽離職,要找好多人審批、以及擊鼓傳花.....
使用場景:在處理訊息的時候要過濾很多道時就可以使用責任鏈模式


責任鏈模式的角色

  • 抽象處理者:一個請求介面,里面包含抽象的處理請求的方法 和 后繼鏈接
  • 具體處理者:抽象處理者的子類,實作了處理請求方法,判斷請求是否能夠處理,能則處理回傳結果,否則將請求傳給它的后繼者
  • 客戶端:創建處理鏈,并向鏈頭的具體處理者物件提交請求,它不關心處理細節和請求的傳遞程序

責任鏈模式的類圖

image-20221102101711575


3.2.1.1、簡單邏輯

1、抽象處理者

package com.zixieqing.handler;

/**
 * <p>@description  : 該類功能  抽象處理者:做用戶名判斷
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public abstract class AbstractHakimHandler {

    private AbstractHakimHandler next;

    public AbstractHakimHandler getNext() {
        return next;
    }

    public void setNext(AbstractHakimHandler next) {
        this.next = next;
    }

    public abstract String hakim(String name);
}


2、具體處理者

  •   package com.zixieqing.handler;
    
      import com.zixieqing.UsernameEnum;
      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 class OneHakimHandler extends AbstractHakimHandler{
    
          private Logger logger = LoggerFactory.getLogger(OneHakimHandler.class);
    
          @Override
          public String hakim(String name) {
              logger.info("進入OneHakimHandler處理器");
    
              // 如果當前處理者能處理該請求,則進行處理,回傳結果
              if (UsernameEnum.ZIXIEQING.toString().equals(name.trim().toUpperCase())) {
                  return "歡迎:" + name + " 進入系統";
              }
    
              // 如果當前處理者不能處理改請求,則丟給后繼者
              if (null != getNext())
                  return getNext().hakim(name);
    
              return "沒有處理者能夠處理該請求";
          }
      }
    
    
  •   package com.zixieqing.handler;
    
      import com.zixieqing.UsernameEnum;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
    
      /**
       * <p>@description  : 該類功能  第二個處理者
       * </p>
       * <p>@package      : com.zixieqing.handler</p>
       * <p>@author       : ZiXieqing</p>
       * <p>@version      : V1.0.0</p>
       */
    
      public class TwoHakimHandler extends AbstractHakimHandler{
    
          private Logger logger = LoggerFactory.getLogger(OneHakimHandler.class);
    
          @Override
          public String hakim(String name) {
              logger.info("進入TwoHakimHandler處理器");
    
              if (UsernameEnum.ZIMINGXUAN.toString().equals(name.trim().toUpperCase())) {
                  logger.info("正在進行資料查詢..........");
                  logger.info("查詢到用戶:{}", name);
                  return "歡迎:" + name + " 進入系統";
              }
    
              if (null != getNext())
                  return getNext().hakim(name);
    
              return "沒有處理者能夠處理該請求";
          }
      }
    
    
  • 上述涉及到的列舉類

    •   package com.zixieqing;
      
        /**
         * <p>@description  : 該類功能  用戶名集合
         * </p>
         * <p>@package      : com.zixieqing</p>
         * <p>@author       : ZiXieqing</p>
         * <p>@version      : V1.0.0</p>
         */
      
        public enum UsernameEnum {
      
            /**
             * 紫邪情
             */
            ZIXIEQING,
      
            /**
             * 紫明軒
             */
            ZIMINGXUAN,
      
            ;
      
        }
      
      

3、客戶端

package com.zixieqing;

import com.zixieqing.handler.OneHakimHandler;
import com.zixieqing.handler.TwoHakimHandler;

/**
 * <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) {

        // 創建處理鏈 并 將請求丟給鏈頭的處理者
        OneHakimHandler handler1 = new OneHakimHandler();
        handler1.setNext(new TwoHakimHandler());

        System.out.println(handler1.hakim("LISI"));

        System.out.println("============華麗的分隔符=============");

        System.out.println(handler1.hakim("ZIXIEQING"));

        System.out.println("============華麗的分隔符=============");

        System.out.println(handler1.hakim("zimingxuan"));
    }
}

image-20221102145348961


責任鏈模式的不足

  • 不能保證每個請求一定能被處理,如:上面的“LISI",原因:因為請求沒有明確的接收者,所以一個請求可能一直傳到處理鏈的末端都還未被處理
  • 責任鏈是否合理需要靠客戶端來進行創建,所以增加了客戶端的復雜性,可能出現客戶端中責任鏈的錯誤設定而導致系統出錯
  • 如果客戶端中的責任鏈過長,那么就需要很多個處理物件的參與,所以系統性能會受到影響

* 3.2.2、命令模式

定義:指的是請求以命令的形式包裹在物件中,并傳給呼叫物件,呼叫物件尋找可以處理該命令的合適的物件,并把該命令傳給相應的物件,該物件執行命令

命令模式是將操作請求(行為請求者)和邏輯實作(行為實作者)進行了分離,從而降低耦合、方便擴展

適用場景: 只要認為是命令的地方都可以使用,如:CMD、GUI界面中的按鈕............


命令模式類圖

image-20221103145416809

  • 場景理解:
    • 電視機換臺:1、想要換臺(呼叫者:發起請求);2、按下遙控器的換臺按鈕(將請求包裝成了命令物件);3、電視機進行換臺(接收者:執行請求相應的操作)
    • 去飯店吃飯:1、你要吃飯(呼叫者:你要選單上的某菜,整個選單就是所有可以呼叫的命令);2、跟服務員講你要選單上的什么菜(命令物件:服務員在本子上記下菜名,即:將請求轉成了命令物件);3、服務員將寫有菜名的紙交給廚師(接收者:廚師通過命令物件接收到呼叫者的請求,開始對應的操作:炒菜)

3.2.2.1、簡單邏輯

使用上面去飯店吃飯為例,對這個場景進行拆解,可以得到如下的邏輯圖

  • 服務員:呼叫者
  • 選單:命令
  • 廚師:接收者
  • 邏輯順序是:呼叫者(invoker)——> 命令(command) ——> 接收者(receiver);而我寫代碼的習慣順序是逆向的

image-20221103150425754


1、先決條件

  • 菜組成
package com.zixieqing;

import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.util.List;

/**
 * <p>@description  : 該類功能  菜
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

@Data
@ToString
@Accessors(chain = true)
public class Food {

    /**
     * 菜名
     */
    private String name;

    /**
     * 油:菜油、豬油、地溝油(^_^)......
     */
    private String oil;

    /**
     * 辣椒型別:有線辣椒、小米椒、青椒、朝天椒..........
     */
    private String chili;

    /**
     * 配料:食鹽、味精、八角、糖、醬油、耗油、醋、料酒、姜、蔥、蒜.........
     */
    private List<String> excipients;

    /**
     * 容器:碗、鍋、盤子
     */
    private String container;
}

  • 選單列舉類
package com.zixieqing;

/**
 * <p>@description  : 該類功能  選單列舉類:就是去點菜時需要看有哪些菜的選單
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public enum MenuEnum {

    /**
     * 魚香肉絲
     */
    YU_XIANG_ROU_SI("魚香肉絲", "18"),

    /**
     * 鴛鴦鍋
     */
    YUAN_YANG_GUO("鴛鴦鍋", "50"),

    ;

    private String foodName;

    private String price;

    MenuEnum(String foodName, String price) {
        this.foodName = foodName;
        this.price = price;
    }

    public String getFoodName() {
        return foodName;
    }

    public void setFoodName(String foodName) {
        this.foodName = foodName;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }
}


2、接收者:廚師

package com.zixieqing.command;

import com.zixieqing.Food;

import java.util.List;

/**
 * <p>@description  : 該類功能  廚師
 * </p>
 * <p>@package      : com.zixieqing.command</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface IChef {

    Food cook(List<String> foodList);
}

  • 張師:專做火鍋系列
package com.zixieqing.command.impl;

import com.zixieqing.Food;
import com.zixieqing.MenuEnum;
import com.zixieqing.command.IChef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * <p>@description  : 該類功能  張師:假如他是專門做火鍋系列的
 * </p>
 * <p>@package      : com.zixieqing.command.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ZhangChefImpl implements IChef {

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

    @Override
    public Food cook(List<String> foodList) {
        for (String name : foodList) {
            if (MenuEnum.YUAN_YANG_GUO.getFoodName().equals(name)) {
                logger.info("張師正在準備做:{}", name);

                List<String> excipients = new ArrayList<>();
                excipients.add("食鹽半勺");
                excipients.add("味精少量");
                excipients.add("八角微量");
                excipients.add("糖少許");
                excipients.add("醬油半勺");
                excipients.add("耗油微量");
                excipients.add("蔥姜蒜適量");

                Food food = new Food();
                food.setName(name)
                        .setOil("菜油")
                        .setChili("朝天椒")
                        .setExcipients(excipients)
                        .setContainer("鴛鴦鍋");

                logger.info("張師做好了:{},成品為:{}", name, food);

                return food;
            }
        }

        return null;
    }
}

  • 李師:專做炒菜系列
package com.zixieqing.command.impl;

import com.zixieqing.Food;
import com.zixieqing.MenuEnum;
import com.zixieqing.command.IChef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * <p>@description  : 該類功能  李師:假如他是專門做炒菜系列的
 * </p>
 * <p>@package      : com.zixieqing.command.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class LiChefImpl implements IChef {

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

    @Override
    public Food cook(List<String> foodList) {
        for (String name : foodList) {
            if (MenuEnum.YU_XIANG_ROU_SI.getFoodName().equals(name)) {
                logger.info("李師正在準備做:{}", name);

                List<String> excipients = new ArrayList<>();
                excipients.add("食鹽少量");
                excipients.add("味精少許");
                excipients.add("蔥蒜少許");

                Food food = new Food();
                food.setName(name)
                        .setOil("豬油")
                        .setChili("青椒")
                        .setExcipients(excipients)
                        .setContainer("盤子");

                logger.info("李師做好了:{},成品為:{}", name, food);

                return food;
            }
        }
        return null;
    }
}


3、命令:選單

package com.zixieqing.command;

import java.util.List;

/**
 * <p>@description  : 該類功能  選單:就是服務員在紙上寫的那些菜名,即:命令物件
 * </p>
 * <p>@package      : com.zixieqing.command</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface IMenu {

    void make(List<String> foodList);
}

  • 火鍋型別選單
package com.zixieqing.command.impl;

import com.zixieqing.command.IChef;
import com.zixieqing.command.IMenu;

import java.util.List;

/**
 * <p>@description  : 該類功能  火鍋型別的選單
 * </p>
 * <p>@package      : com.zixieqing.command.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class HuoGuoMenuImpl implements IMenu {

    private IChef chef;

    public HuoGuoMenuImpl(IChef chef) {
        this.chef = chef;
    }

    @Override
    public void make(List<String> foodList) {
        chef.cook(foodList);
    }
}

  • 炒菜型別選單
package com.zixieqing.command.impl;

import com.zixieqing.command.IChef;
import com.zixieqing.command.IMenu;

import java.util.List;

/**
 * <p>@description  : 該類功能  炒菜型別的選單
 * </p>
 * <p>@package      : com.zixieqing.command.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ChaoCaiMenuImpl implements IMenu {

    private IChef chef;

    public ChaoCaiMenuImpl(IChef chef) {
        this.chef = chef;
    }

    @Override
    public void make(List<String> foodList) {
        chef.cook(foodList);
    }
}


4、呼叫者:服務員

package com.zixieqing.command;

import com.zixieqing.MenuEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * <p>@description  : 該類功能  服務員
 * </p>
 * <p>@package      : com.zixieqing.command</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Waiter {

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

    /**
     * 服務員本子上記的菜名
     */
    private List<String> foodList = new ArrayList<>();
    /**
     * 本店選單
     */
    private MenuEnum[] values = MenuEnum.values();

    private IMenu menu;

    public Waiter(IMenu menu) {
        this.menu = menu;
    }

    /**
     * <p>@description  : 該方法功能 點單
     * </p>
     * <p>@methodName   : order</p>
     * <p>@author: ZiXieqing</p>
     * <p>@version: V1.0.0</p>
     * @param foodName  菜名
     */
    public void order(String foodName) {

        for (MenuEnum value : values) {
            // 判斷客人所說的菜名是否在本店的選單中
            if (foodName.trim().equals(value.getFoodName()))
                this.foodList.add(foodName);
        }
    }

    /**
     * <p>@description  : 該方法功能 下單
     * </p>
     * <p>@methodName   : placeOrder</p>
     * <p>@author: ZiXieqing</p>
     * <p>@version: V1.0.0</p>
     *
     */
    public void placeOrder() {
        menu.make(foodList);

        foodList.clear();
    }
}

5、測驗



package com.zixieqing;

import com.zixieqing.command.Waiter;
import com.zixieqing.command.impl.ChaoCaiMenuImpl;
import com.zixieqing.command.impl.HuoGuoMenuImpl;
import com.zixieqing.command.impl.LiChefImpl;
import com.zixieqing.command.impl.ZhangChefImpl;

/**
 * <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) {

        Waiter waiter = new Waiter(new HuoGuoMenuImpl(new ZhangChefImpl()));

        // 點單
        waiter.order("鴛鴦鍋");

        // 下單
        waiter.placeOrder();

        System.out.println("==========華麗的分隔符==========");

        Waiter newWaiter = new Waiter(new ChaoCaiMenuImpl(new LiChefImpl()));

        // 點單
        newWaiter.order("魚香肉絲");

        // 下單
        newWaiter.placeOrder();
    }
}


  • 結果
11:43:54.317 [main] INFO  c.z.command.impl.ZhangChefImpl - 張師正在準備做:鴛鴦鍋
11:43:54.320 [main] INFO  c.z.command.impl.ZhangChefImpl - 張師做好了:鴛鴦鍋,成品為:Food(name=鴛鴦鍋, oil=菜油, chili=朝天椒, excipients=[食鹽半勺, 味精少量, 八角微量, 糖少許, 醬油半勺, 耗油微量, 蔥姜蒜適量], container=鴛鴦鍋)
==========華麗的分隔符==========
11:43:54.320 [main] INFO  c.zixieqing.command.impl.LiChefImpl - 李師正在準備做:魚香肉絲
11:43:54.320 [main] INFO  c.zixieqing.command.impl.LiChefImpl - 李師做好了:魚香肉絲,成品為:Food(name=魚香肉絲, oil=豬油, chili=青椒, excipients=[食鹽少量, 味精少許, 蔥蒜少許], container=盤子)


命令模式的優點:

  • 將發出命令的責任和執行命令的責任分隔開(請求者 / 呼叫者 和 執行者 / 接收者沒有必然聯系),請求的一方不必知道接收請求的一方的介面,更不必知道請求如何被接收、操作是否被執行、何時被執行,以及是怎么被執行的,所以降低了請求發出者和請求執行者之間的耦合,當然也方便擴展(Command命令采用的是介面+實作類 或是 抽象類+實作類)

命令模式的缺點:

  • 和簡單工廠模式差不多,要是命令Command很多的話,那么實作類就會變多,造成類臃腫,比較繁瑣

* 3.2.3、迭代器模式

定義:迭代、迭代,就是遍歷唄,所以指的就是:提供一種順序訪問集合容器 / 復雜物件中各個元素的方法,又無須暴露集合容器的內部表示

本質:就是將一個集合容器的遍歷方法抽取出來,然后單獨弄到一個迭代器物件中,從而:做到讓我們以相同的方式,可以遍歷不同資料結構的集合容器(這也是這個模式的應用場景)

典型例子:List、Set系列的集合,他們都繼承了Iterable介面,都有Iterator型別的iterator()方法,而hasNext()next()方法就在Iterator介面中

image-20221104101347523


迭代器模式的角色

  • 抽象迭代器(Iterator):負責定義訪問和遍歷元素的介面,通常包含 hasNext()next() 等方法
  • 具體迭代器(Concretelterator):實作抽象迭代器介面中所定義的方法,完成對聚合物件的遍歷,記錄遍歷的當前位置
  • 抽象容器(Aggregate):負責定義創建具體迭代器的介面、以及所謂的對集合容器 / 復雜物件的增刪改這些方法
  • 具體容器(ConcreteAggregate):抽象容器的子類,負責創建具體迭代器 / 回傳迭代器實體

迭代器模式的邏輯

  • 從上面那個list的構成其實也知道了

image-20221104104636281


3.2.3.1、簡單邏輯

  • 注:下面這個邏輯沒多大意思,就意思意思而已,只是為了理解邏輯,因為搞的就是套娃

1、抽象迭代器

package com.zixieqing.o1simple;

/**
 * <p>@description  : 該類功能  抽象迭代器
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface Iterator<E> {

    boolean hasNext();

    E next();
}


2、具體迭代器

package com.zixieqing.o1simple.impl;

import com.zixieqing.o1simple.Iterator;

import java.util.List;

/**
 * <p>@description  : 該類功能  具體迭代器
 * </p>
 * <p>@package      : com.zixieqing.o1simple.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class IteratorImpl<E> implements Iterator<E> {

    /**
     * 下一個元素的索引
     */
    private int cursor;

    /**
     * 最后一個元素的索引,如果沒有就回傳-1
     */
    private int lastRet = -1;

    private List<E> list;

    public IteratorImpl(List<E> list) {
        this.list = list;
    }

    @Override
    public boolean hasNext() {
        return this.cursor < list.size();
    }

    @Override
    public E next() {
        return this.list.get(cursor++);
    }
}


3、抽象容器

package com.zixieqing.o1simple;

/**
 * <p>@description  : 該類功能  抽象容器
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface ICollection<E> {

    Iterator<E> iterator();

    boolean add(E element);
}


4、具體容器

package com.zixieqing.o1simple;

import com.zixieqing.o1simple.impl.IteratorImpl;

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

/**
 * <p>@description  : 該類功能  具體容器
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class NewList<E> implements ICollection<E>{

    private List<E> list = new ArrayList<>();

    @Override
    public Iterator<E> iterator() {
        return new IteratorImpl<>(list);
    }

    @Override
    public boolean add(E element) {
        return list.add(element);
    }
}


5、測驗

package com.zixieqing;

import com.zixieqing.o1simple.Iterator;
import com.zixieqing.o1simple.NewList;

/**
 * <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) {
        NewList<Integer> newList = new NewList<>();
        newList.add(1);
        newList.add(2);
        newList.add(3);
        newList.add(4);
        newList.add(5);

        Iterator<Integer> iterator = newList.iterator();
        while (iterator.hasNext())
            System.out.println(iterator.next());
    }
}


迭代器模式的優點:

  • 在不需要暴露聚合物件的內部結構的情況下,可以讓我們以相同的方式遍歷不同資料結構(鏈表、陣列、樹.......)的聚合物件 / 集合容器的各個元素

迭代器模式的缺點:

  • 它的缺點和工廠方法模式的缺點是類似的,因為迭代器模式是將容器存盤資料和遍歷資料進行了分離,所以就會造成:新增聚合物件就可能需要新增迭代器類(主要為具體迭代器)

使用場景:

  • 想要遍歷不同的聚合結構,這時提供一個統一的介面(迭代器)
  • 想要為聚合物件提供多種遍歷方式

* 3.2.4、中介者模式

定義:我拿你的錢,辦你的事兒;所謂的中介者就是一個和事佬

處理的問題:物件之間存在的依賴關系(多個類相互耦合,形成了網狀結構),即:一個類的方法中用到了另一個的物件(可以是把A類物件當做B類方法的引數;可以是B類方法中new了A類的實體;也可以是B類中的屬性參考了A類物件[但:沒有new,即:聚合],然后在B類方法中new出實體,如果有很多個類之間存在依賴關系,那么就會造成只要動一個類,則很多類都要進行整改,這就是所謂的牽一發而動全身,典型例子就是如下的關系:

image-20221104170153630

中介者模式的角色

  • 抽象中介者(Mediator):定義統一的介面,用于各同事角色之間的通信
  • 具體中介者(ConcreteMediator):通過協調各同事物件實作協作行為,因此它必須依賴于各個同事角色
  • 同事類(Colleague):每一個同事角色都知道中介者角色,而且與其他的同事角色通信的時候,一定要通過中介者角色協作,每個同事類的行為分為兩種:
    • 一種是同事本身的行為,比如改變物件本身的狀態,處理自己的行為等,這種行為叫做自發行為,與其他的同事類或中介者沒有任何的依賴
    • 第二種是必須依賴中介者才能完成的行為,叫做依賴方法
    • 當然:根據需要也可以把同事類進行抽取,進而變成:抽象同事類+具體同事類

3.2.4.1、簡單邏輯

情景:男女分手+中間調和者,類圖邏輯如下:

image-20221107194014877


1、抽象中介者 / 抽象協調者

package com.zixieqing.o1simple;

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

/**
 * <p>@description  : 該類功能  抽象協調者
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public abstract class Coordinator {

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

    protected Man man;

    protected Woman woman;

    public Coordinator() {
        man = new Man(this);
        woman = new Woman(this);
    }

    /**
     * 中介者 / 協調者的核心方法:處理同事角色關系
     *
     * @param type  事件型別 1、傳話;2、退還物品
     * @param thing 發生對應型別時要做的事
     */
    public abstract void handler(int type, String thing);

    /**
     * 傳話
     *
     * @param context 傳話內容
     */
    public abstract void message(String context);

    /**
     * 退還物品
     *
     * @param thing 要退還的東西
     */
    public abstract void handover(String thing);
}


2、具體中介者

package com.zixieqing.o1simple;

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

/**
 * <p>@description  : 該類功能  具體協調者
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ConsreteConnrdinator extends Coordinator {

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

    /**
     * 代表吵架
     */
    private static final int QUARREL = 1;

    /**
     * 代表退還物品
     */
    private static final int HANDOVER = 2;

    /**
     * MAG_FLAG 訊息標識 1、給女傳話;2、給男傳話
     * HANDOVER_FLAG 退還物品標識 1、給女退還物品;2、給男退還物品
     */
    public static int MSG_FLAG = 1, HANDOVER_FLAG = 1;

    /**
     * 中介者 / 協調者的核心方法:處理同事角色關系
     * @param type 事件型別 1、傳話;2、退還物品
     * @param thing 發生對應型別時要做的事
     */
    @Override
    public void handler(int type, String thing) {
        switch (type) {
            case QUARREL:
                this.message(thing);
                break;
            case HANDOVER:
                this.handover(thing);
                break;
            default:
                logger.info("貧道愛莫能助,只能擺爛..........");
        }
    }

    /**
     * 傳話
     * @param context 傳話內容
     */
    @Override
    public void message(String context) {
        if (1==MSG_FLAG)
            super.woman.talkToFriend(context);

        if (2==MSG_FLAG)
            super.man.love(context);
    }

    /**
     * 退還物品
     *
     * @param thing 要退還的東西
     */
    @Override
    public void handover(String thing) {
        if (1==HANDOVER_FLAG)
            super.woman.talkToFriend(thing);

        if (2==HANDOVER_FLAG)
            super.man.love(thing);
    }
}


3、同事類:男人

package com.zixieqing.o1simple;

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

import java.util.Scanner;

/**
 * <p>@description  : 該類功能  男人:同事類A
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Man {

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

    /**
     * 聚合協調者
     */
    private Coordinator coordinator;

    /**
     * 姓名
     */
    private String name;

    /**
     * 年齡
     */
    private int age;

    /**
     * 敏感詞
     */
    private String filter = "不聽,不聽,分手";

    public Man(Coordinator coordinator) {
        this.coordinator = coordinator;
    }

    /**
     * 吃
     * @param foodName 食物名
     */
    public void eat(String foodName) {
        logger.info("這個男人今天吃了:{}", foodName);
    }

    /**
     * 喝
     * @param drinkName 飲料名
     */
    public void drink(String drinkName) {
        logger.info("這個男人剛剛喝了:{}", drinkName);
    }

    /**
     * 做飯
     * @param foodName 菜名
     */
    public void cook(String foodName) {
        logger.info("這個男人正在做:{}", foodName);
    }

    /**
     * 談戀愛
     * @param thing 做的是戀愛中的什么事
     */
    public void love(String thing) {
        logger.info("女:{}", thing);
        if (thing.trim().equals(filter)) {
            ConsreteConnrdinator.MSG_FLAG = 1;
            coordinator.handler(1,"你這娘們兒簡直不可理喻,分就分");
        }

        ConsreteConnrdinator.MSG_FLAG = 1;
        Scanner input = new Scanner(System.in);
        coordinator.handler(1,input.next());
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


4、同事類:女人

package com.zixieqing.o1simple;

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

import java.util.Scanner;

/**
 * <p>@description  : 該類功能  女人:同事類B
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Woman {

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

    private Coordinator coordinator;

    private String name;

    private int age;

    /**
     * 火藥桶
     */
    private String gunpowder = "你這娘們兒簡直不可理喻,分就分";

    public Woman(Coordinator coordinator) {
        this.coordinator = coordinator;
    }

    /**
     * 逛街購物
     */
    public void shopping() {
        logger.info("這個仙女今天去哪里逛街,又買了xxxxxxxxxxx");
    }

    /**
     * 耍朋友
     */
    public void talkToFriend(String context) {
        logger.info("男:{}",context);
        // 如果觸碰火藥桶,那就退還物品
        if (context.trim().equals(gunpowder)) {
            logger.info("分就分,把他的破東西拿回去..........");
            ConsreteConnrdinator.HANDOVER_FLAG = 2;
            ConsreteConnrdinator.MSG_FLAG = 2;
            Scanner input = new Scanner(System.in);
            String goods = input.next();
            // 退還物品
            coordinator.handler(2,goods);
        }
        // 否則就是繼續交流
        ConsreteConnrdinator.MSG_FLAG = 2;
        Scanner input = new Scanner(System.in);
        coordinator.handler(1,input.next());
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


5、測驗

package com.zixieqing;

import com.zixieqing.o1simple.ConsreteConnrdinator;
import com.zixieqing.o1simple.Coordinator;
import com.zixieqing.o1simple.Man;
import com.zixieqing.o1simple.Woman;

/**
 * <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) {

        Coordinator connrdinator = new ConsreteConnrdinator();

        Woman woman = new Woman(connrdinator);
        // 同事類的獨有方法
        woman.shopping();

        Man man = new Man(connrdinator);
        // 同事類獨有方法
        man.eat("閉門羹");
        man.drink("寂寞");
        man.cook("空氣");

        // 提供給外部,進行協調的方法
        woman.talkToFriend("");
        // 提供給外部,進行協調的方法
        man.love("");
    }
}

自娛自樂的聊天室就來了

image-20221107191959858


當然:中介者被稱為協調者,那也得協調協調,做點事情嘛,不能只當個傳話筒,這個就根據開發場景自由發揮了

同時:上面的同事類是可以抽離成一個介面,變成抽象同事類+實作類


分析一丟丟中介者模式:

  • 1、每個同事類物件只與中介者產生依賴關系,同事類彼此之間不產生依賴
  • 2、具體協調者中加入了用private修飾的用于處理各同事類關系的方法,從而讓一個物件依賴多個物件的情況移到這個中介者中來進行處理了
  • 3、同事類中都加入了中介者,從而讓各個同事類都具有中介者的特性,這樣做之后:各個同事類就可以只負責自己的行為(獨有方法),而不需要自己負責的 / 需要協調的就丟給中介者進行處理(這樣做也取消了各個同事類之間產生依賴或關聯關系)
    • 同事類只負責自己的行為,不需要負責的就丟給中介者處理,這樣好處就是:各個同事類之間可以不用知道彼此的存在,它只需要和中介者打交道即可
    • 同事類只負責自己的行為,需要協調的就丟給中介者進行處理,這樣可能會造成這個模式濫用的情況(慎用),因為這樣做一不注意就可能導致:同事類之間知道彼此的存在了,即:各同事類之間一不注意就產生關聯或依賴關系,那這模式架構就當白做了(別被我上面弄的男女耍朋友給搞混了,誰跟你說他們知道彼此存在的,我沒說,萬一這里面有故事呢)

image-20221107213655810

當然上面這些都可以說是中介者模式的優點,相應地就有缺點

  • 原本是各個同事類之間產生依賴或關聯關系,現在是變成同事類和中介者之間產生依賴關系,這樣就造成:同事類越多,那么中介者就越腫大(需要聚合的同事類就變多,核心方法[事件方法]中需要處理的邏輯就越復雜),這個缺點也是決定到底要不要用中介者模式的核心,否則就成為亂整,要用這個模式就一定要考慮是否會讓中介者變得腫大的問題

3.2.5、備忘錄模式

定義:備忘錄模式又稱為快照模式,備忘、備忘嘛,指的就是在不破壞封裝性的前提下,獲取到一個物件的內部狀態,并在物件之外記錄或保存這個狀態,在有需要的時候可將該物件恢復到原先保存的狀態,使用備忘錄模式可以對操作物件提供回滾操作,但對資源消耗過大,每操作一次都需要記錄操作前資料

人話:就是為了防丟失、撤銷、恢復等事情的(即:需要記錄一個物件的內部狀態,目的就是為了允許用戶取消不確定或者錯誤的操作,能夠恢復到他原先的狀態,有 "后悔藥" 可吃,這也是這個模式的適用場景

注意上面的官方話定義:

? 獲取物件內部狀態(內部還有什么狀態,就屬性值唄),并在物件之外保存這個狀態(即:在要保存的物件中會有一個save方法用來把其當前屬性值保存到另一個物件[即:備忘錄物件]中

? 在有需要時可將物件恢復到原先保存的狀態:也就是說在要保存物件中還會有一個提供和撤銷 / 回滾類似的方法,從而讓物件回到上一步的狀態


備忘錄模式的角色

  • 備忘錄(Memento):負責存盤發起人的內部狀態,在需要的時候提供這些內部狀態給發起人

  • 發起人(Originator):需要備忘的物件,記錄當前時刻的內部狀態資訊,提供將當前時刻的內部狀態狀態資訊保存到備忘錄中,并將備忘錄物件回傳 以及 從備忘錄中記錄的狀態資訊恢復此物件狀態的方法

  • 負責人(Caretaker):用于管理備忘錄,提供保存與獲取備忘錄物件的功能(getter、setter),但其不能直接對備忘錄中的內容進行操作(要操作備忘錄中的內容只能找備忘錄本身才可以)


備忘錄模式邏輯關系草圖

image-20221110105013413


3.4.5.1、簡單邏輯

1、備忘錄:存盤發起人的內部狀態資訊,并在需要時能將資料交出去

package com.zixieqing.o1simple;

/**
 * <p>@description  : 該類功能  備忘錄:存盤發起人的內部狀態資訊,并在需要時能夠將存盤的資料交出去
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Memento {
    private Object state;

    public Memento(Object state) {
        this.state = state;
    }

    public Object getState() {
        return state;
    }
}


2、負責人:管理備忘錄,提供獲取、保存備忘錄物件的方法,但不可對備忘錄中的內容進行操作

package com.zixieqing.o1simple;

/**
 * <p>@description  : 該類功能  負責人:管理備忘錄,提供獲取、保存備忘錄物件的方法,但不可以對備忘錄中的內容進行操作
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Caretaker {

    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}


3、發起人:要進行備忘的物件,記錄當前時刻的內部屬性資訊到備忘錄中 并回傳備忘錄物件,同時在需要時可從備忘錄中恢復當前物件的內部資訊

package com.zixieqing.o1simple;

/**
 * <p>@description  : 該類功能  發起人:需要進行備忘的物件,記錄當前時刻的內部屬性資訊到備忘錄中,并回傳備忘錄物件
 * 并在需要時可從備忘錄中記錄的狀態資訊恢復資料
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Orginal {

    private Object field;

    /**
     * 保存當前時刻的狀態資訊到備忘錄中,并回傳備忘錄物件
     */
    public Memento saveToMemento() {
        return new Memento(this.field);
    }

    /**
     * 從備忘錄中恢復當前物件的內部狀態資訊
     * @param memento 備忘錄
     */
    public void rollbackFromMemento(Memento memento) {
        this.field = memento.getState();
    }


    @Override
    public String toString() {
        return "Orginal{" +
                "field=" + field +
                '}';
    }

    public Object getField() {
        return field;
    }

    public void setField(Object field) {
        this.field = field;
    }
}


4、測驗

package com.zixieqing;

import com.zixieqing.o1simple.Memento;
import com.zixieqing.o1simple.Orginal;

/**
 * <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) {

        // 要進行保存的物件
        Orginal orginal = new Orginal();
        orginal.setField("稀里嘩啦保存了一堆的東西");

        // 將物件的當前時刻的內部資訊保存到備忘錄中
        Memento memento = orginal.saveToMemento();

        System.out.println(orginal);

        // 模擬
        orginal.setField("噼里啪啦又是一頓操作,保存了資料");
        System.out.println(orginal);

        System.out.println("=============蕪湖~斷電咯================");
        System.out.println(",,,,,,,,,,,,,,,,,,,,");
        System.out.println("=============耶吼~電力恢復啦=============");

        // 從記錄的備忘錄中恢復物件內部狀態
        orginal.rollbackFromMemento(memento);

        System.out.println("=============物件恢復之后的狀態===========");
        System.out.println(orginal);
    }
}

image-20221110113545017


備忘錄模式的缺點:

  • 如果要保存的物件的內部狀態資訊(屬性)很多的話,那記憶體資源消耗是很大的,所以有時可以使用單例、命令模式這些來進行資料保存,當然更可以通過第三方中間件保存

* 3.2.6、觀察者模式

定義:指的是多個觀察者同時監聽一個主題物件,每當主題物件發生變化時,都會通知監聽 / 依賴它的所有觀察者,從而讓觀察者自動更新自身相關的資料,因此主題物件和觀察者之間就是一對多的依賴關系(這定義就是它的適用場景)

核心:將觀察者和被觀察者進行了解耦,并通過類似于訊息發送的機制讓兩者進行聯動,從而做到被觀察者發生變化時,監聽 / 依賴于它的所有觀察者都會得到通知并做出回應(更新自身資料)


觀察者模式的角色(可以當做發布訂閱模式來理解,但:這二者不能完全等價,發布訂閱模式在觀察者和被觀察者之間再套了一層[套的這一層就是發布訂閱中的事件機制 Event Channel],自行面向百度編程):

  • 抽象主題(Subject):被觀察的物件,是介面或抽象類,也就是發布者,包含了:增加、洗掉、通知觀察者物件的方法
  • 具體主題(ConcreteSubject):具體被觀察者,是抽象主題的子類,當其內部狀態發生改變時會通知所有依賴與它的觀察者
  • 抽象觀察者(Observer):觀察者模型,也就是訂閱者,定義了回應通知的更新方法
  • 具體觀察者(ConcreteObserver):是抽象觀察者的子類,在收到主題物件狀態發生改變的通知后,會立刻對其進行回應,并更新自身的相關資料

觀察者模式邏輯草圖

image-20221111143631182


3.2.6.1、簡單邏輯

場景:博主與粉絲,博主發文章,粉絲收到通知

1、先決條件:文章類

package com.zixieqing.o1simple;

import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.util.Date;

/**
 * <p>@description  : 該類功能  文章
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

@Data
@ToString
@Accessors(chain = true)
public class Article {

    /**
     * 文章標題
     */
    private String title;

    /**
     * 文章內容
     */
    private String context;

    /**
     * 發布人編號
     */
    private Long publisherId;

    /**
     * 發布時間
     */
    private Date createTime;

    /**
     * 文章所屬標簽
     */
    private String articleLabel;

}


2、博主介面:抽象主題 即發布者,包含增加、洗掉、通知觀察者物件等方法

package com.zixieqing.o1simple;

/**
 * <p>@description  : 該類功能  博主介面:抽象主題 包含增加、洗掉、通知觀察者物件的方法
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface IBlogger {

    /**
     * 將粉絲添加到通知串列中,能夠讓粉絲接受到通知
     * @param fan 粉絲
     * @return true / false
     */
    boolean add(IFan fan);

    /**
     * 將粉絲拉入黑名單,不讓其接收到通知
     * @param fan 粉絲
     * @return true / false
     */
    boolean remove(IFan fan);

    /**
     * 通知觀察者
     * @param article 訊息體
     */
    void notice(Article article);
}

  • 具體博主:具體主題
package com.zixieqing.o1simple.impl;

import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IBlogger;
import com.zixieqing.o1simple.IFan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * <p>@description  : 該類功能  一個叫紫邪情的厚臉皮博主
 * </p>
 * <p>@package      : com.zixieqing.o1simple.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Zixieqing implements IBlogger {

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

    /**
     * 粉絲串列:一對多
     */
    private List<IFan> fanList = new ArrayList<>();

    @Override
    public boolean add(IFan fan) {
        if (!fanList.contains(fan))
            return fanList.add(fan);

        return false;
    }

    @Override
    public boolean remove(IFan fan) {
        if (!add(fan))
            return fanList.remove(fan);

        return false;
    }

    @Override
    public void notice(Article article) {
        for (IFan fan : fanList) {
            fan.response(article);
        }
    }
}

  • 注:還是補上吧,上面通知的方法,若是再加上"滿足什么條件就觸發通知方法",把這句話再進行抽取一下搞到另外合適的地方去,就變成了觸發機制,即:被觀察者滿足什么條件觸發通知,從而讓所依賴的所有觀察者都收到通知,并更新自身相關資料,這里抽取出來的觸發機制就是事件通道Event channel,抽取出來之后,這個觀察者模式就可以變成發布-訂閱模式了

3、粉絲介面:即訂閱者 得到通知之后進行回應并改變自身和主題相關的資料

package com.zixieqing.o1simple;

/**
 * <p>@description  : 該類功能  粉絲介面:抽象觀察者 得到通知之后,更新自身相關資料 / 做自己要做的事情
 * </p>
 * <p>@package      : com.zixieqing.o1simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public abstract class IFan {

    public abstract boolean response(Article article);
}

  • 具體粉絲Q
package com.zixieqing.o1simple.impl;

import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IFan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * <p>@description  : 該類功能  一個叫Q的粉絲
 * </p>
 * <p>@package      : com.zixieqing.o1simple.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class QFan extends IFan {

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

    /**
     * 粉絲感興趣的博文串列
     */
    private static List<String> interestList = new ArrayList<>();
    static {
        interestList.add("設計模式");
        interestList.add("愛碼有道");
        interestList.add("Java");
    }

    @Override
    public boolean response(Article article) {
        for (String interest : interestList) {
            if (article.getTitle().equals(interest)) {
                logger.info("粉絲:{},接收了通知:{},并開始去做一系列和自身資料相關的事情",
                        this.getClass().getSimpleName(),
                        article);
                return true;
            }
        }
        return false;
    }
}

  • 具體粉絲X
package com.zixieqing.o1simple.impl;

import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IFan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * <p>@description  : 該類功能  一個叫X的粉絲
 * </p>
 * <p>@package      : com.zixieqing.o1simple.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class XFan extends IFan {

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

    /**
     * 粉絲感興趣的博文串列
     */
    private static List<String> interestList = new ArrayList<>();
    static {
        interestList.add("訊息中間件");
        interestList.add("愛碼有道");
        interestList.add("分布式事務");
        interestList.add("分布式事務");
    }

    @Override
    public boolean response(Article article) {
        for (String interest : interestList) {
            if (article.getTitle().equals(interest)) {
                logger.info("粉絲:{},接收了通知:{},并開始去做一系列和自身資料相關的事情",
                        this.getClass().getSimpleName(),
                        article);
                return true;
            }
        }
        return false;
    }
}


4、測驗

package com.zixieqing;

import com.zixieqing.o1simple.Article;
import com.zixieqing.o1simple.IBlogger;
import com.zixieqing.o1simple.impl.QFan;
import com.zixieqing.o1simple.impl.XFan;
import com.zixieqing.o1simple.impl.Zixieqing;

import java.util.Date;

/**
 * <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) {
        IBlogger blogger = new Zixieqing();

        // 讓指定粉絲能收到通知
        QFan qFan = new QFan();
        XFan xFan = new XFan();
        blogger.add(qFan);
        blogger.add(xFan);

        // 博主開始寫文章
        Article article = new Article();
        String context = "設計莫斯一共有23種,分為創建型、行為型、結構型,這三類其實就是不同層別的架構," +
                "如:創建型就是專門用于new實體的架構;即:物件,而行為型體現在行為上,也就是所謂的方法," +
                "變來變去其實本質就是對類中方法的架構" +
                "學完全部設計模式之后,感受就會起來了,然后只需要知曉每個設計模式解決的場景是什么," +
                "最后開發中遇到對應的設計模式場景就套用,然后思路就出來,也熟練了";
        article.setTitle("設計模式")
                .setPublisherId(System.nanoTime())
                .setContext(context)
                .setArticleLabel("設計")
                .setCreateTime(new Date());

        // 將文章推給粉絲
        blogger.notice(article);


        System.out.println("=============華麗的分隔符===============");

        // blogger.remove(xFan);
        article.setTitle("愛碼有道")
                .setPublisherId(System.nanoTime())
                .setContext("正式開發時還是遵循開發規范比較好")
                .setArticleLabel("代碼整潔")
                .setCreateTime(new Date());

        // 將文章推給粉絲
        blogger.notice(article);
    }
}

image-20221111180101130


3.2.6.2、分析觀察者模式

觀察者模式的優點

  • 被觀察者(Subject主題) 和 觀察者(Observer)是松耦合的,包含關系嘛,同時也是面向介面編程的(即:符合依賴倒置原則)

image-20221114101537419

  • 將表示層(觀察者) 和 資料邏輯層(被觀察者)進行了分離,只要被觀察者的自身狀態發生改變就可以讓觀察者相應的資料也發生改變,即:制定了一套觸發機制,從而做到資料邏輯層的資料變化可以相應到多個表示層上
  • 使用了一對多的機制,被觀察者的狀態發生改變,只有滿足同一套觸發機制的觀察者才能接收到通知,這樣的機制可以做很多事,比如:興趣分發、事件注冊等機制

觀察者模式的缺點

  • 因為觀察者是被放在被觀察者的一個容器中,那眾多的觀察者在這個容器中就產生了類似于鏈化的情況,而只要是容器那就逃不開遍歷的事(像鏈表一樣,或者說線性關系更恰當),這就有缺點了,若是觀察者很多的話,那通知所有的觀察者就需要耗費時間了

image-20221114103110704

  • 基于前面的線性關系的情況,還有造成一個問題:只要一個觀察者沒處理好,導致出現卡死的情況,那么后續的觀察者也別通知了(換言之:后續觀察者可能出現接收不到通知的情況),相當于后續觀察者死翹翹了

* 3.2.7、狀態模式

定義:一個物件在其內部狀態改變時改變它的行為

解決的問題:解決復雜物件的狀態轉換以及不同狀態下行為的封裝問題,一句話:物件在不同狀態下有不同行為時就可以使用狀態模式

核心:將一個物件的狀態從該物件中分離出來,封裝到專門的狀態類中(抽象狀態+實作類),從而使得物件狀態可以靈活變化

本質:在代碼中對物件屬性進行的大量if-else判斷進行抽離(如代碼:if(p.getxxxx().equals("yyyy")){},狀態模式解決的差不多就是這種代碼),在物件內部提供外部呼叫的方法,從而讓其自身根據狀態的不同去走對應的邏輯(狀態實作類中的邏輯),如:一個界面登錄是一種樣子,未登錄又是另一種樣子,狀態不同行為不同,這也是這個模式的適用場景,即:代碼中包含大量與物件狀態有關的條件陳述句時(if-elseswitch)就可以使用狀態模式進行改造


狀態模式的角色

  • 背景關系(Context):狀態所屬者,維護具體狀態類的實體,這個實體存盤其當前狀態
  • 抽象狀態(State):封裝與Context的一個特定狀態相關的行為操作(這里面可以定義多個方法的,這是和命令模式的一個小區別)
  • 具體狀態(ConcreteState):每一個具體的狀態類實作一個與Context的一個狀態相關的行為,在需要時也可以進行狀態切換

狀態模式的邏輯草圖

image-20221121100526039


3.2.7.1、簡單邏輯

場景:去ATM中取款


1、抽象狀態:ATM狀態對應的行為封裝

package com.zixieqing;

/**
 * <p>@description  : 該類功能  抽象狀態:ATM狀態對應的行為
 * 封裝與Context狀態所屬者的狀態 / 屬性相關的行為
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface ATMState {
    /**
     * 插卡
     */
    void insertCard();

    /**
     * 提交密碼
     */
    void submitPwd();

    /**
     * 取款
     */
    void getCash();

    /**
     * 查詢余額
     */
    void queryBalance();

    /**
     * 退卡
     */
    void checkOut();
}


2、具體狀態

  • 準備狀態
package com.zixieqing.impl;

import com.zixieqing.ATMContext;
import com.zixieqing.ATMState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 該類功能  具體狀態:準備狀態
 * 具體狀態:1、實作Context的一個狀態的相關行為邏輯
 *         2、在需要時進行狀態切換
 *              狀態切換可以是本狀態中的不同行為切換,也可以是切換到不同具體狀態者
 * </p>
 * <p>@package      : com.zixieqing.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ReadyState implements ATMState {

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

    /**
     * 依賴:保留狀態所屬者Context的參考,作用:方便對其進行操作
     *      介面ATMState 是對狀態進行了抽象
     *      實作類(ReadyState或其他實作類)是具體的狀態
     * 這些具體狀態者(實作類)都只是狀態本身而已,而狀態所屬者是Context,最后狀態是要回到所屬者身邊的
     * 如果這個具體狀態者中沒有 對 狀態所屬者Context進行相應操作,那:具體狀態者 和 狀態所屬者就是割裂 / 分開的
     */
    private ATMContext atmContext;

    public ReadyState(ATMContext atmContext) {
        this.atmContext = atmContext;
    }

    @Override
    public void insertCard() {
        logger.info("完成插卡");
    }

    @Override
    public void submitPwd() {
        logger.info("正在進行密碼校驗............");
        if ("123456".equals(atmContext.getPwd())) {
            logger.info("密碼校驗成功");
        }else {
            logger.info("密碼不正確");
            // 狀態切換:退卡 / 重新進入準備狀態..........
            checkOut();
        }
    }

    @Override
    public void getCash() {
        // 如果ATM中沒錢了,那就不能再提供服務
        if (atmContext.getATMBalance() == 0) {
            logger.info("無法使用服務,請去另外機子進行業務辦理");

            // 彈卡
            checkOut();

            /*
                狀態切換
                注意這里:這里沒有用new,而是用的atmContext的setter,這也是前面說保留atmContext參考的好處之一
                好處:降低耦合(迪米特原則:最少知道原則),獲取彈性
             */
            atmContext.setCurrState(atmContext.getNoServiceState());
        }else {
            if (atmContext.getMoney() <= atmContext.getATMBalance() && atmContext.getMoney() <= atmContext.getBalance()) {
                // 出鈔、減少用戶的賬戶余額
                logger.info("出鈔¥:{}",atmContext.getMoney());
                atmContext.setBalance(atmContext.getBalance() - atmContext.getMoney());

                // 減少ATM中的鈔票金額
                atmContext.setATMBalance(atmContext.getATMBalance() - atmContext.getMoney());

                // 列印發票、回到準備狀態...........

                // 彈卡
                checkOut();
            }else {
                logger.info("余額不足");
                checkOut();
            }
        }
    }

    @Override
    public void queryBalance() {
        logger.info("賬戶余額為:{}", atmContext.getBalance());
    }

    @Override
    public void checkOut() {
        logger.info("退卡成功");
    }
}

  • 無法提供服務狀態
package com.zixieqing.impl;

import com.zixieqing.ATMContext;
import com.zixieqing.ATMState;
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 NoServiceState implements ATMState {

    private ATMContext atmContext;

    public NoServiceState(ATMContext atmContext) {
        this.atmContext = atmContext;
    }

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

    @Override
    public void insertCard() {
        logger.info("服務停止,請去其他取款機進行業務辦理");
    }

    @Override
    public void submitPwd() {
        logger.info("服務停止,請去其他取款機進行業務辦理");
    }

    @Override
    public void getCash() {
        logger.info("服務停止,請去其他取款機進行業務辦理");
    }

    @Override
    public void queryBalance() {
        logger.info("服務停止,請去其他取款機進行業務辦理");
    }

    @Override
    public void checkOut() {
        logger.info("服務停止,請去其他取款機進行業務辦理");
    }
}


3、核心:Context背景關系物件

package com.zixieqing;

import com.zixieqing.impl.NoServiceState;
import com.zixieqing.impl.ReadyState;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * <p>@description  : 該類功能  Context背景關系物件(這個Context是核心)
 * 狀態所屬者:1、維護具體狀態者實體;
 *           2、也可以有它自己的特有狀態;
 *           3、將狀態改變時其對應的行為交給對應的狀態物件,即:狀態改變時讓其走對應的邏輯(供外部呼叫的)
 * 上面2、3根據情況決定可有可無
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

@Data
@ToString
@Accessors(chain = true)
public class ATMContext {

    // 假設此Context物件有如下的獨有狀態(實質是:老衲待會兒測驗需要,一次性寫在這里了)
    /**
     * 密碼
     */
    private String pwd;

    /**
     * 用戶取款金額
     */
    private int money;

    /**
     * 用戶的賬戶余額(假設為int型別)
     */
    private int balance;

    /**
     * ATM機中的鈔票余額
     */
    private int ATMBalance;

    // 下面這些就是這個Context所維護的具體狀態者實體
    /**
     * 當前狀態
     */
    private ATMState currState;
    /**
     * 準備狀態
     */
    private ATMState readyState;
    /**
     * 無服務狀態
     */
    private ATMState noServiceState;

    /**
     * 初始化所有狀態
     * @param pwd 密碼
     * @param money 用戶取款金額
     * @param balance 用戶的賬戶余額
     * @param ATMBalance ATM機中的鈔票余額
     */
    public ATMContext(String pwd, int money, int balance, int ATMBalance) throws Exception {
        this.pwd = pwd;
        this.money = money;
        this.balance = balance;
        this.ATMBalance = ATMBalance;

        this.readyState = new ReadyState(this);
        this.noServiceState = new NoServiceState(this);
        if (this.getATMBalance() > 0) {
            this.currState = readyState;
        } else if (this.getATMBalance() < 0) {
            this.currState = noServiceState;
        }else {
            throw new Exception();
        }
    }

    // 下面這些就是這個Context在其狀態改變時將具體行為委托給具體狀態物件(可以直接供外部呼叫)

    /**
     * 轉換到插卡行為
     */
    public void insertCard() {
        this.currState.insertCard();
    }

    /**
     * 提交密碼
     */
    public void submitPwd() {
        this.currState.submitPwd();
    }

    /**
     * 取款
     */
    public void getCash() {
        this.currState.getCash();
    }

    /**
     * 查詢余額
     */
    public void queryBalance() {
        this.currState.queryBalance();
    }

    public void checkOut() {
        this.currState.checkOut();
    }
}


4、測驗

package com.zixieqing;

/**
 * <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) throws Exception {

        ATMContext atmContext = new ATMContext("123456", 300, 200000, 100000);

        System.out.println("===============初始狀態===================");
        System.out.println(atmContext);

        atmContext.insertCard();
        atmContext.submitPwd();
        atmContext.getCash();
        atmContext.queryBalance();
        atmContext.checkOut();

        System.out.println("===============結束狀態===================");
        System.out.println(atmContext);
    }
}

  • 結果
===============初始狀態===================
ATMContext(pwd=123456, money=300, balance=200000, ATMBalance=100000, currState=com.zixieqing.impl.ReadyState@5d6f64b1, readyState=com.zixieqing.impl.ReadyState@5d6f64b1, noServiceState=com.zixieqing.impl.NoServiceState@32a1bec0)
17:50:01.410 [main] INFO  com.zixieqing.impl.ReadyState - 完成插卡
17:50:01.413 [main] INFO  com.zixieqing.impl.ReadyState - 正在進行密碼校驗............
17:50:01.413 [main] INFO  com.zixieqing.impl.ReadyState - 密碼校驗成功
17:50:01.413 [main] INFO  com.zixieqing.impl.ReadyState - 出鈔¥:300
17:50:01.413 [main] INFO  com.zixieqing.impl.ReadyState - 退卡成功
17:50:01.413 [main] INFO  com.zixieqing.impl.ReadyState - 賬戶余額為:199700
17:50:01.413 [main] INFO  com.zixieqing.impl.ReadyState - 退卡成功
===============結束狀態===================
ATMContext(pwd=123456, money=300, balance=199700, ATMBalance=99700, currState=com.zixieqing.impl.ReadyState@5d6f64b1, readyState=com.zixieqing.impl.ReadyState@5d6f64b1, noServiceState=com.zixieqing.impl.NoServiceState@32a1bec0)


3.2.7.2、分析狀態模式

狀態模式的優點

  • 消除了使用物件屬性的條件分支陳述句(if-elseswitch),通過Context狀態所屬者中的狀態行為托管,將屬性 / 狀態的邏輯處理遷移到了State子類來進行,降低了相互間的依賴,使代碼結構清晰的同時也易擴展和易維護
  • 顯式化進行狀態轉換:為不同的狀態引入獨立的物件,使得狀態的轉換變得更加明確,而且狀態物件可以保證背景關系不會發生內部狀態不一致的狀況,因為背景關系中只有一個變數來記錄狀態物件,只要為這一個變數賦值就可以了

image-20221117222858243

  • 將所有與某個狀態有關的行為放到一個類中,并且可以方便地增加新的狀態,只需要改變物件狀態即可改變物件的行為

image-20221117222957521


狀態模式的缺點

  • 因為此模式就是讓物件隨著其狀態改變而改變行為,從而讓其本身看起來像改變了一樣,這其實就違背了“開閉原則”,因為若是添加新的具體狀態類,那就需要在Context中維護此具體狀態實體(修改負責狀態轉換的源代碼),否則就無法切換到新增狀態

image-20221117223536846

  • 狀態模式的結構和實作邏輯一般都是很復雜的,如果一不注意搞不好就會導致程式結構和代碼混亂

使用狀態模式的建議:

1、具體狀態類一般不要超過5個左右,不然維護起來也是很麻煩的,因為每一個具體狀態類中不同行為邏輯本身就很復雜,而且不同行為之間會進行切換,甚至會切換到另外的狀態去,例子如下(這還不是復雜的,一般真要用這個模式時復雜程度不算低):

image-20221117224448151


3.2.7.3、狀態模式 VS 命令模式

這兩個模式的思路,或者說結構吧,是有點像的,但還是有區別的,還有一個策略模式,但這個還沒玩呢,后續比較時再加入

狀態模式和命令模式最大的區別是下面這些

  • 首先第一點:狀態模式中在抽象狀態中是可以定義很多方法的,而命令模式的抽象命令中一般都是一個方法

image-20221117224951985


  • 其次第二點:要區分開二者的定義
    • 狀態模式:Context狀態所屬者自己內部維護著狀態,會隨著公共方法(前面示例中哪些供外部呼叫的,如:下圖的)的呼叫而切換狀態,外界不需要知道狀態的真實變化情況(變化情況實質在具體狀態類中,會根據情況進行狀態切換)
      • image-20221117230114447
    • 命令模式:根據請求封裝相應的命令,請求發起者不需要知道真正的命令是什么、該怎么處理(如:電視換臺,只需要按下相應按鈕,這按鈕對應的底層命令是什么、會怎么進行處理,按下按鈕的人不需要知道),只需要去呼叫抽象命令中的execute()介面就行(當然:也不是一定要叫這個介面名

* 3.2.8、策略模式

定義:又名政策模式(Policy),指的是針對一組演算法,將每一個演算法封裝到具有共同介面的獨立的類中,使得它們可以互換(實作同一個介面:里氏替換原則),可以讓演算法隨著呼叫者的變化而變化

解決的問題 / 適用場景:在有多種演算法相似(同類可替代的行為邏輯演算法)的情況下,去掉使用條件分支陳述句(if-elseswitch)所帶來的復雜性和難以維護

場景理解:不同型別的交易方式(微信、支付寶、銀行卡);生成唯一主鍵策略(UUID、DB自增、DB自增+Redis、Leaf演算法),這些就可以使用策略模式對不同的行為進行封裝,供外部呼叫


策略模式的角色

  • 背景關系 / 環境類(Context):負責查詢要做什么,這里面也可以封裝著具體策略物件需要的資料,具體策略物件通過回呼背景關系的方法來獲取這些資料,這玩意兒可以在必要時當做引數(即:關聯)傳給具體策略物件

  • 抽象策略(Strategy):演算法的抽象 / 演算法的規范,介面或抽象類都可以

  • 具體策略(ConcreteStrategy):具體的某一個演算法 / 行為實作,若是有必要可以從背景關系中回呼它里面的方法來獲取所需要的資料


策略模式的邏輯草圖(可以對照命令模式來看,二者結構很像的)

image-20221118111321682


3.2.8.1、簡單邏輯

場景:就用上面說的不同型別交易方式(微信、支付寶)


1、背景關系Context:在必要時存盤具體策略需要的資料;負責呼叫者要做什么

package com.zixieqing;

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

/**
 * <p>@description  : 該類功能  背景關系 / 環境類
 *                      1、可以在必要時存盤具體策略物件需要的資料
 *                      2、負責呼叫者要做什么
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

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

    private PayStrategy payStrategy;

    /**
     * 讓呼叫者自己決定用哪種支付策略
     * @param payStrategy 支付方式
     */
    public PayContext(PayStrategy payStrategy) {
        this.payStrategy = payStrategy;
    }

    /**
     * 負責要做什么:支付
     */
    public void pay() {
        this.payStrategy.pay();
    }
}


2、抽象策略:對同組不同演算法進行抽象,制定規范

package com.zixieqing;

/**
 * <p>@description  : 該類功能  抽象策略:對同組不同演算法進行抽象
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface PayStrategy {

    void pay();
}

  • 微信支付
package com.zixieqing.impl;

import com.zixieqing.PayStrategy;
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 WeChatPayImpl implements PayStrategy {

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

    @Override
    public void pay() {
        logger.info("本微信系統:收到商戶平臺中傳來的 創建訂單請求");
        logger.info("本微信系統:正在創建 預付單");
        logger.info("本微信系統:預付單生成成功:回傳商戶平臺預付單標識");

        logger.info("這中間就是商戶平臺生成帶簽名的支付資訊、用戶發起支付請求、商戶平臺找微信客戶端調起微信支付");

        logger.info("本微信系統:收到微信客戶端(即:微信APP)發起的支付請求,開始驗證支付授權權限");
        logger.info("本微信系統:給微信客戶端回傳支付授權");

        logger.info("用戶:確認支付、輸入密碼,即提交授權給本微信支付系統");

        logger.info("本微信系統:正在驗證支付授權");

        logger.info("本微信系統:異步通知商戶平臺:支付結果");

        logger.info("商戶平臺:保存支付通知,并回傳本微信支付系統成功接收、處理與否");

        logger.info("本微信系統:給微信客戶端回傳支付結果,并發微信訊息提醒(即:微信中的那個微信支付的訊息)");

        logger.info("最后就是另外的一堆邏輯處理情況");
    }
}

  • 支付寶支付
package com.zixieqing.impl;

import com.zixieqing.PayStrategy;
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 AliPayImpl implements PayStrategy {

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

    @Override
    public void pay() {
        logger.info("支付寶支付和微信支付邏輯差不多的,假設這里就是一堆的支付寶支付邏輯,意思意思");
    }
}


3、測驗

package com.zixieqing;

import com.zixieqing.impl.AliPayImpl;
import com.zixieqing.impl.WeChatPayImpl;

/**
 * <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) {
        PayContext payContext = new PayContext(new WeChatPayImpl());
        payContext.pay();

        System.out.println("==================華麗的分隔符====================");

        PayContext payContext1 = new PayContext(new AliPayImpl());
        payContext1.pay();
    }
}

  • 結果
16:56:43.248 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 本微信系統:收到商戶平臺中傳來的 創建訂單請求
16:56:43.250 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 本微信系統:正在創建 預付單
16:56:43.250 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 本微信系統:預付單生成成功:回傳商戶平臺預付單標識
16:56:43.250 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 這中間就是商戶平臺生成帶簽名的支付資訊、用戶發起支付請求、商戶平臺找微信客戶端調起微信支付
16:56:43.250 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 本微信系統:收到微信客戶端(即:微信APP)發起的支付請求,開始驗證支付授權權限
16:56:43.250 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 本微信系統:給微信客戶端回傳支付授權
16:56:43.250 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 用戶:確認支付、輸入密碼,即提交授權給本微信支付系統
16:56:43.251 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 本微信系統:正在驗證支付授權
16:56:43.251 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 本微信系統:異步通知商戶平臺:支付結果
16:56:43.251 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 商戶平臺:保存支付通知,并回傳本微信支付系統成功接收、處理與否
16:56:43.251 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 本微信系統:給微信客戶端回傳支付結果,并發微信訊息提醒(即:微信中的那個微信支付的訊息)
16:56:43.251 [main] INFO  com.zixieqing.impl.WeChatPayImpl - 最后就是另外的一堆邏輯處理情況
==================華麗的分隔符====================
16:56:43.251 [main] INFO  com.zixieqing.impl.AliPayImpl - 支付寶支付和微信支付邏輯差不多的,假設這里就是一堆的支付寶支付邏輯,意思意思

3.2.8.2、分析策略模式

策略模式的優點

  • 首先就是它的適用場景就是優點:避免代碼中使用多重條件分支陳述句來判定采用哪種策略方式,從而變為采用new的方式,即:面向物件

image-20221118175645103

  • 符合開閉原則,當要新增策略時,只需要去實作介面,然后撰寫相應的策略邏輯即可

  • 由于實作同一介面,同時在Context背景關系中擁有介面的參考,所以方便客戶端對不同策略進行自由切換


策略模式的缺點

  • 可能會造成過多的具體策略類,即:在需要的代碼中可以需要new好多個具體策略類(可以通過后續玩的亨元模式來減少一定的物件數量)
  • 因為具體采用哪種策略時客戶端來決定,所以:就會導致客戶端需要知道所有的具體策略類,并且知道這些具體策略類之間有什么不同

3.2.8.3、策略模式 VS 狀態模式 VS 命令模式

狀態模式和命令模式已在前面玩狀態模式時進行了對比


3.2.8.3.1、策略模式 VS 狀態模式

image-20221121100859394

這二者可以說是"親兄弟",二者在結構上很像,但是二者有區別

  • 最主要的區別在定義上,也就是適用場景
    • 1、策略模式講究的是同組不同演算法,從而讓這些相似的演算法之間根據客戶端需要可以自由切換,具體演算法之間沒有互動,即:策略模式講的是客戶端控制物件使用什么策略,策略可自由切換是因為Context類中有Strategy策略類的參考(即:策略類之間依賴注入到Context類中)
      • image-20221121104428114
    • 2、狀態模式講究的是物件內部狀態時行為也跟著改變,同時這些行為、狀態之間可能有互動(即:狀態切換),即:狀態模式講的是自動改變狀態(具體狀態類中會根據相關邏輯進行狀態切換),可進行自動改變狀態是因為具體狀態類中有狀態所屬者Context的參考,從而在狀態子類中已根據情況進行狀態切換
      • image-20221121105403238
  • 其次區別于做的事情上
    • 策略模式做的是同一件事情,如:前面的例子微信支付、支付寶支付,這些都是針對于支付方式這一件事
    • 狀態模式在不同狀態下會做不同事情,如:前面的例子ATM在準備狀態、無服務狀態對于同一方法(插卡、提交密碼、取款......)做的都是不同的事情,方法之間不具有可替代性
  • 然后就是對于Context這個核心的背景關系物件
    • 策略模式在具體策略類中并不持有Context的參考,只供Context本身使用
    • 狀態模式在具體狀態類中會持有Context的參考,目的就是對Context類進行操作,從而讓狀態最后回歸到Context這個所屬者身上(即:方便進行狀態切換)

3.2.8.3.2、策略模式 VS 命令模式

image-20221121110155739

這二者在結構上"有點像",命令模式可以抽象地認為是一種策略模式(命令模式多了一個"接收者") 且 命令處理的問題也更復雜,二者區別如下:

  • 首先從定義上來看:

    • 策略模式:是同組不同演算法,它認為"演算法"是一個已經不可拆分的業務(即:一件事,如:支付),只是這個業務有不同方式的實作而已,目的是為了讓每個"具體演算法"獨立,并彼此之間可替代,讓客戶端來抉擇使用哪一種"演算法",所以:此模式的關注重心在于"演算法"的可替代問題

    • 命令模式:它是將動作進行解耦(即:將請求包裹在命令物件中傳給呼叫物件,由呼叫物件來找能處理該命令的物件進行處理),換言之:就是將一個動作分為了執行行為(命令物件)、執行物件(接收者角色),讓這二者彼此獨立而不相互影響,所以:此模式的關注重心在于動作的解耦問題

  • 負責點不同(策略模式的抽象策略+具體策略、命令的接收者[也可拆為抽象接收者+具體接收者])

    • 策略模式:具體策略是一個完整的邏輯,對客戶端負責,所以具體策略要做即做完,同時一旦修改就意味著對"演算法"的整體調整
    • 命令模式:只對命令負責,至于請求是什么跟它關系不大(如:前面示例的廚師,它只需要負責做菜即可,至于用戶請求是什么關其叼事);接收者可以不是一個完整的邏輯,事情沒做好不直接影響客戶端(影響的是命令物件),所以修改它不需要客戶端操心(如:某個廚師可以不做某個菜、菜怎么做不需要客戶單教他)
  • 適用場景不同

    • 策略模式:適用于"演算法"要求變換的場景
    • 命令模式:適用于解耦兩個有緊耦合關系的物件場合 或 多命令多撤銷的場景

* 3.2.9、模板模式

定義:模板模式也可以叫模板方法模式,指的是:在一個方法中定義一個演算法的骨架(即:演算法的結構),而一些其他非公共的步驟推遲到子類中去實作

一句話:封裝公有部分,擴展非公有

場景理解:建房子,工地上會把地基、走線、水管.....通用的東西搭建好(毛坯房),然后根據情況加壁柜、加柵欄....這些有差異的就是其他接手了這個毛坯房的裝修者來弄

適用場景:有多個子類共有的方法,并且邏輯相同;當然對于重要的、復雜的方法,也可以使用模板模式

注意點:因為是在父類中定義"演算法"骨架,并實作一些公有操作,而子類是不可以改變這個"演算法"骨架的,因此:模板方法一般加上final關鍵字,即:不可更改


模板方法模式的角色:

  • 抽象模板(Template):定義"演算法"模板、公有的具體操作、抽象操作
  • 具體模板(ConcreteTemplate):抽象模板的子類,實作抽象操作(即:非公有部分的邏輯)

模板方法模式的邏輯草圖:

image-20221123145641453


3.2.9.1、簡單邏輯

場景:搞豆漿,分為選原料、添加原料、浸泡、打碎,添加原料是非公有的(可為紅豆、綠豆、黑豆、赤小豆.........)


1、抽象模板

package com.zixieqing;

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

/**
 * <p>@description  : 該類功能  抽象模板
 *                      1、定義模板方法    制定要做的這件事的演算法結構
 *                      2、封裝公有部分 實作其邏輯
 *                      3、抽象非公有部分 等著子類來實作邏輯
 * </p>
 * <p>@package      : com.zixieqing</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public abstract class AbstractSoyMilk {

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

    /**
     * 模板方法:制定"演算法"結構
     */
    protected final void make() {
        // 1、選擇原料
        choose();
        // 2、添加調料
        add();
        // 3、浸泡
        soak();
        // 4、打碎
        smashed();
    }

    /**
     * 添加調料:非公有邏輯交由子類來實作
     */
    protected abstract void add();

    /**
     * 選擇原料:封裝公有部分
     */
    protected void choose() {
        logger.info("{} 正在準備添加原料 ",this.getClass().getSimpleName());

        logger.info("正在選擇原料");
    }

    /**
     * 浸泡:封裝公有部分
     */
    protected void soak() {
        logger.info("{} 正在準備添加原料 ",this.getClass().getSimpleName());

        logger.info("正在進行浸泡!");
    }

    /**
     * 打碎:封裝公有部分
     */
    protected void smashed() {
        logger.info("{} 正在準備添加原料 ",this.getClass().getSimpleName());

        logger.info("正在進行打碎");
    }
}


2、具體模板

  • 紅豆
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 class RedBeans extends AbstractSoyMilk{

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

    @Override
    protected void add() {

        logger.info("{} 正在準備添加原料 ",this.getClass().getSimpleName());

        logger.info("價值3個W的紅豆已添加");
    }
}

  • 綠豆
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 class GreenBeans extends AbstractSoyMilk{

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

    @Override
    protected void add() {
        logger.info("{} 正在準備添加原料 ",this.getClass().getSimpleName());

        logger.info("已經添加成功適合你顏色的綠豆");
    }
}


3、測驗

package com.zixieqing;

/**
 * <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) {
        // 紅豆型豆漿
        AbstractSoyMilk redBeans = new RedBeans();
        redBeans.make();

        System.out.println("=====================華麗的分隔符==================");

        // 綠豆型豆漿
        AbstractSoyMilk greenBeans = new GreenBeans();
        greenBeans.make();
    }
}

  • 結果
15:05:06.896 [main] INFO  com.zixieqing.AbstractSoyMilk - RedBeans 正在準備添加原料 
15:05:06.899 [main] INFO  com.zixieqing.AbstractSoyMilk - 正在選擇原料
15:05:06.899 [main] INFO  com.zixieqing.RedBeans - RedBeans 正在準備添加原料 
15:05:06.899 [main] INFO  com.zixieqing.RedBeans - 價值3個W的紅豆已添加
15:05:06.899 [main] INFO  com.zixieqing.AbstractSoyMilk - RedBeans 正在準備添加原料 
15:05:06.899 [main] INFO  com.zixieqing.AbstractSoyMilk - 正在進行浸泡!
15:05:06.899 [main] INFO  com.zixieqing.AbstractSoyMilk - RedBeans 正在準備添加原料 
15:05:06.899 [main] INFO  com.zixieqing.AbstractSoyMilk - 正在進行打碎
=====================華麗的分隔符==================
15:05:06.899 [main] INFO  com.zixieqing.AbstractSoyMilk - GreenBeans 正在準備添加原料 
15:05:06.899 [main] INFO  com.zixieqing.AbstractSoyMilk - 正在選擇原料
15:05:06.899 [main] INFO  com.zixieqing.GreenBeans - GreenBeans 正在準備添加原料 
15:05:06.899 [main] INFO  com.zixieqing.GreenBeans - 已經添加成功適合你顏色的綠豆
15:05:06.899 [main] INFO  com.zixieqing.AbstractSoyMilk - GreenBeans 正在準備添加原料 
15:05:06.899 [main] INFO  com.zixieqing.AbstractSoyMilk - 正在進行浸泡!
15:05:06.899 [main] INFO  com.zixieqing.AbstractSoyMilk - GreenBeans 正在準備添加原料 
15:05:06.899 [main] INFO  com.zixieqing.AbstractSoyMilk - 正在進行打碎

3.2.9.2、分析模板方法模式

模板方法模式的優點

  • 提取公共代碼,便于維護;封裝了不變部分,擴展可變部分
  • 演算法的結構由父類控制,子類只實作部分邏輯, 即擴大了代碼的復用,父類中定義了模板方法、公有部分邏輯,子類在需要時可以直接復用

模板方法模式的缺點

  • 對于可變部分,每一個不同的實作方式需要不同的類來實作(如:前面示例的紅豆、綠豆),這樣類的個數變多時,系統就變得復雜

3.2.10、訪問者模式

定義·:使用了一個訪問者類,它改變了元素類的執行演算法,通過這種方式,元素的執行演算法可以隨著訪問者改變而改變, 元素物件已接受訪問者物件,這樣訪問者物件就可以處理元素物件上的操作

解決的問題: 穩定的資料結構和易變的操作耦合問題(方式:在被訪問的類里面加一個對外提供接待訪問者的介面)

適用場景: 需要對一個物件結構中的物件進行很多不同的并且不相關的操作,而需要避免讓這些操作"玷污"這些物件的類(即:這些物件所在類的原有的資料結構),使用訪問者模式將這些封裝到類中,通俗易懂的話:就是一個東西不同的人看到的是不一樣的結果也可以用,如:公司查賬和稅務局查賬


訪問者模式的角色

  • 抽象元素(Element): 接收訪問者訪問的方法
  • 具體元素(ConcreteElement): 抽象元素的子類,實作方法
  • 抽象訪問者(Visitor): 定義訪問每個具體元素的方法
  • 具體訪問者(ConcreteVisitor): 抽象訪問者的子類,對方法進行實作
  • 物件結構(Subject Structure): 定義維護/添加元素、提供訪問者能夠訪問所有元素的方法,注:這個是可以根據情況來變的
    • 一是可以采用直接實作抽象介面,和抽象元素并為一個層次,然后里面做物件結構相應的事情即可
    • 二是可以直接起一個物件結構的類,這里面一樣的做物件結構相應的事情即可

3.2.10.1、訪問者模式是怎么變出來的

1、首先知道原有資料結構指的是什么, 肯定用過如下的代碼

@Data
public class Person {

    private String name;

}


class Test{
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("紫邪情");

        System.out.println(person.getName());
    }
}

  • 這整個Person就是資料結構,而name就是所謂的元素,而person.setName("紫邪情")就是對元素的操作,而訪問者模式就是解決對這種資料結構中的物件(元素)進行很多不同且不相關的操作,從而避免這些操作"玷污"這些物件(元素,如上面的name·)的類(如上面的Person)的原有資料結構(即:Person原來的結構)

2、抽象元素:接收訪問者能夠訪問的方法

  • 要訪問的就是元素,也就是要訪問name,因此:對類的所有要進行不同且不相關操作的屬性 進行 抽象化
/**
 * <p>@description  : 該類功能  抽象元素:把原有物件的所有屬性 進行 抽象化
 *                      1、接收訪問者訪問的方法
 * </p>
 * <p>@package      : com.zixieqing.o1derive</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface People {

    /**
     * 接收訪問者訪問的方法   方法名不一定是這個,
     *  根據對要進行不同且不相關操作的屬性操作的抽象化命名即可
     * @param visitor 訪問者
     */
    void accept(Visitor visitor);
}
  • 把對原有資料結構中要進行不同且不相關操作的屬性進行抽象化了
  • 具體元素:原有資料結構(原有類,如:Person)中要進行不同且不相關操作的屬性(如:name......),這些每一個屬性就是一個具體元素(子類),同時:這些子類都有訪問者的參考,所以都能夠讓訪問者訪問了;相應地,具體元素中的構成就變成要對這個元素(屬性)進行的那些不同且不相關的操作(如:簡單的那種setter,但不是真搞這種無趣的操作)
package com.zixieqing.o1derive.impl;

import com.zixieqing.o1derive.People;
import com.zixieqing.o1derive.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 該類功能  具體元素
 *                      1、讓訪問者能夠對原有類中的屬性進行操作
 * </p>
 * <p>@package      : com.zixieqing.o1derive.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Name implements People {

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

    @Override
    public void accept(Visitor visitor) {
        logger.info("{}這個類就是原來類中的屬性,這個{}類中就可以進行這個{}屬性的不同且不相關的操作", 
                this.getClass().getSimpleName(),
                this.getClass().getSimpleName(),
                this.getClass().getSimpleName().toLowerCase());
    }
}


3、訪問者:提供訪問所有元素的方法 訪問所有元素,就是訪問原有類(如:Perosn)的屬性,而現在屬性變成了一個單獨的類(即:具體元素)

import com.zixieqing.o1derive.impl.Name;

/**
 * <p>@description  : 該類功能  抽象訪問者:提供能夠訪問所有元素(原來類中屬性)的方法
 * </p>
 * <p>@package      : com.zixieqing.o1derive</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface Visitor {

    /**
     * 訪問原有類的name屬性 / 訪問name元素
     *      要是還有其他的就繼續加 如:sex........
     * @param name 要訪問的元素
     */
    void visit(Name name);
}
  • 具體訪問者就是去訪問元素,而元素的參考也有了,那執行
package com.zixieqing.o1derive.impl;

import com.zixieqing.o1derive.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>@description  : 該類功能  具體訪問者
 * </p>
 * <p>@package      : com.zixieqing.o1derive.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class ConcreteVisitor implements Visitor {

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

    @Override
    public void visit(Name name) {

        logger.info("{}訪問者即將開始元素訪問",this.getClass().getSimpleName());

        // 需要訪問者,當前這個就是一個具體的訪問者,當然:也可以根據情況來弄
        name.accept(this);
    }
}


4、物件結構:維護元素、提供訪問者訪問所有元素的方法

  • 原有類Person類中要進行不同且不相關操作的屬性被抽象化為了:抽象元素(接收訪問者訪問)+具體操作(該元素進行的不同且不相關的操作)
  • 訪問每個具體元素的也有了:抽象訪問者+具體訪問者
  • 那現在還得有一個東西將元素+訪問者進行連接,即:物件結構(添加元素+讓訪問者能訪問所有的元素)
package com.zixieqing.o1derive;

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

/**
 * <p>@description  : 該類功能  物件結構
 *                      1、添加元素 / 維護元素
 *                      2、讓訪問者能訪問所有元素
 * </p>
 * <p>@package      : com.zixieqing.o1derive</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class SubjectStructure {

    private List<People> peoples = new ArrayList<>();

    /**
     * 添加元素
     * @param people 要添加的元素
     * @return true/false
     */
    public boolean addElement(People people) {
        return peoples.add(people);
    }

    /**
     * 讓訪問者能夠訪問所有元素
     * @param visitor 訪問者
     */
    public void getPeoples(Visitor visitor) {
        for (People people : peoples) {
            people.accept(visitor);
        }
    }
}


5、現在我們要添加元素、獲取元素就找"物件結構"+訪問者

package com.zixieqing;

import com.zixieqing.o1derive.SubjectStructure;
import com.zixieqing.o1derive.impl.ConcreteVisitor;
import com.zixieqing.o1derive.impl.Name;

/**
 * <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) {
        SubjectStructure subjectStructure = new SubjectStructure();
        // 添加要訪問的元素
        subjectStructure.addElement(new Name());
        // 通過物件結構+具體訪問者進行元素訪問
        subjectStructure.getPeoples(new ConcreteVisitor());
    }
}


因此:現在這個模式的邏輯草圖就出來了

image-20221127224417162


3.2.10.2、簡單邏輯

場景:同一問題不同角度觀察,校長對于學生、老師的關注點(升學率);家長對學生老師的關注點(成績)


1、抽象元素:接收訪問者訪問

package com.zixieqing.o2simple.user;

import com.zixieqing.o2simple.visitor.Visitor;

/**
 * <p>@description  : 該類功能  抽象元素:用戶資訊
 *                      1、接收訪問者訪問
 * </p>
 * <p>@package      : com.zixieqing.o2simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public abstract class User {

    /**
     * 姓名
     */
    public String name;
    /**
     * 身份;重點班、普通班 | 特級教師、普通教師、實習教師
     */
    public String identity;
    /**
     * 班級
     */
    public String clazz;

    public User(String name, String identity, String clazz) {
        this.name = name;
        this.identity = identity;
        this.clazz = clazz;
    }

    /**
     * 核心訪問方法
     * @param visitor 訪問者
     */
    public abstract void accept(Visitor visitor);


}

  • 具體元素:學生
package com.zixieqing.o2simple.user.impl;

import com.zixieqing.o2simple.user.User;
import com.zixieqing.o2simple.visitor.Visitor;

/**
 * <p>@description  : 該類功能  具體元素:學生
 * </p>
 * <p>@package      : com.zixieqing.o2simple.user.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Student extends User {

    public Student(String name, String identity, String clazz) {
        super(name, identity, clazz);
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    /**
     * 對這個元素進行的不同且不相關操作之一:排名
     * @return int 排名
     */
    public int ranking() {
        return (int) (Math.random() * 100);
    }
}

  • 具體元素:老師
package com.zixieqing.o2simple.user.impl;

import com.zixieqing.o2simple.user.User;
import com.zixieqing.o2simple.visitor.Visitor;

import java.math.BigDecimal;

/**
 * <p>@description  : 該類功能  具體元素:老師
 * </p>
 * <p>@package      : com.zixieqing.o2simple.user.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Teacher extends User {
    public Teacher(String name, String identity, String clazz) {
        super(name, identity, clazz);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }


    /**
     * 對這個具體元素進行的不同且不相關的操作之一:升本率
     * @return double 升學率
     */
    public double entranceRatio() {
        return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }
}


2、訪問者:訪問每個具體元素

package com.zixieqing.o2simple.visitor;

import com.zixieqing.o2simple.user.impl.*;

/**
 * <p>@description  : 該類功能  訪問者:訪問每個具體元素
 * </p>
 * <p>@package      : com.zixieqing.o2simple.visitor</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public interface Visitor {
    /**
     * 訪問學生資訊
     * @param student 具體元素:學生
     */
    void visit(Student student);

    /**
     * 訪問老師資訊
     * @param teacher 具體元素:老師
     */
    void visit(Teacher teacher);

}

  • 具體訪問者:校長
package com.zixieqing.o2simple.visitor.impl;

import com.zixieqing.o2simple.visitor.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zixieqing.o2simple.user.impl.*;

/**
 * <p>@description  : 該類功能  具體訪問者:校長
 * </p>
 * <p>@package      : com.zixieqing.o2simple.visitor.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Principal implements Visitor {

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

    public void visit(Student student) {
        logger.info("學生資訊 姓名:{} 班級:{}", student.name, student.clazz);
    }

    public void visit(Teacher teacher) {
        logger.info("學生資訊 姓名:{} 班級:{} 升學率:{}", teacher.name, teacher.clazz, teacher.entranceRatio());
    }
}

  • 具體訪問者:家長
package com.zixieqing.o2simple.visitor.impl;

import com.zixieqing.o2simple.visitor.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zixieqing.o2simple.user.impl.*;

/**
 * <p>@description  : 該類功能  具體訪問者:家長
 * </p>
 * <p>@package      : com.zixieqing.o2simple.visitor.impl</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class Parent implements Visitor {
    private Logger logger = LoggerFactory.getLogger(Parent.class);

    public void visit(Student student) {
        logger.info("學生資訊 姓名:{} 班級:{} 排名:{}", student.name, student.clazz, student.ranking());
    }

    public void visit(Teacher teacher) {
        logger.info("老師資訊 姓名:{} 班級:{} 級別:{}", teacher.name, teacher.clazz, teacher.identity);
    }
}


3、物件結構/資料看板

package com.zixieqing.o2simple;

import com.zixieqing.o2simple.user.User;
import com.zixieqing.o2simple.user.impl.Student;
import com.zixieqing.o2simple.user.impl.Teacher;
import com.zixieqing.o2simple.visitor.Visitor;

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

/**
 * <p>@description  : 該類功能  物件結構/資料看板
 *                      1、添加元素
 *                      2、訪問所有元素
 * </p>
 * <p>@package      : com.zixieqing.o2simple</p>
 * <p>@author       : ZiXieqing</p>
 * <p>@version      : V1.0.0</p>
 */

public class DataView {
    private List<User> userList = new ArrayList<User>();

    public DataView() {
        userList.add(new Student("謝飛機", "重點班", "一年一班"));
        userList.add(new Student("windy", "重點班", "一年一班"));
        userList.add(new Student("大毛", "普通班", "二年三班"));
        userList.add(new Student("Shing", "普通班", "三年四班"));
        userList.add(new Teacher("BK", "特級教師", "一年一班"));
        userList.add(new Teacher("娜娜Goddess", "特級教師", "一年一班"));
        userList.add(new Teacher("dangdang", "普通教師", "二年三班"));
        userList.add(new Teacher("澤東", "實習教師", "三年四班"));
    }

    /**
     * 獲取所有元素
     * @param visitor 訪問者
     */
    public void show(Visitor visitor) {
        for (User user : userList) {
            user.accept(visitor);
        }
    }
}


4、測驗

package com.zixieqing;

import com.zixieqing.o2simple.DataView;
import com.zixieqing.o2simple.visitor.impl.Parent;
import com.zixieqing.o2simple.visitor.impl.Principal;
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 class ApiTest {

    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(ApiTest.class);

        DataView dataView = new DataView();

        logger.info("\r\n家長視角訪問:");
        // 家長
        dataView.show(new Parent());

        logger.info("\r\n校長視角訪問:");
        // 校長
        dataView.show(new Principal());
    }
}

  • 結果
10:53:55.565 [main] INFO  com.zixieqing.ApiTest - 
家長視角訪問:
10:53:55.568 [main] INFO  c.z.o2simple.visitor.impl.Parent - 學生資訊 姓名:謝飛機 班級:一年一班 排名:54
10:53:55.568 [main] INFO  c.z.o2simple.visitor.impl.Parent - 學生資訊 姓名:windy 班級:一年一班 排名:83
10:53:55.568 [main] INFO  c.z.o2simple.visitor.impl.Parent - 學生資訊 姓名:大毛 班級:二年三班 排名:84
10:53:55.568 [main] INFO  c.z.o2simple.visitor.impl.Parent - 學生資訊 姓名:Shing 班級:三年四班 排名:91
10:53:55.568 [main] INFO  c.z.o2simple.visitor.impl.Parent - 老師資訊 姓名:BK 班級:一年一班 級別:特級教師
10:53:55.568 [main] INFO  c.z.o2simple.visitor.impl.Parent - 老師資訊 姓名:娜娜Goddess 班級:一年一班 級別:特級教師
10:53:55.568 [main] INFO  c.z.o2simple.visitor.impl.Parent - 老師資訊 姓名:dangdang 班級:二年三班 級別:普通教師
10:53:55.568 [main] INFO  c.z.o2simple.visitor.impl.Parent - 老師資訊 姓名:澤東 班級:三年四班 級別:實習教師
10:53:55.568 [main] INFO  com.zixieqing.ApiTest - 
校長視角訪問:
10:53:55.569 [main] INFO  c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:謝飛機 班級:一年一班
10:53:55.569 [main] INFO  c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:windy 班級:一年一班
10:53:55.569 [main] INFO  c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:大毛 班級:二年三班
10:53:55.569 [main] INFO  c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:Shing 班級:三年四班
10:53:55.571 [main] INFO  c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:BK 班級:一年一班 升學率:44.54
10:53:55.571 [main] INFO  c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:娜娜Goddess 班級:一年一班 升學率:45.49
10:53:55.571 [main] INFO  c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:dangdang 班級:二年三班 升學率:70.92
10:53:55.571 [main] INFO  c.z.o2simple.visitor.impl.Principal - 學生資訊 姓名:澤東 班級:三年四班 升學率:78.14


3.2.10.3、分析訪問者模式

訪問者模式的優點:

  • 符合單一職責
  • 擁有良好的擴展性,要擴展只需添加相應的元素+訪問者即可

訪問者模式的缺點:

  • 因為元素類中有訪問者的參考,即:元素的細節對訪問者是公開的,這違反了迪米特法則(最少知道原則)
  • 在訪問者中用的是具體元素的參考,而不是抽象元素的參考,所以:這違反了里氏替換原則(即:多型)

訪問者模式的重點:元素+訪問者

image-20221127225919674


轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/538668.html

標籤:其他

上一篇:生產問題處理,可別頭疼醫頭腳疼醫腳

下一篇:生產問題處理,可別頭疼醫頭腳疼醫腳

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more