适配器模式在软件开发界使用及其广泛,在工业界,现实中也是屡见不鲜。比如手机充电器,笔记本充电器,广播接收器,电视接收器等等。都是适配器。

适配器主要作用是让本来不兼容的两个事物兼容和谐的一起工作。比如, 通常我们使用的交流电都是220v,但是手机电池能够承载的5v电压,因此直接将我们使用的220v交流电直接接到手机上,手机肯定就坏,第二个作用是匹配交流电插座和手机充电接口不兼容的问题,因此,一个充电器解决了电和手机存在的俩个问题(电压和接口),并使其正常工作。

那么在软件开发过程中也会经常碰到这样的问题,那就是系统都开发好了,突然有一天客户说要接入其它系统的数据,但是当你看到接口接入文档时发现两边的接口都对不上,数据结构定义的也不一样,比如说,我们系统中有个定义的方法叫 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) 模式了。

一、适配器模式的定义

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

二、适配器模式的结构图

1、Target(目标抽象类):

目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。

2、Adapter(适配器类):

适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。在类机构中他直接继承target接口和一个Adaptee类来实现。

3、Adaptee(适配者类):

适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

三、适配器模式的经典实现

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();}
}

客户端调用代码:

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也是类的话就不能使用类结构模式。

五、适配器模式的缺点

A. 类结构适配器和对象结构适配器共有的优点:

  1. 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。

  2.  增加了类的透明性和复用性将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  3. 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则OCP”。

B.除了共有的优点外,类适配器还有如下优点:

  1. 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

C.除了共有的优点外,对象适配器还有如下优点:

  1. 一个对象适配器可以把多个不同的适配者适配到同一个目标
  2. 可以适配一个适配者的父类,由于适配器和适配者之间是关联关系,根据“里氏代换原则LSP”,适配者的子类也可通过该适配器进行适配。

六、适配器模式的缺点

A.类适配器的缺点

  1. 由于C#不支持类的多继承,一次最多只能适配一个适配者类,不能同时适配多个适配者。
  2. 适配者类不能为最终类,C#中不能为sealed类,这样无法继承了。
  3. 在C#语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。其实这些都是单类继承的语言特性造成的。

B.对象适配器的缺点

  1. 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂, 另一种方法是直接在适配器类中将相应的方法重新实现掉。

七、适配器模式的使用场景

  1. 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  2. 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
  3. 在调用第三方接口是,和现有的系统模型不配是可以使用Adapter模式将模型转化一直。

八、扩展-Default Adapter Parttern

在使用适配器模式的时候经常会碰到一类场景,就是已有的类的所有方法都都正常工作,但是只有那么几个方法需要调用第三方的几个系统提供的API,这时我们使用继承在适配器类里重新实现一遍工作量太大。这就要使用适配器模式的一个变体。这就是默认适配器,默认适配器上Target类是一个具体的类,实现大多数方法,甚至所有方法,但都是成虚方法,这样在适配器中有选择的重写Target中的方法就可以了。这种变体在实践中继承使用。也是很有用的一种模式。

会不会存在一个多功能的双向适配器呢(比如A系统对接B系统,同时B系统也要对接A系统)? 如果用C#该如何实现呢?

转载于:https://www.cnblogs.com/vaiyanzi/p/9440685.html

【设计模式】适配器模式 Adapter Pattern相关推荐

  1. C#设计模式——适配器模式(Adapter Pattern)

    一.概述 在软件开发中,常常会想要复用一个已经存在的组件,但该组件的接口却与我们的需要不相符,这时我们可以创建一个适配器,在需复用的组件的接口和我们需要的接口间进行转换,从而能够正常的使用需复用的组件 ...

  2. Java设计模式—适配器模式(adapter pattern)

    现在的手机很多都取消了3.5mm的耳机接口,只留下了一个type-c接口.但是我现在只有一个3.5mm圆孔接口的耳机,我不得不通过一个转接线使得3.5mm的耳机变成type-c接口. //Type-C ...

  3. 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern)

    [索引页] [源码下载] 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) 作者:webabcd 介绍 将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本 ...

  4. 设计模式(七): 通过转接头来观察适配器模式(Adapter Pattern)

    在前面一篇博客中介绍了"命令模式"(Command Pattern),今天博客的主题是"适配器模式"(Adapter Pattern).适配器模式用处还是比较多 ...

  5. 极速理解设计模式系列:6.适配器模式(Adapter Pattern)

    四个角色:目标抽象类角色(Target).目标对象角色(Adapter).源对象角色(Adaptee).客户端角色(Client) 目标抽象类角色(Target):定义需要实现的目标接口 目标对象角色 ...

  6. 适配器模式 Adapter Pattern

    适配器模式 适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁.这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能. 这种模式涉及到一个单一的类,该类负责加入独立的 ...

  7. php本地服务手机适配器,php适配器模式(adapter pattern)

    下午陪家人和小孩,晚上练起来. /* The adapter pattern allows the interface of an existing class to be used from ano ...

  8. 二十三种设计模式[6] - 适配器模式(Adapter Pattern)

    前言 适配器,属于类结构型模式.<设计模式 - 可复用的面向对象软件>一书中将之描述为" 将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可 ...

  9. 设计模式之四:适配器模式(Adapter Pattern)

    在软件系统中,由于应用环境的变化,常常需要将"一些现存的对象"放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的.如果能既能利用现有对象的良好实现,同时又能满足新的应 ...

最新文章

  1. 骑士卡:基于Kafka搭建消息中心,上亿消息推送轻松完成
  2. 学什么都不容易!学好一样更不容易!
  3. C/C++编译预处理指令
  4. XP 安装Oralce 10g 数据库
  5. pthread_create会导致内存泄露
  6. k近邻算法(KNN)-分类算法
  7. java 最大矩形_Java实现 LeetCode 85 最大矩形
  8. Java笔记-concurrent集合及线程池
  9. Python 数据科学手册 5.6 线性回归
  10. 阿里布局无人驾驶;滴滴成立汽车服务;“京东 AI 天团”首亮相| CSDN极客头条...
  11. 揭露QPS增高后的秘密
  12. 可以自由停靠的窗体!
  13. Mac 使用Quik Time Player为iPhone录屏后, iPhone状态条时间变为9:41
  14. 人机工程学座椅设计_人体工程学_座椅设计说明
  15. Django book2.0 contact表单
  16. 拍照识别植物app哪个好?来看看这几个工具
  17. 回忆篇,那些抹不去的童年记忆
  18. Vue学习记录-路由
  19. MySQL数据库图文安装详解及相关问题
  20. multiple definition of `xxxx`问题解决及其原理

热门文章

  1. WordPress建站指南
  2. Scroll View 使用心得
  3. js 定时器用法详解——setTimeout()、setInterval()、clearTimeout()、clearInterval()
  4. 理解和使用SQL Server中的并行
  5. 一个C/S结构的优秀例子: 延迟补偿在C/S架构游戏协议设计和优化中的应用
  6. 图书封面的故事之——“巴别塔”选自《七周七语言:理解多种编程范型》
  7. 客运售票员_汽车客运站售票员售票操作规范
  8. 从梯度下降到ADMM-学习记录
  9. java kill 线程_java – 如何在等待中杀死正在运行的线程?
  10. nucleo stlink 固件_自制nucleo,可以随意更换芯片,支持stlink,板载usbisp