适配器模式在软件开发界使用及其普遍,在工业界,现实中也是家常便饭。好比手机充电器,笔记本充电器,广播接收器,电视接收器等等。都是适配器。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#该如何实现呢?