多系統對接的適配與包裝模式應用

来源:https://www.cnblogs.com/wanglifeng717/archive/2022/06/13/16348529.html
-Advertisement-
Play Games

日常開發系統中通常需要對接多個系統,需要用到適配器模式。 例如:支付方式就涉及多個系統對接。 國際慣例,先引入概念。 適配器模式: 提到適配器自然就能想到手機用的電源適配器。 他的作用就是將220V交流電轉換成手機使用的5V直流電。 適配器作用:將一個介面轉換成另外一個介面,已符合客戶的期望。 軟體 ...


 

日常開發系統中通常需要對接多個系統,需要用到適配器模式。

例如:支付方式就涉及多個系統對接。 

國際慣例,先引入概念。

 

適配器模式:

 

提到適配器自然就能想到手機用的電源適配器。

他的作用就是將220V交流電轉換成手機使用的5V直流電。

適配器作用:將一個介面轉換成另外一個介面,已符合客戶的期望。

 

 

軟體系統中,比如一期我們使用了阿裡雲的sdk包的一些功能介面。

但是二期我想換騰訊雲sdk相同的功能。但是他們相同的功能,介面參數確不同。

我們不想按照騰訊雲的介面修改我們的業務代碼,畢竟業務邏輯已經經過反覆測試驗證了。可以把騰訊雲的介面包裝起來,實現一個一期的介面。這個工作叫【適配】。

在例如:

在軟體系統中,你可能有很多種支付方式,微信支付,支付寶支付,各種銀行。

但是他們的支付介面肯定都是不一樣的,我不希望我新加一種支付方式就加去修改代碼。

此時就需要有一個統一支付的適配器服務幫我們屏蔽各個支付方式的不同。

我的業務服務只和統一支付交互。統一支付向業務系統提供統一介面,統一支付負責路由不同支付系統後臺,並屏蔽掉各系統差異。這個工作也叫【適配】

 

適配模式:

/**
 * 目標介面:提供5V電壓的一個介面
 */
public interface V5Power
{
    public int provideV5Power();
}

/**
 * 被適配者、已有功能:家用220V交流電
 */
public class V220Power
{
    /**
     * 提供220V電壓
     */
    public int provideV220Power()
    {
        System.out.println("我提供220V交流電壓。");
        return 220 ; 
    }
}

/**
 * 適配器,有已有對象已有功能實現介面,把220V電壓變成5V
 */
public class V5PowerAdapter implements V5Power
{
    /**
     * 組合的方式
     */
    private V220Power v220Power ;
    
    public V5PowerAdapter(V220Power v220Power)
    {
        this.v220Power = v220Power ;
    }
 
    @Override
    public int provideV5Power()
    {
        int power = v220Power.provideV220Power() ;
        //power經過各種操作-->5 
        System.out.println("適配器:我悄悄的適配了電壓。");
        return 5 ; 
    } 
    
}
適配器
public class Mobile
{
    // 使用目標介面功能
    public void inputPower(V5Power power)
    {
        int provideV5Power = power.provideV5Power();
        System.out.println("手機(客戶端):我需要5V電壓充電,現在是-->" + provideV5Power + "V");
    }
}

//測試
public class Test
{
    public static void main(String[] args)
    {
        Mobile mobile = new Mobile();
        V5Power v5Power = new V5PowerAdapter(new V220Power()) ; 
        mobile.inputPower(v5Power);
    }
}
Test

定義:

將一個類的介面,轉換成客戶期望的另一個介面。適配器讓原本介面不相容的類可以合作無間。

適配模式說的通俗點就是用一個已有的功能類,去實現一個介面。這樣該功能類,和原來的客戶端代碼都不需要改變,對於客戶端來說相當於換了一種實現方式。

好處就是讓客戶從實現中【解耦】,下次在換其他的功能類,我就再寫一個適配器。有點像策略模式中,我們換一個實現類一樣。

 

當然,我們適配器可以包裝很多被適配對象,即可以組合很多已有功能類。因為很多介面很複雜需要使用很多類。

同樣,適配器也可以不使用【組合】對象形式,而是使用【繼承】。 

 

示例:

以前java集合類都實現了Enumeration枚舉介面,可以遍歷集合中每個元素,而無需知道它們在集合內元素是如何被管理。

之後推出了新的Iterator迭代器介面,這個介面和枚舉介面很像,都可以讓你遍歷集合中每個元素。

不同的是,迭代器還提供了刪除元素的能力。

面對遺留代碼,這些代碼暴露出來的是枚舉介面,他們是老版本的java只支持枚舉,但是我們希望新的代碼中使用迭代器。只能用枚舉實現一個迭代器。

public class EnumerationIterator implements Iterator{
    Enumeration enum;
    
    public EnumerationIterator(Enumeration enum){
        this.enum = enum;
    }
    
    public boolean hasNext(){
        return enum.hasMoreElements();
    }
    
    public Object next(){
        return enum.nextElement();
    }
    
    public void remove(){
        throw new UnsupportedOperationException();
    }

}
/*
枚舉介面是只讀的,適配器無法實現一個有實際功能的remove()方法我們的實現方式並不完美,客戶必須小心潛在的異常。只要客戶足夠小心,並且在適配器的文檔中作出說明,這也算是一個合理的解決方案。

*/
用枚舉實現迭代器

 

外觀模式:

 

外觀模式:提供一個統一介面,用來訪問子系統中的一群介面。外觀定義了一個高層介面,讓子系統更容易使用。

例如:我們遙控器點擊下自動就打開幕布、打開投影機、打開音響、開始放電影。而不是一步一步的自己操作。

不僅僅是簡化了介面,也將客戶從組建彙總解耦了出來。

目的就是讓系統更加容易使用,也符合【最少知道】原則,因為客戶只有【外觀角色】一個朋友。

 

【最少知道】原則就是不要讓太多類耦合在一起,免得修改系統時候,牽一發而動全身。對象儘量“少交朋友”。之和自己有關的交互即可。

就對象而言,在該對象的方法內,我們只應該調用屬於以下範圍的方法:

1.該對象本身

2.被當做方法的參數而傳遞進來的對象

3.此方法所創建或實例化的任何對象

4.對象的任何組建,即屬性變數引用的對象

如果調用返回對象的方法,相當於向另外一個對象的子部分發出請求。我們就多依賴了一個對象。

// 不應用【最少知道】原則

public float getTemp(){

  Thermometer thermometer = station.getThermometer();

  return thermometer.getTemperature(); // 我們從氣象站獲取了溫度計對象,然後從該對象獲取了溫度。

}

// 應用【最少知道】原則

public float getTemp(){

  return station.getTemperature(); // 應該直接讓氣象站給我溫度,我不想依賴溫度計對象。

}

 

裝飾模式:

 

 用面向對象的方式對飲料進行描述。這種描述方式的局限在於。

我們飲料可以添加輔料。例如:大杯、加冰、加奶、雙倍咖啡。

我們不可能一種組合就建一個類。那樣類數量就爆炸了。

此時計算價格就非常困難。

將各種調料放在基類中。調料可以是boolean或者數據或者枚舉類型。

每種飲料計算價錢的時候就看這些調料是否有,或者有多少。

public class Drink{
    private boolean milk;
    private boolean sugar;
    
    public double cost(){
        double price = 0d;
        if(milk){ price +=0.5; }
        if(sugar){price +=0.1; }
        return price;
    } 
    
    public void addMilk(){
        this.mocha=true;
    }
    
    public void addSugar(){
        this.whip= true;
    }
}

public class DarkCoffee extends Drink{
    public double cost(){
        double price = super.cost();
        price += 2.0;
        return price;
    }

}


 測試:
public class Test{

    public static void main(String[] args){
        Drink coffee = new DarkCoffee();
        coffee.addMocha();
        coffee.addWhip();
        System.out.println(coffee.cost());
    }
}
通過設置屬性解決調料問題

這種設計存在問題:

1.如果調味品很多,Drink類非常龐大,且新加和刪除調味品都需要修改類,調料改價格需要調整價格。

2.雙倍調味料的情況boolean值無法滿足。

3.很多調料是互斥的,例如iceCoffee是不能加mocha的,但是他還是繼承了父類的addMocha()方法。這個方法他不適用,他必須覆蓋這個方法讓它什麼都不做。

不符合【開閉原則】

 

裝飾模式解釋:

調料可以包裝基礎飲料,因為裝飾者和被裝飾者有相同的超類型,所以可以套娃形式一直套下去。

裝飾者可以在所委托被裝飾者的行為前後,加上自己的行為,已達到特定目的。

計算價格類似於遞歸,不斷委托給父類,最後統一回溯。

abstract class Drink{
    public String description = "Unknown beverage";
    
    public String getDescription(){
        return description;
    }
    
    public abstract double cost();
}

// 裝飾類
 abstract class CondimentDecorator extends Drink{
    public abstract String getDescription();
}

// 飲料
 class DarkCoffee extends Drink{
    public DarkCoffee(){
        this.description = "DarkCoffee";
    }
    public double cost(){
        return 1.99;
    }
}

// 包裝類:調料
 class Sugar extends CondimentDecorator{
    Drink drink;
    
    public Sugar(Drink drink){
        this.drink = drink;
    }
    
    public String getDescription(){
        return drink.getDescription() + ", add sugar";
    }
    
    public double cost(){
        return .20 + drink.cost();
    }
}

//測試
 class Test{
    public static void main(String[] args){
        Drink drink = new DarkCoffee();
        drink = new Sugar(drink);
        // 加雙份糖
        drink = new Sugar(drink);
        System.out.println(drink.getDescription() + " ,$"+drink.cost());
        //輸出:DarkCoffee, add sugar, add sugar ,$2.39
    }
}
裝飾模式
public interface Drink{
    public double cost();
}

public class DarkCoffee implements Drink{
    public double cost(){
        return 2.5;
    }
}


public class Decorator implements Drink{
    private Drink drink;
    
    public Decorator (Drink drink){
        this.drink= drink;
    }
    
    public double cost(){
        return drink.cost();
    }
}

public class Sugar extends Decorator {

    public Sugar (Drink drink){
        super(drink);
    }
    
    public double cost(){
        return super.cost() + 0.1;
    }

}
裝飾模式2

 

 

裝飾模式:動態將責任附加到對象上,若要拓展功能,裝飾者提供了比繼承更有彈性的替代方案。

裝飾者模式容易造成設計中大量的小類。數量太多。容易把人看懵。例如:java.io

 

/*
 編寫一個裝飾者,將輸入流內所有大寫字元轉小寫。
我們需要拓展InputStream。
  */
public class LowerCaseInputStream extends FilterInputStream {

    public LowerCaseInputStream(InputStream in) {
        super(in);
    }
 
    public int read() throws IOException {
        int c = in.read();
        return (c == -1 ? c : Character.toLowerCase((char)c));
    }
        
    public int read(byte[] b, int offset, int len) throws IOException {
        int result = in.read(b, offset, len);
        for (int i = offset; i < offset+result; i++) {
            b[i] = (byte)Character.toLowerCase((char)b[i]);
        }
        return result;
    }
}


public class InputTest {
    public static void main(String[] args) throws IOException {
        int c;
        InputStream in = null;
        try {
            in = 
                new LowerCaseInputStream( 
                    new BufferedInputStream(
                        new FileInputStream("test.txt")));

            while((c = in.read()) >= 0) {
                System.out.print((char)c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) { in.close(); }
        }
        System.out.println();
        try (InputStream in2 = 
                new LowerCaseInputStream(
                    new BufferedInputStream(
                        new FileInputStream("test.txt")))) 
        {
            while((c = in2.read()) >= 0) {
                System.out.print((char)c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
自己包裝IO

例如:我們對HttpServletRequest進行攔截進行處理,實現過濾請求參數。

1). Servlet API 中提供了一個 HttpServletRequestWrapper 類來包裝原始的 request 對象,
HttpServletRequestWrapper 類實現了 HttpServletRequest 介面中的所有方法, 
這些方法的內部實現都是僅僅調用了一下所包裝的的 request 對象的對應方法

//包裝類實現 ServletRequest 介面. 
public class ServletRequestWrapper implements ServletRequest {

        //被包裝的那個 ServletRequest 對象
        private ServletRequest request;
    
    //構造器傳入 ServletRequest 實現類對象
        public ServletRequestWrapper(ServletRequest request) {
        if (request == null) {
         throw new IllegalArgumentException("Request cannot be null"); 
        }
        this.request = request;
        }

    //具體實現 ServletRequest 的方法: 調用被包裝的那個成員變數的方法實現。 
        public Object getAttribute(String name) {
        return this.request.getAttribute(name);
    }

        public Enumeration getAttributeNames() {
        return this.request.getAttributeNames();
    } 
    
    //...    
}    


2). 作用: 用於對 HttpServletRequest 或 HttpServletResponse 的某一個方法進行修改或增強.

public class MyHttpServletRequest extends HttpServletRequestWrapper{

    public MyHttpServletRequest(HttpServletRequest request) {
        super(request);
    }
    
    @Override
    public String getParameter(String name) {
        String val = super.getParameter(name);
        if(val != null && val.contains(" fuck ")){ 
            val = val.replace("fuck", "****");
        }
        return val;
    }
}

3). 使用: 在 Filter 中, 利用 MyHttpServletRequest 替換傳入的 HttpServletRequest

HttpServletRequest req = new MyHttpServletRequest(request);
filterChain.doFilter(req, response);
增強HttpServletRequest

 例如:在一個方法前後列印時間

// 介面
public interface Dao {
    public void insert();
    public void delete();
    public void update();
}
//  基礎實現類
public class DaoImpl implements Dao {

    @Override
    public void insert() {
        System.out.println("DaoImpl.insert()");
    }

    @Override
    public void delete() {
        System.out.println("DaoImpl.delete()");
    }

    @Override
    public void update() {
        System.out.println("DaoImpl.update()");
    }
}

// 包裝類
public class LogDao implements Dao {

    private Dao dao;

    public LogDao(Dao dao) {
        this.dao = dao;
    }

    @Override
    public void insert() {
        System.out.println("insert()方法開始時間:" + System.currentTimeMillis());
        dao.insert();
        System.out.println("insert()方法結束時間:" + System.currentTimeMillis());
    }

    @Override
    public void delete() {
        dao.delete();
    }

    @Override
    public void update() {
        System.out.println("update()方法開始時間:" + System.currentTimeMillis());
        dao.update();
        System.out.println("update()方法結束時間:" + System.currentTimeMillis());
    }

}

// 調用時候 Dao dao = new LogDao(new DaoImpl());
// 對於調用方來說,只知道調用了dao,不知道加上了日誌功能
// 問題:1.輸出日誌的邏輯無法復用;2.輸入日誌和業務邏輯有耦合。
裝飾模式方法前後打日誌

 

 

 其實裝飾模式和適配器模式很像。都是包裝一個對象,然後利用這個對象的功能搞出點對外提供的方法。

裝飾模式

適配器模式

外觀模式

目的:不改變介面,加入責任

目的:將一個介面轉成另一個介面

目的:讓介面跟簡單

裝飾者模式可以讓新的行為和責任要加入到設計中。而無需修改現有的代碼。
即:方法還是原方法,但是在原方法前後加點東西。

 可以整合若幹類來提供客戶需要的介面。
即將一個不相容的介面對象包裝起來,變成相容的對象。

 

當一個方法調用被委托給裝飾者的時候,
不知道有多少其他裝飾者已經處理過這個調用了。

裝飾者有點像遞歸套娃,你不知道當前是第幾層。

可以使用新的庫和子集合,而無需改變任何代碼。適配器會按照原介面給你包好。

 

拓展包裝對象的行為和責任,不是簡單的傳送

一定進行介面的裝換。

 

 

示例:多支付系統解決方案

通過加入適配器模式,訂單 Service 在進行支付時調用的不再是外部的支付介面,而是“支付方式”介面,與外部系統解耦。

只要保證“支付方式”介面是穩定的,那麼訂單 Service 就是穩定的。比如:

當支付寶支付介面發生變更時,影響的只限於支付寶 Adapter;

當微信支付介面發生變更時,影響的只限於微信支付 Adapter;

當要增加一個新的支付方式時,只需要再寫一個新的 Adapter。

日後不論哪種變更,要修改的代碼範圍縮小了,維護成本自然降低了,代碼質量就提高了。

 

問題:

在劃分微服務過程中,經常糾結,對外的功能有沒有必要抽象出一個代理服務。專門負責與某廠商對接。

根據適配器的理念,如果這個代理服務如果能抽象出標準的介面,那他就有獨立的必要。核心業務服務只和代理服務交互,代理服務屏蔽外部廠商的不同。

日後廠商有變動,我們修改範圍控制在代理服務內,不會涉及到核心業務服務。

 

例如:我們一般會開發一個統一支付的服務,這個服務對接各個支付系統,對內提供標準支付介面,業務服務只和統一支付交互。

同時統一支付還能幫助我們處理對賬、過期自動退款等功能。讓業務系統穩定,輕便。

 

本文來自博客園,作者:wanglifeng,轉載請註明原文鏈接: https://www.cnblogs.com/wanglifeng717/p/16348529.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 導讀: 作為一種基礎的數據結構,圖數據的應用場景無處不在,如社交、風控、搜廣推、生物信息學中的蛋白質分析等。如何高效地對海量的圖數據進行存儲、查詢、計算及分析,是當前業界熱門的方向。本文將介紹位元組跳動自研的圖資料庫ByteGraph及其在位元組內部的應用和挑戰。 本文將圍繞以下五點展開: 瞭解圖資料庫 ...
  • 新 能 力 3D Engine 3D Engine提供高性能、高畫質、高擴展性的實時3D引擎,並提供便捷高效的可視化開發工具。開發者可基於華為的3D Studio開發工具,通過圖形和渲染、動畫、UI等功能模塊,打造高品質的3D應用。同時引擎也開放三方插件的能力,致力協同全球圖形領域的伙伴研究創新技術 ...
  • 前言 ​ 採集應用程式崩潰信息,主要分為以下兩種場景: ​ NSException 異常 ​ Unix 信號異常 一、NSException 異常 ​ NSException 異常是 Objective-C 代碼拋出的異常。在 iOS 應用程式中,最常見就是通過 @throw 拋出的異常。比如,常見 ...
  • 當用戶有跨語種交流或內容翻譯的需求時,應用需要能自動檢測文本的語種再進行翻譯。 HMS Core機器學習服務的語種檢測服務提供線上語種檢測和離線語種檢測,既支持檢測單語種文本,也支持檢測混合語種文本,涵蓋南非荷蘭語、阿拉伯語等百種語言。接入語種檢測服務,App可以輕鬆實現翻譯語種檢測、網頁語種檢測, ...
  • vue的虛擬dom和diff演算法 1.虛擬dom 虛擬dom,我的理解就是通過js對象的方式來具體化每一個節點,把dom樹上面的每個節點都變為對象里的一個元素,元素的子元素變為子節點,節點上面的class、id、attribute等屬性變為data內的值,然後通過dom上面的createElemen ...
  • 一、問題描述 使得填表單位那一行字與下麵的表格左對齊,對錶格使用css中的margin:auto實現了居中,但是對那一行字(用div包裹的)使用margin:auto不生效;並且2021年下麵對應的月份是動態變化的,月份的個數不定; 二、解決方案 (1)把那行字放在表格裡,再把這行表格的邊框去掉; ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 對uni.request的一些共同參數進行簡單的封裝,減少重覆性數據請求代碼。方便全局調用。 先在目錄下創建 utils 和 common 這2個文件夾 utils 是存放工具類的,common 用來放置常用方法的 之後在utils 中創 ...
  • 聲明 這個程式只是用來演示查看後臺和對接後臺的原理和過程,供代碼學習參考,請勿濫用! 起因 因為上海疫情,我們所有的活動都得線上上完成,作為一個新時代好學生,我該做點什麼了。 我的學校選擇的上課平臺是希沃的立知課堂,教師端是希沃白板電腦客戶端,學生端是網頁端、微信小程式端、移動設備軟體端。 觀察後臺 ...
一周排行
    -Advertisement-
    Play Games
  • 什麼是工廠模式 工廠模式是最常用的設計模式之一,屬於創建型模式。 有點: 解耦,可以把對象的創建和過程分開 減少代碼量,易於維護 什麼時候用? 當一個抽象類有多個實現的時候,需要多次實例化的時候,就要考慮使用工廠模式。 比如:登錄的抽象類ILoginBusiness,它有2個實現,一個用用戶名密碼登 ...
  • 這次iNeuOS升級主要升級圖形渲染引擎和增加豐富的圖元信息,可以很快的方案應用。總共增加41個通用和行業領域的圖元應用,增加2154個圖元信息,現在iNeuOS視圖建模功能模塊總共包括5894個行業圖元信息。現在完全支持製作高保真的工藝流程和大屏展示效果。 ...
  • 效果圖先附上: 首先 這是我是參考 教程:使用 SignalR 2 和 MVC 5 實時聊天 | Microsoft Docs 先附上教程: 在“添加新項 - SignalRChat”中,選擇 InstalledVisual> C#>WebSignalR>,然後選擇 SignalR Hub 類 (v ...
  • 一、前言 項目中之前涉及到胎兒心率圖曲線的繪製,最近項目中還需要添加心電曲線和血樣曲線的繪製功能。今天就來分享一下心電曲線的繪製方式; 二、正文 1、胎兒心率曲線的繪製是通過DrawingVisual來實現的,這裡的心電曲線我也是採用差不多相同的方式來實現的,只是兩者曲線的數據有所區別。心電圖的數據 ...
  • 安裝 Redis # 首先安裝依賴gcc, 後面需要使用make編譯redis yum install gcc -y # 進入 /usr/local/src 目錄, 把源碼下載到這裡 cd /usr/local/src # 下載 redis 7.0.2 的源碼,github被牆,可以使用國內的地址 ...
  • Redis 的定義? 百度百科: Redis(Remote Dictionary Server ),即遠程字典服務,是一個開源的使用ANSI C語言編寫、支持網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。 中文官網: Redis是一個開源(BSD許可),記憶體存 ...
  • 事情的起因是收到了一位網友的請求,他的java課設需要設計實現迷宮相關的程式——如標題概括。 我這邊不方便透露相關信息,就只把任務要求寫出來。 演示視頻指路👉: 基於JavaFX圖形界面的迷宮程式演示_嗶哩嗶哩_bilibili 完整代碼鏈接🔎: 網盤:https://pan.baidu.com ...
  • Python中的字典 Python中的字典是另一種可變容器模型,且可存儲任意類型對象。鍵值使用冒號分割,你可以看成是一串json。 常用方法 獲取字典中的值 dict[key] 如果key不存在會報錯,建議使用dict.get(key),不存在返回None 修改和新建字典值 dict[key]=va ...
  • 迎面走來了你的面試官,身穿格子衫,挺著啤酒肚,髮際線嚴重後移的中年男子。 手拿泡著枸杞的保溫杯,胳膊夾著MacBook,MacBook上還貼著公司標語:“加班使我快樂”。 面試官: 看你簡歷上用過MySQL,問你幾個簡單的問題吧。什麼是聚簇索引和非聚簇索引? 這個問題難不住我啊。來之前我看一下一燈M ...
  • tunm二進位協議在python上的實現 tunm是一種對標JSON的二進位協議, 支持JSON的所有類型的動態組合 支持的數據類型 基本支持的類型 "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "varint", "float", "s ...