【设计模式】适配器模式 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)--设计模式
在生活中,想用苹果充电线给安卓的手机充电时,因为两者的接口不一样,会导致充电口无法进行匹配, 这时候,就需要适配器,将安卓的充电口转化为苹果的接口,这样就可以充电啦.已有的类与新的接口不兼容问题是很普 ...
随机推荐
- 解决Configuration 'compile' is obsolete and has been replaced with implementation
项目中Gradle版本升级到4.4后,项目构建时,每次出现红色的警告信息: WARNING: Configuration 'compile' is obsolete and has been repl ...
- SmartSql Redis 分布式缓存
安装 SmartSql.Cache.Redis Install-Package SmartSql.Cache.Redis Cache 配置 <Cache Id="T_Test.Redi ...
- 前端css
CSS介绍 CSS(Cascading Style Sheet,层叠样式表)定义如何显示HTML元素. 当浏览器读到一个样式表,它就会按照这个样式表来对文档进行格式化(渲染). CSS语法 CSS实例 ...
- win10 DVWA下载安装配置(新手学渗透)
电脑重装系统了,需要重新装一下渗透测试的学习环境DVWA,借此机会就跟大家讲一下DVWA的安装过程,因为不同的电脑配置.环境不同,在我的电脑上按照我这个安装教程是一次性就安装好了的.如果安装的时候遇到 ...
- 编程心法 之 敏捷开发(新架构)Agile Team Organization Squads, Chapters, Tribes and Guilds
Agile Team 参考 一般情况下,一个小组有以下功能分布: Squads 每个主要的功能的开发属于一个Squad,比如说QQ这个应用,可以分为QQ空间小组.QQ会员小组等等, 每一个Squad有 ...
- postgreSQL 玩转josnb (长期更新)
json和jsonb的操作符 jsonb额外操作符 json创建函数 json处理函数 函数 返回类型 描述 示例 结果 json_array_length(json) jsonb_array_len ...
- 3星|《绩效使能:超越OKR》:较全较新资料汇编,华为实施经验少
全书是关于绩效管理与OKR的比较新比较全的资料汇编.从泰勒的科学管理说起,一直到现代的KPI.最近的OKR.梳理了工业革命以来重要的绩效管理思想的具体方法.适应情况,详细讲OKR的来龙去脉.适应情况. ...
- ZJOI2019一轮游记
Preface 期待已久的省选终于开始了233,关于之前的一些内容,在ZJOI2019一轮停课刷题记录都可以找到,这里不再赘述 ZJOI2019,Bless All Day -1 今天难得有休息,昨晚 ...
- 一篇读懂HTTPS:加密原理、安全逻辑、数字证书等
1.引言 HTTPS(全称: Hypertext Transfer Protocol Secure,超文本传输安全协议),是以安全为目标的HTTP通道,简单讲是HTTP的安全版.本文,就来深入介绍下其 ...
- Git实际使用
初始化 git init — cd到目录,初始化仓库 git init name — 新建文件,并初始化仓库 .gitignore — 忽略文件(https://github.com/gi ...