【设计模式】适配器模式 Adapter Pattern
适配器模式在软件开发界使用及其广泛,在工业界,现实中也是屡见不鲜。比如手机充电器,笔记本充电器,广播接收器,电视接收器等等。都是适配器。
适配器主要作用是让本来不兼容的两个事物兼容和谐的一起工作。比如, 通常我们使用的交流电都是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 = "",
- PostCode = "",
- Street = "Gao xin",
- Country = new Country
- {
- Abbreviation = "zh-CN",
- Name = "China",
- Number = "",
- },
- 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 = "",
- PostCode = "",
- Street = "Gao xin",
- Country = new Country
- {
- Abbreviation = "zh-CN",
- Name = "China",
- Number = "",
- },
- 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. 类结构适配器和对象结构适配器共有的优点:
将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
- 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
- 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则OCP”。
B.除了共有的优点外,类适配器还有如下优点:
- 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
C.除了共有的优点外,对象适配器还有如下优点:
- 一个对象适配器可以把多个不同的适配者适配到同一个目标。
- 可以适配一个适配者的父类,由于适配器和适配者之间是关联关系,根据“里氏代换原则LSP”,适配者的子类也可通过该适配器进行适配。
六、适配器模式的缺点
A.类适配器的缺点
- 由于C#不支持类的多继承,一次最多只能适配一个适配者类,不能同时适配多个适配者。
- 适配者类不能为最终类,C#中不能为sealed类,这样无法继承了。
- 在C#语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。其实这些都是单类继承的语言特性造成的。
B.对象适配器的缺点
- 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂, 另一种方法是直接在适配器类中将相应的方法重新实现掉。
七、适配器模式的使用场景
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
- 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
- 在调用第三方接口是,和现有的系统模型不配是可以使用Adapter模式将模型转化一直。
八、扩展-Default Adapter Parttern
在使用适配器模式的时候经常会碰到一类场景,就是已有的类的所有方法都都正常工作,但是只有那么几个方法需要调用第三方的几个系统提供的API,这时我们使用继承在适配器类里重新实现一遍工作量太大。这就要使用适配器模式的一个变体。这就是默认适配器,默认适配器上Target类是一个具体的类,实现大多数方法,甚至所有方法,但都是成虚方法,这样在适配器中有选择的重写Target中的方法就可以了。这种变体在实践中继承使用。也是很有用的一种模式。
会不会存在一个多功能的双向适配器呢(比如A系统对接B系统,同时B系统也要对接A系统)? 如果用C#该如何实现呢?
【设计模式】适配器模式 Adapter Pattern的更多相关文章
- 设计模式 - 适配器模式(adapter pattern) 具体解释
适配器模式(adapter pattern) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy 适配器模式(adapter pattern): 将一个类的接 ...
- 设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 具体解释
适配器模式(adapter pattern) 枚举器和迭代器 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考适配器模式(adapter patter ...
- C#设计模式——适配器模式(Adapter Pattern)
一.概述在软件开发中,常常会想要复用一个已经存在的组件,但该组件的接口却与我们的需要不相符,这时我们可以创建一个适配器,在需复用的组件的接口和我们需要的接口间进行转换,从而能够正常的使用需复用的组件. ...
- 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern)
原文:乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) 作者:webabc ...
- 怎样让孩子爱上设计模式 —— 7.适配器模式(Adapter Pattern)
怎样让孩子爱上设计模式 -- 7.适配器模式(Adapter Pattern) 标签: 设计模式初涉 概念相关 定义: 适配器模式把一个类的接口变换成client所期待的还有一种接口,从而 使原本因接 ...
- 设计模式系列之适配器模式(Adapter Pattern)——不兼容结构的协调
模式概述 模式定义 模式结构图 模式伪代码 类适配器,双向适配器,缺省适配器 类适配器 双向适配器 缺省适配器 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 主要缺点 适 ...
- 二十四种设计模式:适配器模式(Adapter Pattern)
适配器模式(Adapter Pattern) 介绍将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作.示例有一个Message实体类 ...
- 设计模式(七): 通过转接头来观察"适配器模式"(Adapter Pattern)
在前面一篇博客中介绍了“命令模式”(Command Pattern),今天博客的主题是“适配器模式”(Adapter Pattern).适配器模式用处还是比较多的,如果你对“适配器模式”理解呢,那么自 ...
- 适配器模式(Adapter Pattern)--设计模式
在生活中,想用苹果充电线给安卓的手机充电时,因为两者的接口不一样,会导致充电口无法进行匹配, 这时候,就需要适配器,将安卓的充电口转化为苹果的接口,这样就可以充电啦.已有的类与新的接口不兼容问题是很普 ...
随机推荐
- ssm日期格式转换
ssm日期格式转换 1 需求 前端传入字符串类型日期转化成java中的Date类型,存入数据库中;将数据库中的日期类型通过jstl标签在前端页面转换成字符串类型. 2 步骤 2.1 ...
- 在ASP.NET Core中使用EPPlus导入出Excel文件
这篇文章说明了如何使用EPPlus在ASP.NET Core中导入和导出.xls/.xlsx文件(Excel).在考虑使用.NET处理excel时,我们总是寻找第三方库或组件.使用Open Offic ...
- Unity 用ml-agents机器学习造个游戏AI吧(1)(Windows环境配置)
前言:以前觉得机器学习要应用于游戏AI,还远得很. 最近看到一些资料后,突发兴致试着玩了玩Unity机器学习,才发觉机器学习占领游戏AI的可能性和趋势. Unity训练可爱柯基犬Puppo 机器学习训 ...
- python微信聊天机器人改进版,定时或触发抓取天气预报、励志语录等,向好友推送
最近想着做一个微信机器人,主要想要实现能够每天定时推送天气预报或励志语录,励志语录要每天有自动更新,定时或当有好友回复时,能够随机推送不同的内容.于是开始了分析思路.博主是采用了多线程群发,因为微信对 ...
- 卷积神经网络之AlexNet
由于受到计算机性能的影响,虽然LeNet在图像分类中取得了较好的成绩,但是并没有引起很多的关注. 知道2012年,Alex等人提出的AlexNet网络在ImageNet大赛上以远超第二名的成绩夺冠,卷 ...
- 卷积神经网络之LeNet
开局一张图,内容全靠编. 上图引用自 [卷积神经网络-进化史]从LeNet到AlexNet. 目前常用的卷积神经网络 深度学习现在是百花齐放,各种网络结构层出不穷,计划梳理下各个常用的卷积神经网络结构 ...
- C# .NET Web API 如何自訂 ModelBinder
各位好!這次要來替大家介紹的是如何在 .NET Web API 中自訂一個 ModelBinder 透過自定義的 ModelBinder 我們可以很簡單的將 QueryString 傳過來的參數綁定 ...
- 【转载】Win7利用任务计划程序实现定时关机
在Win7.Win8或者Win10系统中,如果要实现电脑的自动定时关机,不需要借用任何的外部程序,直接系统自带的任务计划程序即可实现电脑的定时自动关机,支持设定电脑关机时间以及执行频率次数,如固定每天 ...
- windows dll的def文件
DLL(testcase_1.dll )源码:myfun.h #pragma once #ifdef TESTCASE_1_EXPORTS #define MY_API __declspec(dlle ...
- 服务端渲染和nuxt简单介绍
概述 最近研究了一下服务端渲染,有一些心得,记录下来供以后开发时参考,相信对其他人也有用. 参考资料: Vue SSR指南 nuxt.js官网 服务端渲染介绍 服务端渲染简单来说,就是分别对项目用we ...