【設計模式】適配器模式 Adapter Pattern

適配器模式在軟件開發界使用及其普遍,在工業界,現實中也是家常便飯。好比手機充電器,筆記本充電器,廣播接收器,電視接收器等等。都是適配器。html

image 

適配器主要做用是讓原本不兼容的兩個事物兼容和諧的一塊兒工做。好比, 一般咱們使用的交流電都是220v,可是手機電池可以承載的5v電壓,所以直接將咱們使用的220v交流電直接接到手機上,手機確定就壞,第二個做用是匹配交流電插座和手機充電接口不兼容的問題,所以,一個充電器解決了電和手機存在的倆個問題(電壓和接口),並使其正常工做。git

那麼在軟件開發過程當中也會常常碰到這樣的問題,那就是系統都開發好了,忽然有一天客戶說要接入其它系統的數據,可是當你看到接口接入文檔時發現兩邊的接口都對不上,數據結構定義的也不同,好比說,咱們系統中有個定義的方法叫 GetUserByUserId(int userId) 返回的數據結構是這樣定義的:數據庫

image

public class User
{
    public int UserId { get; set; }
    public string UserName { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }
    public string TelNumber{get;set;}
    public string MobileNumber { get; set; }
}

而對方系統接口也定義了一個方法叫 GetUserInfoById(int id)  可是返回的數據結構長這樣子:數據結構

image

public class UserInfo
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public Address Address { get; set; }
    public string TelphoneNumber { get; set; }
    public string CellphoneNumber { get; set; }
}
public class Address
{
    public Country Country { get; set; }
    public string City{get;set;}
    public  string Street{get;set;}
    public string Number { get; set; }
    public Location Location { get; set; }
    public string PostCode { get; set; }
       
}

public class Country
{
    public string Name { get; set; }
    public string Number { get; set; }
    public string Abbreviation { get; set; }
}
public class Location
{
    public long Longitude { get; set; }
    public long Latitude { get; set; }
}

 

 

那咱們該怎麼對接這個外部系統的用戶到咱們的系統中來呢? 這就是了咱們要討論的適配器(Adapter) 模式了。app

1、適配器模式的定義

適配器模式(Adapter Pattern):將一個接口轉換成客戶但願的另外一個接口,使接口不兼容的那些類能夠一塊兒工做,其別名爲包裝器(Wrapper)。適配器模式既能夠做爲類結構型模式,也能夠做爲對象結構型模式。ide

2、適配器模式的結構圖

image 一、Target(目標抽象類):

目標抽象類定義客戶所需接口,能夠是一個抽象類或接口,也能夠是具體類。this

二、Adapter(適配器類):

適配器能夠調用另外一個接口,做爲一個轉換器,對Adaptee和Target進行適配,適配器類是適配器模式的核心,在對象適配器中,它經過繼承Target並關聯一個Adaptee對象使兩者產生聯繫。在類機構中他直接繼承target接口和一個Adaptee類來實現。     spa

三、Adaptee(適配者類):

適配者即被適配的角色,它定義了一個已經存在的接口,這個接口須要適配,適配者類通常是一個具體類,包含了客戶但願使用的業務方法,在某些狀況下可能沒有適配者類的源代碼。code

3、適配器模式的經典實現

public abstract class Target
{
    public abstract void Request();
}
public class Adaptee
{
    public void specificRequest()
    {
        Console.WriteLine("I'm Adaptee method");
    }
}
public class Adapter : Target
{
    private Adaptee _adaptee;
    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }
    public override void Request()
    {
        _adaptee.specificRequest();
    }
}

客戶端調用代碼:orm

static void Main(string[] args)
{
    Target target = new Adapter(new Adaptee());
    target.Request();

    Console.ReadKey();
}

結果輸出:

image

4、適配器模式實例

討論完適配器模式的概念後咱們來使用適配器模式解決文中開頭提出來的問題, 怎麼將UserProvider 接口適配到IUserService接口(注意:這裏所說的接口是廣義的接口,而不是C#中用I開頭定義的接口),有了適配器模式如今就變得簡單了,IUserService 接口就是適配器模式的目標抽象類(Target), UserProvider 就是適配器模式的適配者類(Adaptee),咱們新建一個適配器類UserAdapter (Adapter) 就可讓它們工做了。結構圖以下:

image

對象結構型實現:

在UserPorvider類中實例化兩個UserInfo對象(模擬數據存儲在數據庫中),假設它就是要接入的數據。那麼代碼就是這樣子:

public class User
{
    public int UserId { get; set; }
    public string UserName { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }
    public string TelNumber { get; set; }
    public string MobileNumber { get; set; }
}

public class UserInfo
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public Address Address { get; set; }
    public string TelphoneNumber { get; set; }
    public string CellphoneNumber { get; set; }
}
public class Address
{
    public Country Country { get; set; }
    public string City { get; set; }
    public string Street { get; set; }
    public string Number { get; set; }
    public Location Location { get; set; }
    public string PostCode { get; set; }

}

public class Country
{
    public string Name { get; set; }
    public string Number { get; set; }
    public string Abbreviation { get; set; }
}
public class Location
{
    public double Longitude { get; set; }
    public double Latitude { get; set; }
}

public class UserProvider
{
    private static IDictionary<int, UserInfo> innerDictionary = new Dictionary<int, UserInfo>();
    static UserProvider()
    {
        innerDictionary.Add(1, new UserInfo
        {
            FirstName = "Kevin",
            LastName = "Durnt",
            Age = 30,
            CellphoneNumber = "136xxxx1234",
            TelphoneNumber = "010-34567890",
            Id = 1,
            Address = new Address
            {
                City = "Xi'an",
                Number = "24",
                PostCode = "710000",
                Street = "Gao xin",
                Country = new Country
                {
                    Abbreviation = "zh-CN",
                    Name = "China",
                    Number = "018",
                },
                Location = new Location
                {
                    Latitude = 31.123456,
                    Longitude = 35.23456,
                }
            }
        });
        innerDictionary.Add(2, new UserInfo
        {
            FirstName = "Kobe",
            LastName = "Durnt",
            Age = 39,
            CellphoneNumber = "139xxxx1234",
            TelphoneNumber = "010-24567890",
            Id = 2,
            Address = new Address
            {
                City = "Xi'an",
                Number = "24",
                PostCode = "710000",
                Street = "Gao xin",
                Country = new Country
                {
                    Abbreviation = "zh-CN",
                    Name = "China",
                    Number = "018",
                },
                Location = new Location
                {
                    Latitude = 31.123456,
                    Longitude = 35.23456
                }
            }
        });
    }
    public UserInfo GetUserById(int id)
    {
        return innerDictionary[id];
    }
}

public interface IUserService
{
    User GetUserByUserId(int userId);
}
public class UserAdapter : IUserService
{
    private UserProvider _userProvider;
    public UserAdapter(UserProvider userProvider)
    {
        _userProvider = userProvider;
    }
    public User GetUserByUserId(int userId)
    {
        UserInfo userInfo = _userProvider.GetUserById(userId);

        User user = new User();
        user.UserId = userInfo.Id;
        user.UserName = string.Format("{0} {1}", userInfo.FirstName, userInfo.LastName);
        user.TelNumber = userInfo.TelphoneNumber;
        user.MobileNumber = userInfo.CellphoneNumber;
        user.Age = userInfo.Age;
        user.Address = string.Format("{0} {1}, {2},{3}, Location:{4}, {5}",
            userInfo.Address.Street,
            userInfo.Address.Number,
            userInfo.Address.Country.Name,
            userInfo.Address.PostCode,
            userInfo.Address.Location.Latitude,
            userInfo.Address.Location.Longitude);

        return user;
    }
}

客戶端調用:

static void Main(string[] args)
{
    IUserService target = new UserAdapter(new UserProvider());
    User user=target.GetUserByUserId(1);

    Console.WriteLine("UserId: " + user.UserId);
    Console.WriteLine("UserName: " + user.UserName);
    Console.WriteLine("Age: " + user.Age);
    Console.WriteLine("TelNumber: " + user.TelNumber);
    Console.WriteLine("MobileNumber: " + user.MobileNumber);
    Console.Write("Address: " + user.Address);

    Console.ReadKey();
}

輸出結果:

image 

反射+配置實現熱替換

爲了達到靈活配置的目的,其實在不少時候,客戶端不須要知道第三方接口長什麼樣,所以,在適配器類裏面能夠隱藏掉調用第三方代碼的細節,那麼對Adaptee的實例化直接放到Adapter裏,所以,客戶端直接依賴高層抽象Target就能夠了,這樣就能夠隨時將Adaptee 替換掉, 而且咱們可使用配置+反射來達到這種動態替換的效果。下面咱們稍加修改UserAdapter類,並加一個配置來完成這個設想:

A、在UserAdapter構造裏去掉類型爲UserProvider 的參數,UserAdapter變成這樣了:

public class UserAdapter : IUserService
{
    private UserProvider _userProvider;
    public UserAdapter()
    {
        _userProvider = new UserProvider();
    }
    public User GetUserByUserId(int userId)
    {
        UserInfo userInfo = _userProvider.GetUserById(userId);

        User user = new User();
        user.UserId = userInfo.Id;
        user.UserName = string.Format("{0} {1}", userInfo.FirstName, userInfo.LastName);
        user.TelNumber = userInfo.TelphoneNumber;
        user.MobileNumber = userInfo.CellphoneNumber;
        user.Age = userInfo.Age;
        user.Address = string.Format("{0} {1}, {2},{3}, Location:{4}, {5}",
            userInfo.Address.Street,
            userInfo.Address.Number,
            userInfo.Address.Country.Name,
            userInfo.Address.PostCode,
            userInfo.Address.Location.Latitude,
            userInfo.Address.Location.Longitude);

        return user;
    }
}

B. 在App.config中加入以下配置:

<appSettings>
  <add key="Adapter" value="DesignPattern.Adapter.UserAdapter"/>
</appSettings>

C.在代碼中使用反射獲得具體的Adapter 類,而後調用相應方法:

static void Main(string[] args)
{    
    var setting = ConfigurationSettings.AppSettings["Adapter"];
    Assembly assembly=Assembly.GetExecutingAssembly();
    IUserService target = assembly.CreateInstance(setting) as IUserService;
    
    User user=target.GetUserByUserId(1);

    Console.WriteLine("UserId: " + user.UserId);
    Console.WriteLine("UserName: " + user.UserName);
    Console.WriteLine("Age: " + user.Age);
    Console.WriteLine("TelNumber: " + user.TelNumber);
    Console.WriteLine("MobileNumber: " + user.MobileNumber);
    Console.Write("Address: " + user.Address);

    Console.ReadKey();
}

結果:

image

類結構實現

上面的adapter是對象結構型的實現。adapter 還能夠是類結構型模式, 類適配器和對象適配器的不一樣之處就是適配器與適配者的關係不一樣。對象適配器,適配器與適配者之間是關聯關係,而類適配器,適配器與適配者之間是繼承關係。

下來咱們使用類結構來實現上面的需求:

public class UserClassAdapter : UserProvider, IUserService
{      
    public User GetUserByUserId(int userId)
    {
        UserInfo userInfo =this.GetUserById(userId);

        User user = new User();
        user.UserId = userInfo.Id;
        user.UserName = string.Format("{0} {1}", userInfo.FirstName, userInfo.LastName);
        user.TelNumber = userInfo.TelphoneNumber;
        user.MobileNumber = userInfo.CellphoneNumber;
        user.Age = userInfo.Age;
        user.Address = string.Format("{0} {1}, {2},{3}, Location:{4}, {5}",
            userInfo.Address.Street,
            userInfo.Address.Number,
            userInfo.Address.Country.Name,
            userInfo.Address.PostCode,
            userInfo.Address.Location.Latitude,
            userInfo.Address.Location.Longitude);

        return user;
    }
}

僅僅只須要須要將UserAdapter和UserProvider的關係改爲集成就能夠了。 輸出結果和以前是同樣的。

在C#中因爲類只能是單繼承關係, 一個類只能繼承自一個類,但能夠繼承多個接口,若是Target角色是類,Adaptee也是類的話就不能使用類結構模式。

5、適配器模式的缺點

A. 類結構適配器和對象結構適配器共有的優勢:

  1. 將目標類和適配者類解耦,經過引入一個適配器類來重用現有的適配者類,無須修改原有結構。

  2.  增長了類的透明性和複用性將具體的業務實現過程封裝在適配者類中,對於客戶端類而言是透明的,並且提升了適配者的複用性,同一個適配者類能夠在多個不一樣的系統中複用。
  3. 靈活性和擴展性都很是好,經過使用配置文件,能夠很方便地更換適配器,也能夠在不修改原有代碼的基礎上增長新的適配器類,徹底符合「開閉原則OCP」。

B.除了共有的優勢外,類適配器還有以下優勢:

  1. 因爲適配器類是適配者類的子類,所以能夠在適配器類中置換一些適配者的方法,使得適配器的靈活性更強。

C.除了共有的優勢外,對象適配器還有以下優勢:

  1. 一個對象適配器能夠把多個不一樣的適配者適配到同一個目標
  2. 能夠適配一個適配者的父類,因爲適配器和適配者之間是關聯關係,根據「里氏代換原則LSP」,適配者的子類也可經過該適配器進行適配。

6、適配器模式的缺點

A.類適配器的缺點

  1. 因爲C#不支持類的多繼承,一次最多隻能適配一個適配者類,不能同時適配多個適配者。
  2. 適配者類不能爲最終類,C#中不能爲sealed類,這樣沒法繼承了。
  3. 在C#語言中,類適配器模式中的目標抽象類只能爲接口,不能爲類,其使用有必定的侷限性。其實這些都是單類繼承的語言特性形成的。

B.對象適配器的缺點

  1. 與類適配器模式相比,要在適配器中置換適配者類的某些方法比較麻煩。若是必定要置換掉適配者類的一個或多個方法,能夠先作一個適配者類的子類,將適配者類的方法置換掉,而後再把適配者類的子類當作真正的適配者進行適配,實現過程較爲複雜, 另外一種方法是直接在適配器類中將相應的方法從新實現掉。

7、適配器模式的使用場景

  1. 系統須要使用一些現有的類,而這些類的接口(如方法名)不符合系統的須要,甚至沒有這些類的源代碼。
  2. 想建立一個能夠重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在未來引進的類一塊兒工做。
  3. 在調用第三方接口是,和現有的系統模型不配是可使用Adapter模式將模型轉化一直。

8、擴展-Default Adapter Parttern

在使用適配器模式的時候常常會碰到一類場景,就是已有的類的全部方法都都正常工做,可是隻有那麼幾個方法須要調用第三方的幾個系統提供的API,這時咱們使用繼承在適配器類裏從新實現一遍工做量太大。這就要使用適配器模式的一個變體。這就是默認適配器,默認適配器上Target類是一個具體的類,實現大多數方法,甚至全部方法,但都是成虛方法,這樣在適配器中有選擇的重寫Target中的方法就能夠了。這種變體在實踐中繼承使用。也是頗有用的一種模式。

 

會不會存在一個多功能的雙向適配器呢(好比A系統對接B系統,同時B系統也要對接A系統)? 若是用C#該如何實現呢?

相關文章
相關標籤/搜索