在实际的开发过程中,由于需求的变化和扩展,我们的代码也需要做相应的扩展。想象这样一个场景,原项目中接口返回的数据是XML格式的数据,但现在来了一个新客户,它期望接口返回的数据类型为json格式的。想要实现要么就是改原有接口,但这样就违反了开闭原则,容易出现未知bug,影响到老客户的正常使用。而如果写一个适配器类也就是转换类(第三方类),将原本返回的XML格式数据转换成json格式数据,而具体数据是怎么来的则直接用原有接口方法就可以,这就是适配器模式。
现实生活中空调插头一般都是三头的,但如果家里只有两孔插座,那必然是插不进去的。而如果提供一个拥有三孔插座和两头插头的转换器的话,那空调可以先插在这个转换器上,然后这个转换器再插在插座上就可以了。本质并没有变,只是将二孔插座包装了一下,向外界提供了一个三孔插座的外观以供客户使用。
适配的本质就是转换,将不满足使用条件的东西通过第三方类进行加工处理成可使用的东西。
以常见的数据库辅助接口为例
//数据库辅助接口【目标角色】 public interface IDbHelper { //负责执行数据查询 Query void ExecDQL(string sql); //负责执行数据操作 Create,Delete,Update void ExecDML(string sql); } // SqlServer的辅助类 实现 public class SqlServerHelper : IDbHelper { public void ExecDML(string sql) { Console.WriteLine($"SqlServerHelper执行了【操作】sql:{sql}"); } public void ExecDQL(string sql) { Console.WriteLine($"SqlServerHelper执行了【查询】sql:{sql}"); } } // SqlServer的辅助类 实现 public class MySqlHelper : IDbHelper { public void ExecDML(string sql) { Console.WriteLine($"MySqlHelper执行了【操作】sql:{sql}"); } public void ExecDQL(string sql) { Console.WriteLine($"MySqlHelper执行了【查询】sql:{sql}"); } } //业务扩展了,关系型数据库已经不满足于现在的业务了 //需要给系统增加缓存,用到了非关系型数据库 //非关系数据库辅助类 【被适配者】 public class NoSqlHelper { //非关系数据库【特有的】操作数据方法 public void ExecNoSqlDML(string str) { Console.WriteLine($"NoSqlHelper执行了【操作】:{str}"); } //非关系数据库【特有的】查询数据方法 public void ExecNoSqlDQL(string str) { Console.WriteLine($"NoSqlHelper执行了【查询】:{str}"); } } //适配器类 public class NoSqlHelperAdapter : NoSqlHelper, IDbHelper { //实际上调用非关系型数据库的数据操作方法 public void ExecDML(string sql) { base.ExecNoSqlDML(sql); } public void ExecDQL(string sql) { base.ExecNoSqlDQL(sql); } }
客户端调用
public static void Main(string[] args) { //客户端可以通过适配器来使用IDbHelper这个数据库辅助类 //因为通过NoSqlHelperAdapter 适配器类,已经将其包装成了IDbHelper IDbHelper dbHelper = new NoSqlHelperAdapter(); dbHelper.ExecDML("..."); Console.ReadLine(); }
从实例中可以看出,类适配器主要是用继承来实现的,但如果有很多个类进行适配,这个方式就不支持了。
相对于类适配器,这部分代码不变
//数据库辅助类 public interface IDbHelper { //负责执行数据查询 Query void ExecDQL(string sql); //负责执行数据操作 Create,Delete,Update void ExecDML(string sql); } // SqlServer的辅助类 实现 public class SqlServerHelper : IDbHelper { public void ExecDML(string sql) { Console.WriteLine($"SqlServerHelper执行了【操作】sql:{sql}"); } public void ExecDQL(string sql) { Console.WriteLine($"SqlServerHelper执行了【查询】sql:{sql}"); } } // SqlServer的辅助类 实现 public class MySqlHelper : IDbHelper { public void ExecDML(string sql) { Console.WriteLine($"MySqlHelper执行了【操作】sql:{sql}"); } public void ExecDQL(string sql) { Console.WriteLine($"MySqlHelper执行了【查询】sql:{sql}"); } } //业务扩展了,关系型数据库已经不满足于现在的业务了 //需要给系统增加缓存,用到了非关系型数据库 //非关系数据库辅助类 public class NoSqlHelper { //非关系数据库操作数据 public void ExecNoSqlDML(string str) { Console.WriteLine($"NoSqlHelper执行了【操作】:{str}"); } //非关系数据库查询数据 public void ExecNoSqlDQL(string str) { Console.WriteLine($"NoSqlHelper执行了【查询】:{str}"); } }
仅仅是适配类的代码发生改变
//适配器 public class NoSqlHelperAdapter : IDbHelper { //引用非关系数据库辅助类 的实例 public NoSqlHelper noSqlHelper = new NoSqlHelper(); public void ExecDML(string sql) { //通过实例调用相关数据操作的方法 noSqlHelper.ExecNoSqlDML(sql); } public void ExecDQL(string sql) { noSqlHelper.ExecNoSqlDQL(sql); } }
从实例中可以看出,对象适配器其实就是在适配器类中创建了一个被适配者的实例,从而将两者联系在一起。这种方式采用 “对象组合”的方式,更符合松耦合。
从两个案例上知道,适配器模式并不是项目一开始就会用到的,而是随着需求的变更和扩展,我们不得已才开发一个适配器类,将新增的功能通过适配器类 “包一层” 的方式转换为我们原有对外提供的接口,使得我们可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”
类的适配器模式:
对象的适配器模式:
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
c#中适配器模式详解
C#设计模式(7)-适配器模式
上一篇:golang离线引用依赖包