步步为营 .NET 设计模式学习笔记 六、Adapter(适配器模式)
概述
在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。那么如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?这就是本文要说的Adapter 模式。
意图
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
结构图
图1 类的Adapter模式结构图
图2 对象的Adapter模式结构图
生活中的例子
适配器模式允许将一个类的接口转换成客户期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。扳手提供了一个适配器的例子。一个孔套在棘齿上,棘齿的每个边的尺寸是相同的。在美国典型的边长为1/2''和1/4''。显然,如果不使用一个适配器的话,1/2''的棘齿不能适合1/4''的孔。一个1/2''至1/4''的适配器具有一个1/2''的阴槽来套上一个1/2''的齿,同时有一个1/4的阳槽来卡入1/4''的扳手。
图3使用扳手适配器例子的适配器对象图
示例图用例设计:
先创建User.cs
public class User { public int ID { get; set; } public string Name { get; set; } public string Age { get; set; } }
然后再新建IDBHelper.cs
public interface IDBHelper { /// <summary> /// 连接字符串 /// </summary> /// <returns></returns> string DBConnectString(); /// <summary> /// 返回结果集 /// </summary> /// <param name="strsql"></param> /// <returns></returns> DataSet GetUserGroup(string strsql); /// <summary> /// 返回插入的行数 /// </summary> /// <param name="user"></param> /// <returns></returns> int InsertUser(User user); }
然后再新建SQLHelper.cs:
public class SQLHelper:IDBHelper { #region IDBHelper 成员 public string DBConnectString() { return "SQL Connect String"; } public DataSet GetUserGroup(string strsql) { DataSet ds = new DataSet(); return ds; } public int InsertUser(User user) { return 1; } #endregion }
然后再新建OracleHelper.cs:
public class OracleHelper:IDBHelper { #region IDBHelper 成员 public string DBConnectString() { return "Oracle Connect String."; } public DataSet GetUserGroup(string strsql) { DataSet ds = new DataSet(); return ds; } public int InsertUser(User user) { return 1; } #endregion }
然后再建DataContent.cs:
public class DataContent:IDBHelper { private IDBHelper DbHelper = GetDBHelper(); public static IDBHelper GetDBHelper() { string strClass = ConfigurationSettings.AppSettings["DBHeper"].ToString(); Assembly assembly = Assembly.Load("Adapter"); IDBHelper dbHelper = assembly.CreateInstance(strClass) as IDBHelper; return dbHelper; } #region IDBHelper 成员 public string DBConnectString() { return DbHelper.DBConnectString(); } public System.Data.DataSet GetUserGroup(string strsql) { return DbHelper.GetUserGroup(strsql); } public int InsertUser(User user) { return DbHelper.InsertUser(user); } #endregion }
在App.config里加下面代码:
<appSettings> <add key="DBHeper" value="Adapter.SQLHelper"/> <!--<add key="DBHeper" value="Adapter.OracleHelper"/>--> </appSettings>
看我们的调用程序:
public partial class Run : Form { public Run() { InitializeComponent(); } private void btnRun_Click(object sender, EventArgs e) { DataContent dataContent = new DataContent(); rtbResult.AppendText(dataContent.DBConnectString()); } }
结果如图:
实现要点
1.Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。
2.Adapter模式有对象适配器和类适配器两种形式的实现结构,但是类适配器采用“多继承”的实现方式,带来了不良的高耦合,所以一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合精神。
3.Adapter模式的实现可以非常的灵活,不必拘泥于GOF23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。
4.Adapter模式本身要求我们尽可能地使用“面向接口的编程”风格,这样才能在后期很方便的适配。[以上几点引用自MSDN WebCast]
5. 适配器模式是否能成功运用的关键在于代码本身是否是基于接口编程的,如果不是的话,那么适配器无能为力。
6. 适配器模式的实现很简单,基本的思想就是适配器一定是遵循目标接口的。
7. 适配器模式的变化比较多,可以通过继承和组合方式进行适配,适配器可以是一组适配器产品,适配器也可以是抽象类型。
8. 适配器模式和Facade的区别是,前者是遵循接口的,后者可以是不遵循接口的,比较灵活。
9. 适配器模式和Proxy的区别是,前者是为对象提供不同的接口,或者为对象提供相同接口,并且前者有一点后补的味道,后者是在设计时就会运用的。
效果
对于类适配器:
1.用一个具体的Adapter类对Adaptee和Taget进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。
2.使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。
3.仅仅引入了一个对象,并不需要额外的指针一间接得到Adaptee.
对于对象适配器:
1.允许一个Adapter与多个Adaptee,即Adaptee本身以及它的所有子类(如果有子类的话)同时工作。Adapter也可以一次给所有的Adaptee添加功能。
2.使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。
适用性
在以下各种情况下使用适配器模式:
1.系统需要使用现有的类,而此类的接口不符合系统的需要。
2.想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
3.(对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。
4. 从代码角度来说, 如果需要调用的类所遵循的接口并不符合系统的要求或者说并不是客户所期望的,那么可以考虑使用适配器。
5. 从应用角度来说, 如果因为产品迁移、合作模块的变动,导致双方一致的接口产生了不一致,或者是希望在两个关联不大的类型之间建立一种关系的情况下可以考虑适配器模式。
总结
总之,通过运用Adapter模式,就可以充分享受进行类库迁移、类库重用所带来的乐趣。
欢迎拍砖.