重學Java設計模式-學習筆記(1)

發表於2020-10-15

重學Java設計模式-學習筆記(http://book.bugstack.cn/#s/6AneBuNA)

設計六大原則:

單一職責:一個類和方法只做一件事

裡式替換:多型,子類可擴充套件父類

依賴倒置:細節依賴抽象,下層依賴上層

介面隔離:建立單一介面

迪米特原則:最少知道,降低耦合

開閉原則:抽象架構,擴充套件實現

工廠模式

工廠模式又稱工廠方法模式,是一種建立型設計模式,其在父類中提供一個建立物件的方法, 允許子類決定例項化物件的型別。

這種設計模式也是 Java 開發中最常見的一種模式,它的主要意圖是定義一個建立物件的介面,讓其子類自己決定例項化哪一個工廠類,工廠模式使其建立過程延遲到子類進行。

簡單說就是為了提供程式碼結構的擴充套件性,遮蔽每一個功能類中的具體實現邏輯。讓外部可以更加簡單的只是知道呼叫即可,同時,這也是去掉眾多ifelse的方式。當然這可能也有一些缺點,比如需要實現的類非常多,如何去維護,怎樣減低開發成本。但這些問題都可以在後續的設計模式結合使用中,逐步降低。

模擬發獎多種商品

為了可以讓整個學習的案例更加貼近實際開發,這裡模擬網際網路中在營銷場景下的業務。由於營銷場景的複雜、多變、臨時的特性,它所需要的設計需要更加深入,否則會經常面臨各種緊急CRUD操作,從而讓程式碼結構混亂不堪,難以維護。

在營銷場景中經常會有某個使用者做了一些操作;打卡、分享、留言、邀請註冊等等,進行返利積分,最後通過積分在兌換商品,從而促活和拉新。

那麼在這裡我們模擬積分兌換中的發放多種型別商品,假如現在我們有如下三種型別的商品介面;

序號型別介面
1優惠券CouponResult sendCoupon(String uId, String couponNumber, String uuid)
2實物商品Boolean deliverGoods(DeliverReq req)
3第三方愛奇藝兌換券void grantToken(String bindMobileNumber, String cardId)
  • 三個介面返回型別不同,有物件型別、布林型別、還有一個空型別。
  • 入參不同,發放優惠券需要仿重、兌換卡需要卡ID、實物商品需要發貨位置(物件中含有)。
  • 另外可能會隨著後續的業務的發展,會新增其他種商品型別。因為你所有的開發需求都是隨著業務對市場的擴充而帶來的。

用糟糕的方法實現

itstack-demo-design-1-01
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── AwardReq.java
    │           ├── AwardRes.java
    │           └── PrizeController.java 
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

如果不考慮擴充套件性,只為了更快的滿足需求,那麼對這幾種獎勵只需要進行ifelse語句判斷,呼叫不同的介面即可滿足需求。這也可能是剛入門的小夥伴常用的方法。我們先用這種方法實現一下。

public class PrizeController {

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

    public AwardRes awardToUser(AwardReq req) {
        String reqJson = JSON.toJSONString(req);
        AwardRes awardRes = null;
        try {
            logger.info("獎品發放開始{}。req:{}", req.getuId(), reqJson);
            // 按照不同型別方法商品[1優惠券、2實物商品、3第三方兌換卡(愛奇藝)]
            if (req.getAwardType() == 1) {
                CouponService couponService = new CouponService();
                CouponResult couponResult = couponService.sendCoupon(req.getuId(), req.getAwardNumber(), req.getBizId());
                if ("0000".equals(couponResult.getCode())) {
                    awardRes = new AwardRes("0000", "發放成功");
                } else {
                    awardRes = new AwardRes("0001", couponResult.getInfo());
                }
            } else if (req.getAwardType() == 2) {
                GoodsService goodsService = new GoodsService();
                DeliverReq deliverReq = new DeliverReq();
                deliverReq.setUserName(queryUserName(req.getuId()));
                deliverReq.setUserPhone(queryUserPhoneNumber(req.getuId()));
                deliverReq.setSku(req.getAwardNumber());
                deliverReq.setOrderId(req.getBizId());
                deliverReq.setConsigneeUserName(req.getExtMap().get("consigneeUserName"));
                deliverReq.setConsigneeUserPhone(req.getExtMap().get("consigneeUserPhone"));
                deliverReq.setConsigneeUserAddress(req.getExtMap().get("consigneeUserAddress"));
                Boolean isSuccess = goodsService.deliverGoods(deliverReq);
                if (isSuccess) {
                    awardRes = new AwardRes("0000", "發放成功");
                } else {
                    awardRes = new AwardRes("0001", "發放失敗");
                }
            } else if (req.getAwardType() == 3) {
                String bindMobileNumber = queryUserPhoneNumber(req.getuId());
                IQiYiCardService iQiYiCardService = new IQiYiCardService();
                iQiYiCardService.grantToken(bindMobileNumber, req.getAwardNumber());
                awardRes = new AwardRes("0000", "發放成功");
            }
            logger.info("獎品發放完成{}。", req.getuId());
        } catch (Exception e) {
            logger.error("獎品發放失敗{}。req:{}", req.getuId(), reqJson, e);
            awardRes = new AwardRes("0001", e.getMessage());
        }

        return awardRes;
    }

    private String queryUserName(String uId) {
        return "花花";
    }

    private String queryUserPhoneNumber(String uId) {
        return "15200101232";
    }

}

如果僅從業務角度看,研發如期甚至提前實現了功能,這樣的程式碼目前來看並不會有什麼問題,但如果在經過幾次的迭代和擴充,接手這段程式碼的研發將十分痛苦。重構成本高需要理清之前每一個介面的使用,測試迴歸驗證時間長,需要全部驗證一次。這也就是很多人並不願意接手別人的程式碼,如果接手了又被壓榨開發時間。那麼可想而知這樣的 ifelse 還會繼續增加

工廠模式介紹

來源:菜鳥教程

工廠模式(Factory Pattern)是 Java 中最常用的設計模式之一。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。

在工廠模式中,我們在建立物件時不會對客戶端暴露建立邏輯,並且是通過使用一個共同的介面來指向新建立的物件。

**意圖:**定義一個建立物件的介面,讓其子類自己決定例項化哪一個工廠類,工廠模式使其建立過程延遲到子類進行。

**主要解決:**主要解決介面選擇的問題。

**何時使用:**我們明確地計劃不同條件下建立不同例項時。

**如何解決:**讓其子類實現工廠介面,返回的也是一個抽象的產品。

**關鍵程式碼:**建立過程在其子類執行。

應用例項: 1、您需要一輛汽車,可以直接從工廠裡面提貨,而不用去管這輛汽車是怎麼做出來的,以及這個汽車裡面的具體實現。 2、Hibernate 換資料庫只需換方言和驅動就可以。

優點: 1、一個呼叫者想建立一個物件,只要知道其名稱就可以了。 2、擴充套件性高,如果想增加一個產品,只要擴充套件一個工廠類就可以。 3、遮蔽產品的具體實現,呼叫者只關心產品的介面。

**缺點:**每次增加一個產品時,都需要增加一個具體類和物件實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的複雜度,同時也增加了系統具體類的依賴。這並不是什麼好事。

使用工廠模式

itstack-demo-design-1-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── store    
    │           │   ├── impl
    │           │   │   ├── CardCommodityService.java
    │           │   │   ├── CouponCommodityService.java 
    │           │   │   └── GoodsCommodityService.java  
    │           │   └── ICommodity.java
    │           └── StoreFactory.java 
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

定義發獎介面

// 定義一個統一的介面,而這個介面的引數要保證能涵蓋所有實現方法的需要引數

public interface ICommodity {

    void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;

}
  • 所有的獎品無論是實物、虛擬還是第三方,都需要通過我們的程式實現此介面進行處理,以保證最終入參出參的統一性。
  • 介面的入參包括;使用者ID獎品ID業務ID以及擴充套件欄位用於處理髮放實物商品時的收穫地址。

獎品發放介面

優惠券

public class CouponCommodityService implements ICommodity {

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

    private CouponService couponService = new CouponService();

    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
        CouponResult couponResult = couponService.sendCoupon(uId, commodityId, bizId);
        logger.info("請求引數[優惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
        logger.info("測試結果[優惠券]:{}", JSON.toJSON(couponResult));
        if (!"0000".equals(couponResult.getCode())) throw new RuntimeException(couponResult.getInfo());
    }

}

實物商品

public class GoodsCommodityService implements ICommodity {

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

    private GoodsService goodsService = new GoodsService();

    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
        DeliverReq deliverReq = new DeliverReq();
        deliverReq.setUserName(queryUserName(uId));
        deliverReq.setUserPhone(queryUserPhoneNumber(uId));
        deliverReq.setSku(commodityId);
        deliverReq.setOrderId(bizId);
        deliverReq.setConsigneeUserName(extMap.get("consigneeUserName"));
        deliverReq.setConsigneeUserPhone(extMap.get("consigneeUserPhone"));
        deliverReq.setConsigneeUserAddress(extMap.get("consigneeUserAddress"));

        Boolean isSuccess = goodsService.deliverGoods(deliverReq);

        logger.info("請求引數[優惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
        logger.info("測試結果[優惠券]:{}", isSuccess);

        if (!isSuccess) throw new RuntimeException("實物商品發放失敗");
    }
	// 實際中應該是個查詢語句
    private String queryUserName(String uId) {
        return "花花";
    }
	// 實際中應該是個查詢語句
    private String queryUserPhoneNumber(String uId) {
        return "15200101232";
    }

}

第三方兌換卡

public class CardCommodityService implements ICommodity {

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

    // 模擬注入
    private IQiYiCardService iQiYiCardService = new IQiYiCardService();

    public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
        String mobile = queryUserMobile(uId);
        iQiYiCardService.grantToken(mobile, bizId);
        logger.info("請求引數[愛奇藝兌換卡] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
        logger.info("測試結果[愛奇藝兌換卡]:success");
    }
	// 實際中應該是個查詢語句
    private String queryUserMobile(String uId) {
        return "15200101232";
    }

}
  • 從上面可以看到每一種獎品的實現都包括在自己的類中,新增、修改或者刪除都不會影響其他獎品功能的測試,降低迴歸測試的可能。
  • 後續在新增的獎品只需要按照此結構進行填充即可,非常易於維護和擴充套件。
  • 在統一了入參以及出參後,呼叫方不在需要關心獎品發放的內部邏輯,按照統一的方式即可處理。

建立商店工廠

// 這裡應該也可以是個控制層,根據傳來的引數呼叫對應的方法

public class StoreFactory {

    public ICommodity getCommodityService(Integer commodityType) {
        if (null == commodityType) return null;
        if (1 == commodityType) return new CouponCommodityService();
        if (2 == commodityType) return new GoodsCommodityService();
        if (3 == commodityType) return new CardCommodityService();
        throw new RuntimeException("不存在的商品服務型別");
    }

}
  • 這裡我們定義了一個商店的工廠類,在裡面按照型別實現各種商品的服務。可以非常乾淨整潔的處理你的程式碼,後續新增的商品在這裡擴充套件即可。如果你不喜歡if判斷,也可以使用switch或者map配置結構,會讓程式碼更加乾淨。
  • 另外很多程式碼檢查軟體和編碼要求,不喜歡if語句後面不寫擴充套件,這裡是為了更加乾淨的向你體現邏輯。在實際的業務編碼中可以新增括號。

總結

  • 從上到下的優化來看,工廠方法模式並不複雜,甚至這樣的開發結構在你有所理解後,會發現更加簡單了。
  • 那麼這樣的開發的好處知道後,也可以總結出來它的優點;避免建立者與具體的產品邏輯耦合滿足單一職責,每一個業務邏輯實現都在所屬自己的類中完成滿足開閉原則,無需更改使用呼叫方就可以在程式中引入新的產品型別
  • 但這樣也會帶來一些問題,比如有非常多的獎品型別,那麼實現的子類會極速擴張(這是工廠模式的缺點)。因此也需要使用其他的模式進行優化,這些在後續的設計模式中會逐步涉及到。

抽象工廠模式

抽象工廠模式與工廠方法模式雖然主要意圖都是為了解決,介面選擇問題。但在實現上,抽象工廠是一箇中心工廠,建立其他工廠的模式。

可能在平常的業務開發中很少關注這樣的設計模式或者類似的程式碼結構,但是這種場景確一直在我們身邊,例如;

  1. 不同系統內的回車換行
    1. Unix系統裡,每行結尾只有 <換行>,即 \n
    2. Windows系統裡面,每行結尾是 <換行><回車>,即 \n\r
    3. Mac系統裡,每行結尾是 <回車>

**除了這樣顯而易見的例子外,我們的業務開發中時常也會遇到類似的問題,需要相容做處理。**但大部分經驗不足的開發人員,常常直接通過新增ifelse方式進行處理了。

案例場景模擬

模擬企業級雙套Redis叢集升級

很多時候初期業務的蠻荒發展,也會牽動著研發對系統的建設。

預估QPS較低系統壓力較小併發訪問不大近一年沒有大動作等等,在考慮時間投入成本的前提前,並不會投入特別多的人力去構建非常完善的系統。就像對 Redis 的使用,往往可能只要是單機的就可以滿足現狀。

但隨著業務超過預期的快速發展,系統的負載能力也要隨著跟上。原有的單機 Redis 已經滿足不了系統需求。這時候就需要更換為更為健壯的Redis叢集服務,雖然需要修改但是不能影響目前系統的執行,還要平滑過渡過去。

隨著這次的升級,可以預見的問題會有;

  1. 很多服務用到了Redis需要一起升級到叢集。
  2. 需要相容叢集A和叢集B,便於後續的災備。
  3. 兩套叢集提供的介面和方法各有差異,需要做適配。
  4. 不能影響到目前正常執行的系統。

場景簡述

模擬單機redis

Redis單機服務

模擬叢集egm

模擬叢集 EGM

模擬叢集iir

模擬叢集 IIR

綜上可以看到,我們目前的系統中已經在大量的使用redis服務,但是因為系統不能滿足業務的快速發展,因此需要遷移到叢集服務中。而這時有兩套叢集服務需要相容使用,又要滿足所有的業務系統改造的同時不影響線上使用。但是方法名與各業務系統中使用的方法名不同。有點像你mac,我用win。做一樣的事,但有不同的操作。

定義使用介面

public interface CacheService {

    String get(final String key);

    void set(String key, String value);

    void set(String key, String value, long timeout, TimeUnit timeUnit);

    void del(String key);

}

實現呼叫程式碼

public class CacheServiceImpl implements CacheService {

    private RedisUtils redisUtils = new RedisUtils();

    public String get(String key) {
        return redisUtils.get(key);
    }

    public void set(String key, String value) {
        redisUtils.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        redisUtils.set(key, value, timeout, timeUnit);
    }

    public void del(String key) {
        redisUtils.del(key);
    }

}

用糟糕的程式碼實現

itstack-demo-design-2-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── impl
                │   └── CacheServiceImpl.java
                └── CacheService.java
// 這裡的實現過程非常簡單,主要根據型別判斷是哪個Redis叢集。
// 雖然實現是簡單了,但是對使用者來說就麻煩了,並且也很難應對後期的擴充和不停的維護。
public class CacheServiceImpl implements CacheService {

    private RedisUtils redisUtils = new RedisUtils();

    private EGM egm = new EGM();

    private IIR iir = new IIR();

    public String get(String key, int redisType) {

        if (1 == redisType) {
            return egm.gain(key);
        }

        if (2 == redisType) {
            return iir.get(key);
        }

        return redisUtils.get(key);
    }

    public void set(String key, String value, int redisType) {

        if (1 == redisType) {
            egm.set(key, value);
            return;
        }

        if (2 == redisType) {
            iir.set(key, value);
            return;
        }

        redisUtils.set(key, value);
    }

    //... 同類不做太多展示,可以下載原始碼進行參考

}

抽象工廠介紹

抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠建立其他工廠。該超級工廠又稱為其他工廠的工廠。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。

在抽象工廠模式中,介面是負責建立一個相關物件的工廠,不需要顯式指定它們的類。每個生成的工廠都能按照工廠模式提供物件。

**意圖:**提供一個建立一系列相關或相互依賴物件的介面,而無需指定它們具體的類。

**主要解決:**主要解決介面選擇的問題。

**何時使用:**系統的產品有多於一個的產品族,而系統只消費其中某一族的產品。

**如何解決:**在一個產品族裡面,定義多個產品。

**關鍵程式碼:**在一個工廠裡聚合多個同類產品。

**應用例項:**工作了,為了參加一些聚會,肯定有兩套或多套衣服吧,比如說有商務裝(成套,一系列具體產品)、時尚裝(成套,一系列具體產品),甚至對於一個家庭來說,可能有商務女裝、商務男裝、時尚女裝、時尚男裝,這些也都是成套的,即一系列具體產品。假設一種情況(現實中是不存在的,要不然,沒法進入共產主義了,但有利於說明抽象工廠模式),在您的家中,某一個衣櫃(具體工廠)只能存放某一種這樣的衣服(成套,一系列具體產品),每次拿這種成套的衣服時也自然要從這個衣櫃中取出了。用 OOP 的思想去理解,所有的衣櫃(具體工廠)都是衣櫃類的(抽象工廠)某一個,而每一件成套的衣服又包括具體的上衣(某一具體產品),褲子(某一具體產品),這些具體的上衣其實也都是上衣(抽象產品),具體的褲子也都是褲子(另一個抽象產品)。

**優點:**當一個產品族中的多個物件被設計成一起工作時,它能保證客戶端始終只使用同一個產品族中的物件。

**缺點:**產品族擴充套件非常困難,要增加一個系列的某一產品,既要在抽象的 Creator 里加程式碼,又要在具體的裡面加程式碼。

使用場景: 1、QQ 換皮膚,一整套一起換。 2、生成不同作業系統的程式。

抽象工廠模式實現

這裡的抽象工廠的建立和獲取方式,會採用代理類的方式進行實現。所被代理的類就是目前的Redis操作方法類,讓這個類在不需要任何修改下,就可以實現呼叫叢集A和叢集B的資料服務。

並且這裡還有一點非常重要,由於叢集A和叢集B在部分方法提供上是不同的,因此需要做一個介面適配,而這個適配類就相當於工廠中的工廠,用於建立把不同的服務抽象為統一的介面做相同的業務。這一塊與我們上一章節中的工廠方法模型型別,可以翻閱參考。

itstack-demo-design-2-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── factory    
    │           │   ├── impl
    │           │   │   ├── EGMCacheAdapter.java 
    │           │   │   └── IIRCacheAdapter.java
    │           │   ├── ICacheAdapter.java
    │           │   ├── JDKInvocationHandler.java
    │           │   └── JDKProxy.java
    │           ├── impl
    │           │   └── CacheServiceImpl.java    
    │           └── CacheService.java 
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

抽象工廠模型結構

抽象工廠模型結構

  • 工程中涉及的部分核心功能程式碼,如下;
    • ICacheAdapter,定義了適配介面,分別包裝兩個叢集中差異化的介面名稱。EGMCacheAdapterIIRCacheAdapter
    • JDKProxyJDKInvocationHandler,是代理類的定義和實現,這部分也就是抽象工廠的另外一種實現方式。通過這樣的方式可以很好的把原有操作Redis的方法進行代理操作,通過控制不同的入參物件,控制快取的使用。

好,那麼接下來會分別講解幾個類的具體實現。

定義適配介面

public interface ICacheAdapter {
	// 這個類的主要作用是讓所有叢集的提供方,能在統一的方法名稱下進行操作。也方面後續的擴充。
    
    String get(String key);

    void set(String key, String value);

    void set(String key, String value, long timeout, TimeUnit timeUnit);

    void del(String key);

}

實現叢集使用服務

public class EGMCacheAdapter implements ICacheAdapter {

    private EGM egm = new EGM();

    public String get(String key) {
        return egm.gain(key);
    }

    public void set(String key, String value) {
        egm.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        egm.setEx(key, value, timeout, timeUnit);
    }

    public void del(String key) {
        egm.delete(key);
    }
}
public class IIRCacheAdapter implements ICacheAdapter {

    private IIR iir = new IIR();

    public String get(String key) {
        return iir.get(key);
    }

    public void set(String key, String value) {
        iir.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        iir.setExpire(key, value, timeout, timeUnit);
    }

    public void del(String key) {
        iir.del(key);
    }

}

定義抽象工程代理類和實現

public static <T> T getProxy(Class<T> interfaceClass, ICacheAdapter cacheAdapter) throws Exception {
    InvocationHandler handler = new JDKInvocationHandler(cacheAdapter);
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    Class<?>[] classes = interfaceClass.getInterfaces();
    return (T) Proxy.newProxyInstance(classLoader, new Class[]{classes[0]}, handler);
}
public class JDKInvocationHandler implements InvocationHandler {

    private ICacheAdapter cacheAdapter;

    public JDKInvocationHandler(ICacheAdapter cacheAdapter) {
        this.cacheAdapter = cacheAdapter;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
    }

}
  • 在代理類的實現中其實也非常簡單,通過穿透進來的叢集服務進行方法操作。
  • 另外在invoke中通過使用獲取方法名稱反射方式,呼叫對應的方法功能,也就簡化了整體的使用。
  • 到這我們就已經將整體的功能實現完成了,關於抽象工廠這部分也可以使用非代理的方式進行實現。

使用

@Test
public void test_CacheService() throws Exception {
    CacheService proxy_EGM = JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
    proxy_EGM.set("user_name_01","小傅哥");
    String val01 = proxy_EGM.get("user_name_01");
    System.out.println(val01);
    
    CacheService proxy_IIR = JDKProxy.getProxy(CacheServiceImpl.class, new IIRCacheAdapter());
    proxy_IIR.set("user_name_01","小傅哥");
    String val02 = proxy_IIR.get("user_name_01");
    System.out.println(val02);
}
  • 在測試的程式碼中通過傳入不同的叢集型別,就可以呼叫不同的叢集下的方法。JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
    我們就已經將整體的功能實現完成了,關於抽象工廠這部分也可以使用非代理的方式進行實現。

使用

@Test
public void test_CacheService() throws Exception {
    CacheService proxy_EGM = JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
    proxy_EGM.set("user_name_01","小傅哥");
    String val01 = proxy_EGM.get("user_name_01");
    System.out.println(val01);
    
    CacheService proxy_IIR = JDKProxy.getProxy(CacheServiceImpl.class, new IIRCacheAdapter());
    proxy_IIR.set("user_name_01","小傅哥");
    String val02 = proxy_IIR.get("user_name_01");
    System.out.println(val02);
}
  • 在測試的程式碼中通過傳入不同的叢集型別,就可以呼叫不同的叢集下的方法。JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
  • 如果後續有擴充套件的需求,也可以按照這樣的型別方式進行補充,同時對於改造上來說並沒有改動原來的方法,降低了修改成本。

相關文章