适配器模式

摘要

本文以C#示例说明适配器模式的概念和应用场景。

定义

适配器模式(adapter Pattern, 有时又被称为包装样式或者包装(wrapper), 是软件设计模式的一种。在此种模式中,将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类能在一起工作,做法是将自己的接口包裹在一个已存在的类中。维基百科

Alt Text

解读

  • 关键词:适配;
  • 使得原本由于接口不兼容而不能在一起工作的类可以一起工作;
  • 符合开闭原则;
  • 应用场景: 对接<第三方>类库(接口);项目重构背景下的新老接口对接;
  • 适配器模式解决的是正在服役的项目中存在的问题;
  • 对于正在开发的新项目,如非必要,不要使用适配器模式,条件允许的情况下请重构;
  • 项目中过多的适配器模式的应用,会让系统变得凌乱不堪,难以把握;
  • 在GoF的设计模式中, 适配器模式分为两种类型, 类适配器模式和对象适配器模式。 由于类适配器模式通过多重继承对一个接口与另一个接口进行匹配,而C#,Java等语言都不支持多重继承,因而这里只介绍对象适配器。

现实生活中的场景

  • 翻译工作
  • 笔记本电源
  • 在Linux系统中运行Windows程序
  • ...

思考

下面用代码示例进行说明:

Alt Text

代码示例

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吧

All Text

posted @ 2017-09-28 18:30  DebugLife  阅读(718)  评论(4编辑  收藏  举报