前言
举个现实生活中例子,泡茶和去茶馆喝茶的区别,如果是自己泡茶需要自行准备茶叶、茶具和开水,而去茶馆喝茶,最简单的方式就是跟茶馆服务员说想要一杯什么样的茶,是铁观音、碧螺春还是西湖龙井?正因为茶馆有服务员,顾客无须直接和茶叶、茶具、开水等交互,整个泡茶过程由服务员来完成,顾客只需与服务员交互即可,整个过程非常简单省事。
以上这种场景类似设计模式中的外观模式,也叫做门面模式。外观模式通过引入一个新的外观类来实现该功能,外观类充当了“服务员”的角色,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。本文我们一起来聊聊外观模式。
外观模式介绍
外观模式是一种使用频率非常高的结构型设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度,且客户端调用非常方便。
外观模式结构图:
(1) Facade(外观角色):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
(2) SubSystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。
案例场景
场景一:自己泡茶,整个过程应该是“烧开水->拿茶具->泡茶叶”然后喝茶。
定义一个饮用水类:DrinkableWater
/**
* 烧水
*/
public class DrinkableWater {
public DrinkableWater(){
System.out.println("水准备好了");
}
//烧水
public void facadeWater(){
System.out.println("水烧开了");
}
}
定义一个茶叶类:Tea
/**
* 茶叶
*/
public class Tea {
public Tea(){
System.out.println("茶叶准备好了");
}
//取茶
public void facadeTea(){
System.out.println("可以泡茶了");
}
}
定义一个茶杯类:TeaCup
/**
* 茶杯
*/
public class TeaCup {
public TeaCup(){
System.out.println("茶杯准备好了");
}
//泡茶
public void facadeTeaCup(Tea tea){
tea.facadeTea();
System.out.println("茶叶泡进茶杯了");
System.out.println("茶冲好了");
}
}
准备就绪,开始泡茶
public static void main(String[] args) {
System.out.println("准备泡茶...");
DrinkableWater drinkableWater = new DrinkableWater();
TeaCup teaCup = new TeaCup();
Tea tea = new Tea();
drinkableWater.facadeWater();
teaCup.facadeTeaCup(tea);
System.out.println("喝茶...");
}
运行结果
准备泡茶...
水准备好了
茶杯准备好了
茶叶准备好了
水烧开了
可以泡茶了
茶叶泡进茶杯了
茶冲好了
喝茶...
场景二:去茶馆喝茶,不用自己动手泡茶了,直接告诉茶馆的服务员就行了。
定义一个服务员类:
/**
* 服务员
*/
public class Waiter {
private DrinkableWater drinkableWater = new DrinkableWater();
private TeaCup teaCup = new TeaCup();
private Tea tea = new Tea();
//获得一杯茶
public void getTea(){
drinkableWater.facadeWater();
teaCup.facadeTeaCup(tea);
}
}
客户类:Customer
public class Customer {
public static void main(String[] args) {
//叫店小二
Waiter waiter = new Waiter();
//从店小二那获得一杯茶
waiter.getTea();
}
}
运行结果
水准备好了
茶杯准备好了
茶叶准备好了
水烧开了
可以泡茶了
茶叶泡进茶杯了
茶冲好了
在上面的泡茶的例子中,客人就是客户角色,茶馆服务员就是门面角色,茶具、饮用水、茶叶就是子系统角色。
代码看上去都很简单,也许你感觉二者区别不是很大,假如在软件开发中三个子系统之间有先后顺序,还有来自不同网络开销,我们通过门面模式提供的方法,就屏蔽了这些差异,让我们只需要调用门面角色提供给我们的方法即可。
外观模式优点
- 减少系统的相互依赖
如果我们不使用门面模式, 外界访问直接深入到子系统内部, 相互之间是一种强耦合关系, 你死我就死, 你活我才能活, 这样的强依赖是系统设计所不能接受的, 门面模式的出现就很好地解决了该问题, 所有的依赖都是对门面对象的依赖, 与子系统无关。
- 提高安全性
想让你访问子系统的哪些业务就开通哪些逻辑, 不在门面上开通的方法, 你休想访问到。
外观模式缺点
-
不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。
-
如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。
外观模式应用场景
- 解决易用性问题
门面模式可以用来封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用、更高层的接口。
- 解决性能问题
通过将多个接口调用替换为一个门面接口调用,减少网络通信成本,提高客户端的响应速度。
- 解决分布式事务问题
需要调用多个子系统的接口方法,而这些接口要么都成功,要么都失败,我们就可以利用门面模式包裹这些子系统接口,然后通过某种方法保证这些接口在一个事务中完成。
总结
在几乎所有的软件中都能够找到外观模式的应用,如绝大多数B/S系统都有一个首页或者导航页面,大部分C/S系统都提供了菜单或者工具栏,在这里,首页和导航页面就是B/S系统的外观角色,而菜单和工具栏就是C/S系统的外观角色,通过它们用户可以快速访问子系统,降低了系统的复杂程度。所有涉及到与多个业务对象交互的场景都可以考虑使用外观模式进行重构。
很多时候不是设计模式没有用,而是自己编程开发经验不足导致即使学了设计模式也很难驾驭。毕竟这些知识都是经过一些实际操作提炼出来的精华。