适配器模式与外观模式

适配器模式

定义

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


适配器工作起来就如同一个中间人,它将客户所发出的请求转换成厂商类能理解的请求。

结构图

实现

如果一个动物,走起路来像鸭子,叫起来像鸭子,那么它可能是一只包装了鸭子适配器的火鸡……

下面先创建鸭子的接口和一个鸭子的具体实现类:

1
2
3
4
public interface Duck {
public void quack();
public void fly();
}
1
2
3
4
5
6
7
8
9
10
11
12
public class MallarDuck implements Duck {

@Override
public void quack() {
System.out.println("Quack!");
}

@Override
public void fly() {
System.out.println("I'm flying!");
}
}

接下来创建与鸭子不同的火鸡接口和具体实现类:

1
2
3
4
public interface Turkey {
public void gobble();
public void fly();
}
1
2
3
4
5
6
7
8
9
10
11
12
public class WildTurkey implements Turkey{

@Override
public void gobble() {
System.out.println("Gobble gobble!");
}

@Override
public void fly() {
System.out.println("I'm flying a short distance");
}
}

火鸡的叫声和鸭子不一样,而且飞行距离也没有鸭子远。

现在假设你缺鸭子对象,想用一些火鸡对象来冒充,显而易见,因为火鸡的接口不同,所以我们不能公然拿来用,因此就写一个适配器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TurkeyAdapter implements Duck {
Turkey turkey;

public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}

@Override
public void quack() {
turkey.gobble();
}

@Override
public void fly() {
for (int i = 0; i < 5; i++) {
turkey.fly();
}
}
}

下面创建一个测试类来测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DuckTestDrive {
public static void main(String[] args) {
Duck duck = new MallarDuck();
Turkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);

System.out.println("The Turkey says...");
turkey.gobble();
turkey.fly();

System.out.println("The Duck says...");
duck.quack();
duck.fly();

System.out.println("THe TurkeyAdapter sasy...");
turkeyAdapter.quack();
turkeyAdapter.fly();
}
}

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
The Turkey says...
Gobble gobble!
I'm flying a short distance
The Duck says...
Quack!
I'm flying!
THe TurkeyAdapter sasy...
Gobble gobble!
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance

客户使用适配器模式的过程如下:

  1. 客户通过目标接口调用适配器的方法对适配器发出请求。
  2. 适配器使用被适配者接口把请求转换成被适配者的一个或多个调用接口。
  3. 客户接收到调用的结果,但并未察觉这一切是适配器在起转换作用。

总结

  • 适配器模式的主要优点是将目标类和适配者类解耦,增加了类的透明性和复用性,同时系统的灵活性和扩展性都非常好,更换适配器或者增加新的适配器都非常方便,符合“开闭原则”;
  • 类适配器模式的缺点是适配器类在很多编程语言中不能同时适配多个适配者类,对象适配器的缺点是很难置换适配者类的方法。
  • 适配器模式适用情况包括:系统需要使用现有的类,而这些类的接口不符合系统的需要;想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类一起工作。

外观模式

我们现在要看一个改变接口的新模式,但是它改变接口的原因是为了简化接口。这个模式被巧妙地命名为外观模式(Facade Pattern),之所以这么称呼,是因为它将一个或数个类的复杂的一切都隐藏在背后,只显露出一个干净美好的外观。

定义

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

结构图

实现

想象你有一个家庭影院系统,当你想要观看电影时,如果没有外观模式,你还需要一步步完成操作,当有了外观模式,这些都交给外观模式来一键看电影。

首先创建一个家庭影院类:

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
public class MovieSystem {
public void turnOnTV() {
System.out.println("Turn On TV");
}

public void setCD(String cd) {
System.out.println("Set CD :" + cd);
}

public void startWatching() {
System.out.println("Star Watching TV");
}

public void turnOffTV() {
System.out.println("Turn Off TV");
}

public void outCD(String cd) {
System.out.println("Out CD :" + cd);
}

public void endtWatching() {
System.out.println("End Watching TV");
}
}

然后创建外观模式类,帮助你一键完成操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Facade {
MovieSystem movieSystem;
public Facade(MovieSystem movieSystem) {
this.movieSystem = movieSystem;
}

public void watchMovie(String cd) {
movieSystem.turnOnTV();
movieSystem.setCD(cd);
movieSystem.startWatching();
}

public void overMovie(String cd) {
movieSystem.turnOffTV();
movieSystem.outCD(cd);
movieSystem.endtWatching();
}
}

当你需要看电影时,只需要点一下:

1
2
3
4
5
6
7
8
9
10
public class Client {
public static void main(String[] args) {
MovieSystem movieSystem = new MovieSystem();
Facade facade = new Facade(movieSystem);

facade.watchMovie("变形金刚");
System.out.println("Watching...");
facade.overMovie("变形金刚");
}
}

就可以让外观模式类帮你完成整个过程:

1
2
3
4
5
6
7
Turn On TV
Set CD :变形金刚
Star Watching TV
Watching...
Turn Off TV
Out CD :变形金刚
End Watching TV

总结

  • 设计原则:“最少知识”原则:只和你的密友交谈。也就是说客户对象所需要交互的对象应当尽可能少。
  • 外观模式的主要优点在于对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易,它实现了子系统与客户之间的松耦合关系,并降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程。
  • 其缺点在于不能很好的限制客户使用子系统类,而且不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类和客户端的源代码,违背了“开闭原则”。
  • 外观模式适用情况包括:要为一个负责子系统提供一个简单接口;客户程序与多个子系统之间存在很大的依赖性;在层次化结构中,需要定义系统中每一层的入口,使得层与层之间不产生联系。

总结

  • 当需要使用一个现有的类而其接口不符合你的需要时,就是用适配器。
  • 当需要简化并统一一个很大的接口或一群复杂的接口时,使用外观。
  • 适配器改变接口以符合客户的期望。
  • 外观将客户从一个复杂的子系统中解耦。
  • 实现一个适配器可能需要一番功夫,也可能不费功夫,视目标接口的大小与复杂度而定。
  • 实现一个外观,需要将子系统组合进外观中,然后将工作委托给子系统执行。
  • 适配器模式有两种形式:对象适配器和类适配器。类适配器需要用到多重继承。
  • 你可以为一个子系统实现一个以上的外观。
  • 适配器将一个对象包装起来以改变其接口;而外观将一群对象“包装”起来以简化其接口。