适配器(Adapter)模式的作用是把一个类的编程接口转换成另外一个类的编程接口,任何时候只要是想让一些无关的类在某个程序中相互配合,一起工作,都可以使用适配器模式。
适配器的概念相当简单:编写一个有着 所希望的接口的类,让后让该类与其他有着不同接口的类进行沟通。
有两种方法可以做到这一点:通过继承和通过对象组合。在继承方法中,我们从不适用的对象中派生出一个新的对象,然后添加所需要的方法使得新的派生对象能够满足所需要的接口;在通过对象组合中,把最初的类包含到新创建的类中,然后在新的类中创建方法来转换调用。这两种方法分别成为类适配器和对象适配器。
适配器更多的就是把一些不方便直接使用的类重新进行封装,让我们可以更简洁方便的进行调用。比如说,在c语言中,字符串的处理需要使用指向字符的指针控制,并且每个字符都要进行处理,这样对于经常需要的操作很麻烦,在C#中,我们就可以通过string对象来直接操作,并且可以连接字符串等,这就是一种让字符串操作更加简单的适配器模式。
下面举例介绍适配器模式。
要实现这样的一个功能,用户界面如图:
在这个功能界面中,选中左边列表的一个名字,我们会在邮编列表添加一个表项,显示用户名字和游泳速度。
为了节省空间,我们这个例子中用到的Swimmer类和SwimData类请见这个连接中的详细定义:
http://blog.csdn.net/weixingstudio/article/details/7277494
这样在右边的列表中添加新的项目就可以这样实现:
Swimmer sw=swdata.getSwimmer(i);
lsnewKids.Item.Add(sw.getName()+"\t"+sw.getTime());
注意,这里的给列表想添加新的项目,我们需要自己给这个项目的字符串信息进行控制合并,添加制表符等信息。但是名字的长度不一样,这样不同的项目显示出来的效果就不一样。更加有利于程序员编程的是这样的一种操作:程序员根据选择的左侧列表的名字,然后直接给右侧列表添加这个名字的游泳选手的Swimmer对象,然后让适配器去做应该给列表项添加什么数据的操作。
我们需要的适配器代码:
using System;
using System.Windows.Forms;
namespace LstAdapter
{
/// <summary>
/// Summary description for ListAdapter.
/// </summary>
public class ListAdapter {
private ListBox listbox; //operates on this one
public ListAdapter(ListBox lb) {
listbox = lb;
}
//-----
public void Add(string s) {
listbox.Items.Add (s);
}
//-----
public void Add(Swimmer sw) {
listbox.Items.Add (sw.getName()+"\t"+sw.getTime());
}
//-----
public int SelectedIndex() {
return listbox.SelectedIndex;
}
//-----
public void Clear() {
listbox.Items.Clear ();
}
//-----
public void clearSelection() {
int i = SelectedIndex();
if(i >= 0) {
listbox.SelectedIndex =-1;
}
}
}
}
在这个适配器中,可以直接添加一个Swimmer对象进去,然后由适配器选择要显示的数据,完全的方便的程序员的使用。在这个适配器中,有一个指向用户界面的ListBox的引用,就可以通过这个引用直接操作用户界面的ListBox中的列表项了。
这样可以生成两个适配器实例,分别指向两个不同的列表框。
private ListAdapter lskids, lsnewKids;
private System.Windows.Forms.Button putBack;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
private void init() {
swdata = new SwimData ("swimmers.txt");
lskids= new ListAdapter (lsKids);
lsnewKids = new ListAdapter (lsNewKids);
reload();
}
然后处理添加按钮事件:
private void btClone_Click(object sender, EventArgs e) {
int i = lskids.SelectedIndex ();
if( i >= 0) {
Swimmer sw = swdata.getSwimmer (i);
lsnewKids.Add (sw.getName() +"\t"+sw.getTime ());
lskids.clearSelection ();
}
}
这里就是通过给适配器传送一个Swimmer对象直接进行编程,由适配器选择需要显示的数据。这样就简化了对ListBox的使用,方便开发人员的操作。
下面是一个稍微复杂一点的适配器例子.
这个例子需要在用户界面的右侧显示一个DataGrid,用以显示详细的游泳选手的数据。用户界面如图:
左侧的列表框我们仍然可以使用上面例子的适配器进行控制,但是右侧的DataGrid就需要新的适配器了。关于DataGrid的具体操作我们这里不过多的描述,下面仅给出DataGrid的最常用的初始化和添加数据的操作:
private void createGrid() {
dTable = new DataTable("Kids");
dTable.MinimumCapacity = 100;
dTable.CaseSensitive = false;
DataColumn column =
new DataColumn("Frname",System.Type.GetType("System.String"));
dTable.Columns.Add(column);
column = new DataColumn("Lname", System.Type.GetType("System.String"));
dTable.Columns.Add(column);
column = new DataColumn("Age", System.Type.GetType("System.Int16"));
dTable.Columns.Add(column);
dGrid.DataSource = dTable;
dGrid.CaptionVisible = false; //no caption
dGrid.RowHeadersVisible = false; //no row headers
dGrid.EndInit();
}
public void Add(Swimmer sw) {
DataRow row = dTable.NewRow();
row["Frname"] = sw.getFrname();
row[1] = sw.getLName();
row[2] = sw.getAge(); //This one is an integer
dTable.Rows.Add(row);
dTable.AcceptChanges();
}
//-----
仍然像上面例子一样,我们希望用户选择左边的名字以后,我们开发人员就直接给右侧的数据显示区域添加一个Swimmer对象实例,至于需要显示什么数据,就要看适配器怎么操作了。
适配器的代码:
using System;
using System.Windows.Forms ;
using System.Data;
namespace GridAdapt
{
/// <summary>
/// Summary description for GridAdapter.
/// </summary>
public class GridAdapter:LstAdapter {
private DataGrid grid;
private DataTable dTable;
private int row;
//-----
public GridAdapter(DataGrid grd) {
grid = grd;
dTable = (DataTable)grid.DataSource;
grid.MouseDown +=new System.Windows.Forms.MouseEventHandler (Grid_Click);
row = -1;
}
//-----
public void Grid_Click(object sender, System.Windows.Forms.MouseEventArgs e) {
DataGrid.HitTestInfo hti = grid.HitTest (e.X, e.Y);
if(hti.Type == DataGrid.HitTestType.Cell ){
row = hti.Row ;
}
}
//-----
public void Add(Swimmer sw) {
DataRow row = dTable.NewRow();
row["Frname"] = sw.getFrname();
row[1] = sw.getLName();
row[2] = sw.getAge(); //This one is an integer
dTable.Rows.Add(row);
dTable.AcceptChanges();
}
//-----
public int SelectedIndex() {
return row;
}
//-----
public void Clear() {
int count = dTable.Rows.Count ;
for(int i=0; i< count; i++) {
dTable.Rows[i].Delete ();
}
}
//-----
public void clearSelection() {}
}
}
DataGrid灭有SelectedIndex这样的属性,也没有Selected时间,所以需要自己操作进行第几行的选择。如上面的程序中Grid_Click()函数。
然后在这个应用中,只需要给对应的适配器传递一个Swimmer对象就可以了,剩下的工作由适配器去控制显示。
private void btClone_Click(object sender, System.EventArgs e) {
int i = lskids.SelectedIndex ();
if( i >= 0) {
Swimmer sw = swdata.getSwimmer (i);
lsNewKids.Add (sw);
lskids.clearSelection ();
}
}
以上我们说的适配器都是对象适配器,通过对对象的组合进行操作。下面介绍一下类适配器,类适配器就是需要从已有的类中派生一个我们自己的类,然后在自己的类中给这个类添加以前没有的不同意操作的一些函数或者属性。
其中一个派生于ListBox的类的定义如下:
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
namespace ClassAdapt
{
public class MyList : System.Windows.Forms.ListBox, ListAdapter {
private System.ComponentModel.Container components = null;
//-----
public MyList() {
InitializeComponent();
}
//-----
public void Add(string s) {
this.Items.Add (s);
}
//-----
public void Add(Swimmer sw) {
this.Items.Add (sw.getName() +
"\t" + sw.getAge ().ToString () );
}
//-----
public void Clear() {
this.Items.Clear ();
}
//-----
public void clearSelection() {
this.SelectedIndex = -1;
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
}
}
类适配器方法和对象适配器方法存在一些不同:
1. 类适配器不能做到让一个类及其所有子类都适配,因为在创建适配器时就定义了派生该适配器的类。
2. 类适配器允许适配器改变被适配的类的一些方法,不过依然允许使用其他未修改的方法。
3. 对象适配器可以允许子类被适配,但需要把子类作为适配器构造函数的一部分传递进来就可以了。
4. 对象适配器需要明确的把被适配对象的任何可供使用的方法放到可公开访问的层面上来。