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

来源: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
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...