適配器模式在軟件開發界使用及其普遍,在工業界,現實中也是家常便飯。好比手機充電器,筆記本充電器,廣播接收器,電視接收器等等。都是適配器。html
適配器主要做用是讓原本不兼容的兩個事物兼容和諧的一塊兒工做。好比, 一般咱們使用的交流電都是220v,可是手機電池可以承載的5v電壓,所以直接將咱們使用的220v交流電直接接到手機上,手機確定就壞,第二個做用是匹配交流電插座和手機充電接口不兼容的問題,所以,一個充電器解決了電和手機存在的倆個問題(電壓和接口),並使其正常工做。git
那麼在軟件開發過程當中也會常常碰到這樣的問題,那就是系統都開發好了,忽然有一天客戶說要接入其它系統的數據,可是當你看到接口接入文檔時發現兩邊的接口都對不上,數據結構定義的也不同,好比說,咱們系統中有個定義的方法叫 GetUserByUserId(int userId) 返回的數據結構是這樣定義的:數據庫
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) 可是返回的數據結構長這樣子:數據結構
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
適配器模式(Adapter Pattern):將一個接口轉換成客戶但願的另外一個接口,使接口不兼容的那些類能夠一塊兒工做,其別名爲包裝器(Wrapper)。適配器模式既能夠做爲類結構型模式,也能夠做爲對象結構型模式。ide
目標抽象類定義客戶所需接口,能夠是一個抽象類或接口,也能夠是具體類。this
適配器能夠調用另外一個接口,做爲一個轉換器,對Adaptee和Target進行適配,適配器類是適配器模式的核心,在對象適配器中,它經過繼承Target並關聯一個Adaptee對象使兩者產生聯繫。在類機構中他直接繼承target接口和一個Adaptee類來實現。 spa
適配者即被適配的角色,它定義了一個已經存在的接口,這個接口須要適配,適配者類通常是一個具體類,包含了客戶但願使用的業務方法,在某些狀況下可能沒有適配者類的源代碼。code
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(); }
結果輸出:
討論完適配器模式的概念後咱們來使用適配器模式解決文中開頭提出來的問題, 怎麼將UserProvider 接口適配到IUserService接口(注意:這裏所說的接口是廣義的接口,而不是C#中用I開頭定義的接口),有了適配器模式如今就變得簡單了,IUserService 接口就是適配器模式的目標抽象類(Target), UserProvider 就是適配器模式的適配者類(Adaptee),咱們新建一個適配器類UserAdapter (Adapter) 就可讓它們工做了。結構圖以下:
在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(); }
輸出結果:
爲了達到靈活配置的目的,其實在不少時候,客戶端不須要知道第三方接口長什麼樣,所以,在適配器類裏面能夠隱藏掉調用第三方代碼的細節,那麼對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(); }
結果:
上面的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也是類的話就不能使用類結構模式。
將目標類和適配者類解耦,經過引入一個適配器類來重用現有的適配者類,無須修改原有結構。
在使用適配器模式的時候常常會碰到一類場景,就是已有的類的全部方法都都正常工做,可是隻有那麼幾個方法須要調用第三方的幾個系統提供的API,這時咱們使用繼承在適配器類裏從新實現一遍工做量太大。這就要使用適配器模式的一個變體。這就是默認適配器,默認適配器上Target類是一個具體的類,實現大多數方法,甚至全部方法,但都是成虛方法,這樣在適配器中有選擇的重寫Target中的方法就能夠了。這種變體在實踐中繼承使用。也是頗有用的一種模式。
會不會存在一個多功能的雙向適配器呢(好比A系統對接B系統,同時B系統也要對接A系統)? 若是用C#該如何實現呢?