思考并回答以下问题:
适配器模式
适配器模式定义:1
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式主要解决什么问题呢?
需要的东西就在面前,但却不能使用,而短时间又无法改造它,于是就想办法适配它。
“适配”这个词应该是最早出现在电工学里,有些国家用110V电压,而我们国家用的是220V,但我们的电器,比如笔记本电脑是不能什么电压都能用的,但国家不同,电压可能不相同也是事实,于是就用一个电源适配器,只要是电,不管多少伏,都能把电源变成需要的电压,这就是电源适配器的作用。适配器的意思就是使得一个东西适合另一个东西的东西。
姚明不会英语,要在美国NBA打球,不会英语如何交流?没有交流如何理解教练和同伴的意图?又如何让他们理解自己的想法?不能沟通就打不好球了。于是就有三个办法,第一,让姚明学会英语,不符合实际,姚明刚到NBA打球,之前又没有时间在学校里认真学好英语,马上学到可以听懂会说的地步是很困难的。第二种方法,让教练和球员学会中文。第三种方法,给姚明找个翻译。翻译就是适配器。
在不能更改球队的教练、球员和姚明的前提下,能做的就是想办法找个适配器。在软件开发中,也就是系统的数据和行为都正确,但接口不符时,应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,比如在需要对早期代码复用一些功能等应用上很有实际价值。
在GoF的设计模式中,对适配器模式讲了两种类型,类适配器模式和对象适配器模式,由于类适配器模式通过多重继承对一个接口与另一个接口进行匹配,而C#、VB.NET、JAVA等语言都不支持多重继承(C++支持),也就是一个类只有一个父类,所以我们这里主要讲的是对象适配器。”
适配器模式(Adapter)结构图
Target(这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口)。
1
2
3
4
5
6
7 class Target
{
public virtual void Request()
{
Console.WriteLine("普通请求");
}
}Adaptee(需要适配的类):
1 | class Adaptee |
Adapter(通过在内部包装一个Adaptee对象,把源接口转换成目标接口)
1
2
3
4
5
6
7
8
9 class Adapter : Target
{
private Adaptee adaptee = new Adaptee();
public override void Request()
{
adaptee.SpecificRequest();
}
}客户端:
1 | class Program |
何时使用适配器模式
在想使用一个已经存在的类,但如果它的接口,也就是它的方法和要求不相同时,就应该考虑用适配器模式。两个类所做的事情相同或相似,但是具有不同的接口时要使用它。而且由于类都共享同一个接口,客户代码可以统一调用同一接口就行了,这样可以更简单、更直接、更紧凑。
其实用适配器模式也是无奈之举,很有点‘亡羊补牢’的感觉,没办法呀,是软件就有维护的一天,维护就有可能会因不同的开发人员、不同的产品、不同的厂家而造成功能类似而接口不同的情况,此时就是适配器模式大展拳脚的时候了。
通常是在软件开发后期或维护期再考虑使用它,因为如果是在设计阶段,没有必要把类似的功能类的接口设计得不同。
首先,公司内部,类和方法的命名应该有规范,最好前期就设计好,接口不相同时,首先不应该考虑用适配器,而是应该考虑通过重构统一接口。
要在双方都不太容易修改的时候再使用适配器模式适配,而不是一有不同时就使用它。那有没有设计之初就需要考虑用适配器模式的时候?
当然有,比如公司设计一系统时考虑使用第三方开发组件,而这个组件的接口与自己的系统接口是不相同的,而完全没有必要为了迎合它而改动自己的接口,此时尽管是在开发的设计阶段,也是可以考虑用适配器模式来解决接口不同的问题。
篮球翻译适配器
后卫、中锋、前锋都是球员,所以应该有一个球员抽象类,有进攻和防守的方法。
球员类1
2
3
4
5
6
7
8
9
10
11
12//篮球运动员
abstract class Player
{
protected string name;
public Player(string name)
{
this.name = name;
}
public abstract void Attack();
public abstract void Defense();
}
后卫、中锋、前锋类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56//前锋
class Forwards : Player
{
public Forwards(string name)
: base(name)
{
}
public override void Attack()
{
Console.WriteLine("前锋 {0} 进攻", name);
}
public override void Defense()
{
Console.WriteLine("前锋 {0} 防守", name);
}
}
//中锋
class Center : Player
{
public Center(string name)
: base(name)
{
}
public override void Attack()
{
Console.WriteLine("中锋 {0} 进攻", name);
}
public override void Defense()
{
Console.WriteLine("中锋 {0} 防守", name);
}
}
//后卫
class Guards : Player
{
public Guards(string name)
: base(name)
{
}
public override void Attack()
{
Console.WriteLine("后卫 {0} 进攻", name);
}
public override void Defense()
{
Console.WriteLine("后卫 {0} 防守", name);
}
}
客户端代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Program
{
static void Main(string[] args)
{
Player b = new Forwards("巴蒂尔");
b.Attack();
Player m = new Guards("麦克格雷迪");
m.Attack();
Player ym = new Center("姚明");
ym.Attack();
ym.Defense();
Console.Read();
}
}
结果显示1
2
3
4前锋 巴蒂尔 进攻
后卫 麦克格雷迪 进攻
中锋 姚明 进攻
中锋 姚明 防守
姚明刚来到NBA,他那时还不懂英语,也就是说,他听不懂教练的战术安排,Attack和Defense是什么意思不知道。这样的写法就是有问题的。姚明是外籍中锋,需要有翻译者类来‘适配’。
外籍中锋1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//外籍中锋
class ForeignCenter
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public void 进攻()
{
Console.WriteLine("外籍中锋 {0} 进攻", name);
}
public void 防守()
{
Console.WriteLine("外籍中锋 {0} 防守", name);
}
}
翻译者类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//翻译者
class Translator : Player
{
private ForeignCenter wjzf = new ForeignCenter();
public Translator(string name)
: base(name)
{
wjzf.Name = name;
}
public override void Attack()
{
wjzf.进攻();
}
public override void Defense()
{
wjzf.防守();
}
}
客户端代码改写如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Program
{
static void Main(string[] args)
{
Player b = new Forwards("巴蒂尔");
b.Attack();
Player m = new Guards("麦克格雷迪");
m.Attack();
//Player ym = new Center("姚明");
Player ym = new Translator("姚明");
ym.Attack();
ym.Defense();
Console.Read();
}
}
结果显示1
2
3
4前锋 巴蒂尔 进攻
后卫 麦克格雷迪 进攻
外籍中锋 姚明 进攻
外籍中锋 姚明 防守
代码结构图
尽管姚明曾经是不太懂英文,尽管火箭教练和球员也不会学中文,但因为有了翻译者,团队沟通合作成为了可能。
适配器模式的.NET应用
在现实中也很常用。
比如在.NET中有一个类库已经实现的、非常重要的适配器,那就是DataAdapter。DataAdapter用作DataSet和数据源之间的适配器以便检索和保存数据。DataAdapter通过映射Fill(这更改了DataSet中的数据以便与数据源中的数据相匹配)和Update(这更改了数据源中的数据以便与DataSet中的数据相匹配)来提供这一适配器。由于数据源可能是来自SQL Server,可能来自Oracle,也可能来自Access、DB2,这些数据在组织上可能有不同之处,但我们希望得到统一的DataSet(实质是XML数据),此时用DataAdapter就是非常好的手段,我们不必关注不同数据库的数据细节,就可以灵活的使用数据。
扁鹊的医术
当年,魏文王问名医扁鹊说:‘你们家兄弟三人,都精于医术,到底哪一位最好呢?’扁鹊答:‘长兄最好,中兄次之,我最差。’文王再问:‘那么为什么你最出名呢?’扁鹊答:‘长兄治病,是治病于病情发作之前。由于一般人不知道他事先能铲除病因,所以他的名气无法传出去;中兄治病,是治病于病情初起时。一般人以为他只能治轻微的小病,所以他的名气只及本乡里。而我是治病于病情严重之时。一般人都看到我在经脉上穿针管放血、在皮肤上敷药等大手术,所以大家都以为我的医术高明,名气因此响遍全国。’这个故事说明什么?
如果能事先预防接口不同的问题,不匹配问题就不会发生;在有小的接口不统一问题发生时,及时重构,问题不至于扩大;只有碰到无法改变原有设计和代码的情况时,才考虑适配。事后控制不如事中控制,事中控制不如事前控制。
如果能事前控制,又何必要事后再去弥补呢?“适配器模式当然是好模式,但如果无视它的应用场合而盲目使用,其实是本末倒置了。