设计模式——适配器与外观

  
  这次是两个模式,适配器模式 & 外观模式,为什么一下子写两个模式呢?来想想上次讲的装饰模式的功能是什么?我个人感觉:他将一个有相同超类的类进行包装,从而使其获得新的功能;而在看完适配器模式和外观模式之后,我觉得他们三者很像:一个通过包装增加功能,另一个通过包装改变接口,最后一个通过包装简化接口。这样一看可能会觉得比较绕,没事,接下来我会理清这3者之间的相似与不同。依然会用到那个例子,你问我那个?看看前面几章就知道了…
  

适配器模式

  
  适配器模式:将一个类的接口,转换成客户期望的另一个接口。让原本接口不兼容的类可以合作。

常见的适配器

  适配器这个概念应该很好理解,现实中也有很常见的例子,比如各种数据线的转接头。更具体的说:电脑上都有usb插口,这就是一个接口,而任何实现了这个接口的对象都可以和电脑进行连接,比如U盘。那么如果手机想和电脑连接怎么办呢,手机也没有实现usb接口啊(没有符合usb接口的插头),所以手机没法直接和电脑连接(手机没法像U盘那样直接插在电脑上)。
  你可能会说,这不是有数据线么? 对,数据线就可以当做是适配器,一头是usb插头连接电脑,另一头是符合你的手机的接头,他完成了将两个不同的头转换的功能。那么将其抽象,手机和电脑就是 原有类 和 目标类,(哪个是哪个无所谓,只需知道他们的接口不同就好了),而数据线就是 适配器类。

请假的厨师

  天气无比的热,这40多度的天气真不是人能承受的,尤其是在厨房啊!所以啊,Pizza店的厨师中暑无法工作(真是个悲伤的故事)可是店依旧得开下去,怎么办呢?只能先随便找个人顶上去!于是你去隔壁的酒楼花了不少money“借”了个厨师,让他先顶上。人是借到了,但是现在来不及给他培训了怎么办呢?由于比较紧急,“那就安你会做的东西来做!”你这样对厨师说道。他一拍胸脯“这事包我身上了”(最近一有空就在看武林外传…)
  先来对比一下这两位厨师的类吧(突然感觉能把人抽象好可怕…)

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
/**
* 这个可以算是目标接口
*/
public interface PizzaChef{
public void doughFlat();
public void addOutside();
public void bake();
}

/**
* 你原来的厨师
*/
public class ChefPizza implements PizzaChef{

public void doughFlat(){
System.out.println("pizza的揉面团方法,做个面饼");
}

public void addOutside(){
System.out.println("在面饼上放上各种素材");
}

public void bake(){
System.out.println("烘烤pizza");
}
}


/**
* 新来的那个,会降龙十巴掌那个...
*/
public class ChefTongfu{

public void doughRound(){
System.out.println("还能怎么办呢,瞎揉呗");
}

public void steamedBun(){
System.out.println("做包子");
}

public void steamed(){
System.out.println("蒸包子");
}
}

  可以看到 以上两个厨师里的方法完完全全不一样,在现在的程序体系中使用ChefPizza的方法,新的ChefTongfu没有,所以需要一个适配器来进行转接。下面就看看这个适配器该怎么写,看完你会发现原来这么简单。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Adapter implements PizzaChef{
ChefTongfu cheftongfu;

public Adapter(ChefTongfu obj){
this.cheftongfu = obj;
}

public void doughFlat(){
cheftongfu.doughRound();
}

public void addOutside(){
cheftongfu.steamedBun();
}

public void bake(){
cheftongfu.steamed();
}
}

  首先这个适配器实现了目标接口,理解上就是:你这个适配器是将其他对象转换成 满足PizzaChef 这个接口的东西的,然后是将什么东西转换呢?看构造方法,在上面的构造方法中,接受了ChefTongfu类型的对象,将其保存。然后在PizzaChef接口所需实现的方法中,调用ChefTongfu中对应要转换的方法。由于其实现了PizzaChef接口,所以对外那些面向接口编程的代码中,Adapter对象可以当做是一个PizzaChef来使用,他的具体方法一一对应到了他构造方法接受到的对象的那些方法。
  说了这么多绕口的,下面直接写一下具体的使用也比较清晰

1
2
3
4
5
6
7
8
9
10
11
public class Simulator{
public static void main(String args[]){
ChefTongfu lidazui = new ChefTongfu();//这个是待转对象
Adapter exlidazui = new Adapter(lidazui);//这个就是转换过的对象

//可使用目标接口的方法了
exlidazui.doughFlat();
exlidazui.addOutside();
exlidazui.bake();
}
}

  现在已经成功的让新来的厨师去工作了,虽然他做出的东西可能并不是你想要的,但是。。。他至少顶上了这一天不是么?至于效果就不要多追究了。。。
最后看一下适配器模式的类图
img

双向适配器

  上面写的这些,从功能上来说,是单向适配器,何为单向?就字面理解,他只能讲一种类转换成目标类。那双向适配器就很容易的理解了,可以支持双向转换。在深透理解了单向适配器后就很容易去写了。

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
57
58
59
60
61
62
63
64
65
public interface TongfuChef{
public void doughRound();
public void steamedBun();
public void steamed();
}


public class ChefTongfu implements TongfuChef{

public void doughRound(){
System.out.println("还能怎么办呢,瞎揉呗");
}

public void steamedBun(){
System.out.println("做包子");
}

public void steamed(){
System.out.println("蒸包子");
}
}

public TwoWayAdapter implements PizzaChef,TongfuChef{
TongfuChef tongfu;
PizzaChef pizza;

public TwoWayAdapter(TongfuChef obj){
this.tongfu = obj;
}

public TwoWayAdapter(PizzaChef obj){
this.pizza = obj;
}

public TwoWayAdapter(TongfuChef obj1,PizzaChef obj2){
this.tongfu = obj1;
this.pizza = obj2;
}

//TongfuChef 转 PizzaChef
public void doughFlat(){
tongfu.doughRound();
}

public void addOutside(){
tongfu.steamedBun();
}

public void bake(){
tongfu.steamed();
}

//PizzaChef 转 TongfuChef
public void doughRound(){
pizza.doughFlat();
}

public void steamedBun(){
pizza.addOutside();
}

public void steamed(){
pizza.bake();
}
}

  因为现在是双向适配器,所以所谓的目标类又多了一个,我就给原先的ChefTongfu 做了一个接口TongfuChef,取名字反了一下(这个是我的错,我想不出有什么名字了,所以在看的时候一定一定要分清是 接口 还是 类)。然后在双向适配器TwoWayAdapter中,我写了3个构造方法,第一个是在单向适配器中已经解释过了的,第二个同理,反向的一个构造方法。这里我解释一下第三个,他可以同时担任两者的适配器。有人可能会说,有了第一个和第二个构造方法,第三个还有必要么? 我举个例子:有一条单行道,他既可以从左往右(第一种构造),也可以从右往左(第二种构造),但无论是哪一种,他同一时刻只能担任一种角色,若要给2个同时做适配,那就需要多创建一个实例。我突发奇想,同时接受2个参数的话,就可以同时当做2种对象来用了,这样很cooooooooool不是么。。(虽然我不是很推荐这样干。。)在最后写的是2种类具体的转换,详细的不多赘述,在单向中已经说过了。
  至于双相适配器的类图嘛、、大家想想应该是什么样子的呢?探索一下吧
  

java中的适配器

  在java中如果使用过集合set,那么肯定对Iterator不陌生,迭代器。他可以方便的让你遍历集合类型中的元素。但是,在Iterator之前还有一个Enumeration枚举器这个东西,不过已经很久不用了。但是如果要处理一些历史遗留代码(一些老程序)可能他用的就是enum,这时候就可以自己写一个适配器了。现在已知Iterator接口中有hasNext(),next(),remove()方法、在Enumeration中有hasMoreElements(),nextElement()方法,那么试试看如何写一个适配器呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class EnumerationIterator implements Iterator{
Enumeration enum;

public EnumerationIterator(Enumeration obj){
this.enum = obj
}

public boolean hasNext(){
return enum.hasMoreElements();
}

public Object next(){
return enum.nextElement();
}

public void remove(){
return new UnsupportedOperationException();
}
}

  在写适配器的时候最重要的就是在两者之间找到映射关系,这里看上面的remove方法,由于在enum中没有可以转换的方法,但是空着不写也不行(不写的话就不会执行任何东西,客户会觉得是代码哪里出错了“我按了啊?怎么没反应呢?”)所以在这里抛出一个运行时的异常,通知外部这里的情况。

  

*对象适配器和类适配器

  以上说的全部都是对象适配器,类适配器涉及到了多重继承,而在java中是没有多种继承的,所以我并不很清楚这到底是什么。不过根据参考资料所说,类适配器与对象适配器唯一的区别就是,类适配器继承了Target 和 Adaptee。而对象适配器利用组合的方法来将请求传递给别适配者。如果有兴趣的可自己去查阅一下这方面的资料。
  

外观模式

好了 外观严意义上来说是另一个模式吧。那下周更吧。。

三方会谈

这里主要是 写 装饰者 外观 适配器 三个的比较下周再说