适配器模式
摘要
本文以C#示例说明适配器模式的概念和应用场景。
定义
适配器模式(adapter Pattern, 有时又被称为包装样式或者包装(wrapper), 是软件设计模式的一种。在此种模式中,将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类能在一起工作,做法是将自己的接口包裹在一个已存在的类中。维基百科
解读
- 关键词:适配;
- 使得原本由于接口不兼容而不能在一起工作的类可以一起工作;
- 符合开闭原则;
- 应用场景: 对接<第三方>类库(接口);项目重构背景下的新老接口对接;
- 适配器模式解决的是正在服役的项目中存在的问题;
- 对于正在开发的新项目,如非必要,不要使用适配器模式,条件允许的情况下请重构;
- 项目中过多的适配器模式的应用,会让系统变得凌乱不堪,难以把握;
- 在GoF的设计模式中, 适配器模式分为两种类型, 类适配器模式和对象适配器模式。 由于类适配器模式通过多重继承对一个接口与另一个接口进行匹配,而C#,Java等语言都不支持多重继承,因而这里只介绍对象适配器。
现实生活中的场景
- 翻译工作
- 笔记本电源
- 在Linux系统中运行Windows程序
- ...
思考
- 和外观模式的区别
下面用代码示例进行说明:
代码示例
ORM包装
假设数据库ORM有一个Get方法专门用于根据传入的SQL语句获取DataTable,而且此方法所在的Class不允许修改,随着数据的增多,开发人员需要一个获取分页数据的方法且能够自定义排序字段,但却不想每次获取数据时都编写分页查询的语句,这个时候,我们可以适配一个中间类来操作
// adaptee - 获取DataTable
public class DataRepo
{
public DataTable Get(string select_sql, string connectionString)
{
using (SqlConnection con = new SqlConnection(connectionString))
using (var da = new System.Data.SqlClient.SqlDataAdapter(select_sql, con))
{
var dt = new DataTable();
try
{
con.Open();
da.Fill(dt);
}
catch (Exception)
{
throw;
}
return dt;
}
}
}
// adapter - 分页获取数据
public class UserDataRepo
{
public DataTable GetPageTable(string select_sql, int pageIndex, int pageSize, string orderfield = "date desc")
{
int row_from = (pageIndex - 1) * pageSize;
var page_sql = $"select * from ({select_sql}) as cc order by cc.{orderfield} \
offset {row_from} row fetch next {pageSize} rows only";
var dataRepo = new DataRepo();
return dataRepo.Get(page_sql);
}
}
// client - 分页获取
public class UserOperationCls
{
var u_data_repo = new UserDataRepo();
DataTable table = u_data_repo.GetPageTable("select * from Users where isDelete=false \
and createDate > 2015-1-1",
1,
10,
"createDate desc");
// do something else...
}
上面的例子,在 UserOperationCls 和 DataRepo 之间定义了一个适配类UserDataRepo,解决了用户和ORM之间的接口适配问题。可能例子太过简单,也可能不太恰当,就当是我扔了个砖头,没砸到人就权当是引玉了。
延伸阅读:单接口适配器
当不需要全部接口实现的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择的覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况,因此也称为单接口适配器模式。
/// <summary>
/// 日志操作接口
/// </summary>
public interface ILog
{
bool Insert(string message);
void Clear();
bool Delete(int id);
}
/// <summary>
/// 实现ILog接口的抽象基类
/// </summary>
public abstract class absLog : ILog
{
public abstract void Clear();
public abstract bool Delete(int id);
public virtual bool Insert(string message)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 用户日志
/// </summary>
public abstract class absUserLog : absLog
{
/// <summary>
/// 删除指定ID的日志
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public override bool Delete(int id)
{
throw new NotImplementedException();
}
/// <summary>
/// 清空日志,注意加了sealed关键字
/// </summary>
public sealed override void Clear()
{
}
}
/// <summary>
/// 更完美一点:彻底隐藏Clear方法
/// </summary>
public class WebUserLog : absUserLog
{
public override bool Delete(int id)
{
return base.Delete(id);
}
public override bool Insert(string message)
{
return base.Insert(message);
}
}
好了,今天的适配器模式就聊到这里,大家继续回去写BUG吧