一起學習設計模式--07.適配器模式

前言

有的筆記本電腦工作電壓是20V,但是國家標準用電電壓是220V,如何讓20V的筆記本電腦能夠在220V的電壓下工作?答案是引入一個電源適配器,俗稱充電器/變壓器,有了這個電源適配器,生活用電和筆記本電腦即可兼容。

在軟件開發中,也存在類似的不兼容的情況,也可以像引入電源適配器一樣引入一個被稱爲適配器的角色來協調這些存在不兼容的結構,這種設計方案就是適配器模式。

一、沒有源碼的算法庫

背景

A公司以前開發了一個算法庫,裏面包含了一些常用的算法,如排序和查找等算法,在進行各類軟件開發時經常需要重用該算法庫中的算法。在爲某個學校開發教務管理系統時,開發人員發現需要對學生成績進行排序和查找。該系統的設計人員已經開發了一個成績操作接口 IScoreOperation,在該接口中聲明瞭排序方法 Sort(int[]) 和查找方法 Search(int[], int) 。爲了提高排序和查找效率,開發人員決定重用算法庫中的快速排序算法類 QuickSort 和二分查找算法類 BinarySearch ,其中 QuickSort 的 QuickSort(int[]) 方法實現了快速排序,BinarySearch 的 BinarySearch(int[], int) 方法實現了二分查找。
由於某些原因,現在A公司的開發人員已經找不到該算法庫的源代碼,無法直接通過複製和粘貼來重用其中的代碼。部分開發人員已經針對 IScoreOperation 接口編程,如果再要求對該接口進行修改或要求大家直接使用 QuickSort 類和 BinarySearch類將導致大量代碼需要修改。
A公司開發人員面對這個沒有源碼的算法庫,遇到一個幸福而又煩惱的問題:如何在既不修改現有接口又不需要任何算法庫代碼的基礎上實現算法庫的重用。

通過分析,不難得知,現在A公司面臨的問題有點類似之前提到的電壓的問題。成績操作接口 IScoreOperation 好比只支持20V電壓的筆記本電腦,而算法庫好比220V的家庭用電,這兩部分都沒有辦法再進行修改,而且它們原本是兩個完全不相關的結構。如圖:

現在需要 IScoreOperation 接口能夠和已有算法庫一起工作,讓他們在同一個系統中能夠兼容。最好的實現方法是增加一個類似的電源適配器的適配器角色,通過適配器類協調這兩個原本不兼容的結構。

二、適配器的概述

適配器模式可以將一個類的接口和另一個類的接口匹配起來,而無需修改原來的適配者接口和抽象目標類接口。
適配器模式的定義如下:

適配器模式(Adapter Pattern):將一個接口轉換成客戶希望的另一個接口,使接口不兼容的那些類可以一起工作,其別名爲包裝器(Wrapper)。適配器模式即可以作爲類結構型模式,也可以作爲對象結構型模式。

在適配器模式中,通過增加一個新的適配器類來解決接口不兼容的問題,使得原本沒有任何關係的類可以協同工作。根據適配器類和適配者類的關係的不同,適配器模式可以分爲對象適配器模式和類適配器模式兩種。在對象適配器模式中,適配器與適配者之間是關聯關係;在類適配器模式中,適配器與適配者之間是繼承(或實現)關係。在實際開發中,對象適配器模式的使用頻率更高,其結構如圖:

上圖可以看到包含3個角色:

  1. Target(目標抽象類):目標抽象類定義客戶所需要的接口,可以是一個抽象類或接口,也可以是具體類。
  2. Adapter(適配器類):適配器可以調用另一個接口,作爲一個轉換器,對Adaptee和Target進行適配。適配器類是適配器模式的核心,在對象適配器模式中,它可以通過繼承Target並關聯一個Adaptee對象使二者產生聯繫。
  3. Adaptee(適配者類):適配者即被適配的角色,它定義了一個已經存在的接口,這個接口需要適配。適配者一般是一個具體類,包含了客戶希望使用的業務方法,在某些情況下可能沒有適配者類的源代碼

上圖中可以看出,客戶端需要調用Request()方法,但是適配者類Adaptee中沒有該該方法,但是有一個SpecificRequest() 卻是它需要的方法,兩個名稱不一樣怎麼辦?這就需要提供一個適配器類Adapter來進行銜接,在適配器類中的Request()方法中去調用適配者的 SpecificRequest() 方法。典型對象適配器代碼如下:

public class Adapter : Target {
	private Adaptee _adaptee;
    
    public Adapter(Adaptee adaptee){
    	_adaptee = adaptee;
    }
    
    public void Request(){
    	_adaptee.SpecificRequest();//轉發調用
    }
}

三、完整解決方案

開發人員決定使用適配器模式來重用算法庫中的算法。其基本結構如圖:

IScoreOperation 接口充當抽象目標,QuickSort 和 BinarySearch 充當適配者,OperationAdapter 充當適配器,完整代碼如下:

    /// <summary>
    /// 抽象成績操作類:目標接口
    /// </summary>
    public interface IScoreOperation
    {
        /// <summary>
        /// 成績排序
        /// </summary>
        int[] Sort(int[] array);
        /// <summary>
        /// 成績查找
        /// </summary>
        int Search(int[] array, int key);
    }

    /// <summary>
    /// 快速排序類:適配者
    /// </summary>
    public class QuickSortService
    {
        public int[] QuickSort(int[] array)
        {
            Sort(array, 0, array.Length - 1);
            return array;
        }

        public void Sort(int[] array, int p, int r)
        {
            int q = 0;
            if (p < r)
            {
                q = Partiion(array, p, r);
                Sort(array, p, q - 1);
                Sort(array, q + 1, r);
            }
        }

        public int Partiion(int[] a ,int p ,int r)
        {
            int x = a[r];
            int j = p - 1;
            for(var i = p; i <= r - 1; i++)
            {
                if (a[i] <= x)
                {
                    j++;
                    Swap(a, j, i);
                }
            }
            Swap(a, j + 1, r);
            return j + 1;
        }

        public void Swap(int[] a,int i,int j)
        {
            int t = a[i];
            a[i] = a[j];
            a[j] = t;
        }
    }

    /// <summary>
    /// 二分查找類:適配者
    /// </summary>
    public class BinarySearchService
    {
        public int BinarySearch(int[] array, int key)
        {
            int low = 0;
            int high = array.Length - 1;
            while (low <= high)
            {
                int mid = (low + high) / 2;
                int midVal = array[mid];
                if (midVal < key)
                {
                    low = mid + 1;
                }
                else if (midVal > key)
                {
                    high = mid - 1;
                }
                else
                {
                    return 1;
                }
            }
            return -1;
        }
    }

    /// <summary>
    /// 操作適配器:適配器類
    /// </summary>
    public class OperationAdapter : IScoreOperation
    {
        private QuickSortService _quickSort;
        private BinarySearchService _binarySearch;

        public OperationAdapter()
        {
            _quickSort = new QuickSortService();
            _binarySearch = new BinarySearchService();
        }

        public int[] Sort(int[] array)
        {
            return _quickSort.QuickSort(array);
        }

        public int Search(int[] array, int key)
        {
            return _binarySearch.BinarySearch(array, key);
        } 
    }

將適配器的類名添加到配置文件中,如果要使用其他的排序或算法,可以新增一個適配器,然後修改配置文件中的適配器類名爲新的適配器即可,這樣就無需修改原有代碼

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="AdapterName" value="LXP.DesignPattern.Adapter.OperationAdapter"/>
  </appSettings>
</configuration>

App.Config 幫助類

    public class AppConfigHelper
    {
        public static object GetAdapter()
        {
            try
            {
                var adapterName = ConfigurationManager.AppSettings["AdapterName"];
                var type = Type.GetType(adapterName);

                return type == null ? null : Activator.CreateInstance(type);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return null;
        }
    }

客戶端代碼:

    class Program
    {
        static void Main(string[] args)
        {
            var operation = (OperationAdapter)AppConfigHelper.GetAdapter();
            int[] scores = { 84, 76, 50, 65, 90, 91, 88, 96 };
            int[] result;
            int score;

            Console.WriteLine("成績排序結果:");
            result = operation.Sort(scores);

            //便利輸出成績
            foreach (var i in result)
            {
                Console.Write(i + ",");
            }
            Console.WriteLine();

            Console.WriteLine("查找成績90:");
            score = operation.Search(result, 90);

            if (score != -1)
            {
                Console.WriteLine("找到成績90");
            }
            else
            {
                Console.WriteLine("沒有找到成績90");
            }

            Console.WriteLine("查找成績92:");
            score = operation.Search(result, 92);

            if (score != -1)
            {
                Console.WriteLine("找到成績92");
            }
            else
            {
                Console.WriteLine("沒有找到成績92");
            }

            Console.ReadKey();

        }
    }

編譯並運行輸出結果:

四、適配器模式總結

適配器模式將現有接口轉化爲客戶類所期望的接口,實現了對現有類的複用。他是一種使用頻率非常高的設計模式。

1.主要優點

  1. 將目標類和適配者類解耦。通過引入一個適配器類來重用現有的適配者類,無需修改原有結構。
  2. 增加了類的透明性和複用性。將具體的業務實現過程封裝在適配者類中,對於客戶端而言是透明的,提高了適配者類的複用性,同一個適配者類可以在多個不同的系統中複用。
  3. 靈活性和擴展性都非常好。通過使用配置文件,可以很方便的更換適配器,也可以在不修改原有代碼的基礎上增加新的適配器類,完全符合開閉原則。

對象適配器的優點:

  1. 一個對象適配器可以把多個不同的適配者適配到同一個目標。
  2. 可以適配一個適配者的子類。由於適配器和適配者之間是關聯關係,根據里氏替換原則,適配者的子類也可以通過該適配器進行適配。

2.適用場景

  1. 系統需要使用一些現有類,而這些類的接口不符合系統的需要,甚至沒有這些類的源代碼。
  2. 想創建一個可以重複使用的類,用於與一些彼此之間的沒有太大關聯的類,包括一些可能在將來引進的類一起工作。

如果您覺得這篇文章有幫助到你,歡迎推薦,也歡迎關注我的公衆號。

示例代碼:

https://github.com/crazyliuxp/DesignPattern.Simples.CSharp

參考資料:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章