1、概述
背景
我们在买港版的电器的时候,肯定会伴随着要额外购买转换器,因为香港的插座跟内地是有区别的。我们需要一个转换头也就是适配器来适配内地的插座。
定义
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
结构
适配器模式(Adapter)包含以下主要角色:
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
2、实现思路
场景
以前开发的系统的关系型数据库接口存在满足新系统功能需求,但是非关系型数据库的接口(比如Redis)不满足新系统要求。此时,我们就需要加一个适配器类来适配非关系型数据库的接口。具体情况如下所示,
我们有两个关系型数据库类,MySQLClient和SqlserverClient,这两个类都有增删改成方法并且满足新系统调用要求;
interface IDBClient
{
public void Add();
public bool Delete();
public void Update();
public void Query();
}
public class MySQLClient : IDBClient
{
public void Add()
{
throw new NotImplementedException();
}
public bool Delete()
{
throw new NotImplementedException();
}
public void Query()
{
throw new NotImplementedException();
}
public void Update()
{
throw new NotImplementedException();
}
}
public class SqlserverClient : IDBClient
{
public void Add()
{
throw new NotImplementedException();
}
public bool Delete()
{
throw new NotImplementedException();
}
public void Query()
{
throw new NotImplementedException();
}
public void Update()
{
throw new NotImplementedException();
}
}
public class RedisClient
{
public void AddCache()
{
}
public bool DeleteCache()
{
return true;
}
public void UpdateCache()
{
}
public void QueryCache()
{
}
}
class Program
{
static void Main(string[] args)
{
#region MySQL
{
MySQLClient dBClient = new MySQLClient();
dBClient.Add();
dBClient.Delete();
dBClient.Update();
dBClient.Query();
}
#endregion
#region Sqlserver
{
SqlserverClient dBClient = new SqlserverClient();
dBClient.Add();
dBClient.Delete();
dBClient.Update();
dBClient.Query();
}
#endregion
#region Redis
{
RedisClient dBClient = new RedisClient();
dBClient.Add();
dBClient.Delete();
dBClient.Update();
dBClient.Query();
}
#endregion
}
}
AI 代码解读
此时,新系统要求通过Add、Delete、Update、Query方法进行增删改成操作时,编译器报错了,如下图所示。
可以看出RedisClient的方法并不满足我们的需求,并且RedisClient这个类是不允许修改的,因此我们需要在RedisClient类外面包一层来适配新系统。
类适配器
/// <summary>
/// 对象适配器
/// </summary>
public class RedisClientClassAdapter : RedisClient, IDBClient
{
public void Add()
{
base.AddCache();
}
public bool Delete()
{
return base.DeleteCache();
}
public void Query()
{
base.QueryCache();
}
public void Update()
{
base.UpdateCache();
}
}
AI 代码解读
顾名思义,类适配器就是新增一个类并让其继承需要适配的类。此时Program类可以改成如下,
class Program
{
static void Main(string[] args)
{
#region MySQL
{
IDBClient dBClient = new MySQLClient();
dBClient.Add();
dBClient.Delete();
dBClient.Update();
dBClient.Query();
}
#endregion
#region Sqlserver
{
IDBClient dBClient = new SqlserverClient();
dBClient.Add();
dBClient.Delete();
dBClient.Update();
dBClient.Query();
}
#endregion
#region Redis
{
IDBClient dBClient = new RedisClientAdapter();
dBClient.Add();
dBClient.Delete();
dBClient.Update();
dBClient.Query();
}
#endregion
}
}
AI 代码解读
对象适配器
/// <summary>
/// 对象适配器
/// </summary>
public class RedisClientObjectAdapter : IDBClient
{
private RedisClient _redisClient = new RedisClient();
public void Add()
{
this._redisClient.AddCache();
}
public bool Delete()
{
return this._redisClient.DeleteCache();
}
public void Query()
{
this._redisClient.QueryCache();
}
public void Update()
{
this._redisClient.UpdateCache();
}
}
AI 代码解读
对象适配器不是继承一个类,而是在适配器类中创建一个对象,其实就是组合关系。
对比
接下来,我们对比一下类适配器和对象适配器,通过代码解释为什么前者类之间的耦合度比后者高。
我们知道继承的缺点是侵入性、不够灵活、高耦合:
- 继承是侵入性的,只要继承就必须拥有父类的所有方法和属性,在一定程度上约束了子类,降低了代码的灵活性;
- 增加了耦合,当父类的常量、变量或者方法被修改了,需要考虑子类的修改,所以一旦父类有了变动,很可能会造成
非常糟糕的结果,要重构大量的代码。
这些问题,在类适配器中一样存在,我们可以看到类适配器由于继承了RedisClient,会把该类的方法也暴露出来,也就是其他开发同事可以调用AddCache等方法。如下所示,
#region Redis
{
RedisClientClassAdapter dBClient = new RedisClientClassAdapter();
dBClient.Add();
dBClient.Delete();
dBClient.Update();
dBClient.Query();
dBClient.AddCache();
dBClient.DeleteCache();
dBClient.UpdateCache();
dBClient.QueryCache();
}
#endregion
AI 代码解读
当然这个问题我们可以通过如下方法来回避掉这个问题,
另外,RedisClient升级了,我们就要去修改对应的RedisClientClassAdapter;
对象适配器可以为多个对象服务(通过构造函数传入);
因此,我们说组合优于继承。
3、应用场景
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。