一、概述
外观模式(Facade Pattern)是一种结构型设计模式,它为多个子系统中的操作提供一个统一的高层接口,用于访问复杂子系统的功能。其核心思想是通过一个外观类将子系统的复杂操作进行封装,客户端只需与外观类进行交互,无需了解子系统的具体操作细节。这大大简化了客户端的使用,同时降低了客户端和子系统的耦合度。
通俗的讲,我们要实现一个功能,可能需要调用一个子系统中的多个操作,或者组合不同子系统中的操作。而外观模式就是提供一个门面,即在一堆子系统之上,提供一个统一的外界访问平台,通过该平台可以一键完成复杂的子系统功能。
今天是六一儿童节,一起致童真,我们用游乐园举个例子。一个大型儿童游乐园,会有很多的收费项目,我们可以在每个项目游玩地单独购票游玩,也可以在游乐场大门口的综合售票平台或者网上平台直接购买所有项目门票,甚至购买通玩票,这大大缩短了排队等待的时间。这就是外观模式的应用,各个项目的售票处代表各个子系统,综合售票平台就是我们的外观,它提供统一的方式访问各个子售票系统。
外观模式涉及的角色
- 外观角色(Facade ):一个单独的对象,内部持有子系统的引用,其中定义了一系列方法以访问子系统中的功能。
- 子系统角色(SubSystem):一组相互关联的类或对象,每个子系统可以独立运作。
- 客户端(Client):直接与外观对象进行交互,从而间接访问子系统功能。
实现原理
- 客户端通过实例化外观对象来访问子系统。
- 外观对象内部持有子系统的实例引用。
- 外观对象将客户端的请求委派给子系统的相应组件。
- 子系统的组件处理具体的工作,并将结果返回给外观对象。
- 外观对象将结果返回给客户端。
优点
- 隐藏子系统的复杂性和具体细节,简化客户端使用:外观模式让客户端通过一个简单的接口与复杂子系统进行交互,无需关注子系统的具体实现,降低了客户端使用难度。
- 降低客户端和子系统之间的耦合:客户端不直接和子系统交互,使得子系统的变更不会对客户端造成太大影响,是“迪米特法则“的体现。
- 易于维护:将子系统的操作封装在一个外观类中,使维护和修改变得更容易。
缺点
- 不符合开闭原则:增加新的子系统可能需要修改外观类或客户端代码。
- 可能导致功能未被充分利用:统一的高层接口可能会限制子系统的功能使用。
- 可能造成滥用:将过多的功能放在外观对象中,可能导致外观对象变得庞大和复杂。
适用场景
- 当你需要为多个复杂子系统提供一个统一的操作接口或简单入口时,可以使用外观模式。
- 当希望将系统的复杂性隐藏起来,只向客户端暴露必要的接口时,可以使用外观模式。
- 当客户端与多个子系统之间存在很多依赖关系,希望降低耦合度,可以使用外观模式。
二、案例实现
案例分析
如上图案例所示,我们有三个儿童游乐项目积木乐园、碰碰车和旋转木马,分别代表三个子系统,首先我们可以定义一个抽象接口ChildrenPlayItem,表示所有游乐项目的公共接口,里面定义了购票和游玩的公共行为,然后三个子系统分别实现这个接口完成子系统的功能。接下来定义一个外观类PlayEntry,表示游玩入口,我们要玩什么项目,都从这个入口进入。不管选择几个项目,都可以通过这个入口进行购票和游玩,最后还可以计算所有花费的票价总额。
代码实现
步骤1:创建游乐园项目抽象接口ChildrenPlayItem,里面声明购票和游玩的方法。
public interface ChildrenPlayItem {
int ticket();
void play();
}
步骤2:分别实现三个子系统:积木乐园、碰碰车、旋转木马
public class BlockPark implements ChildrenPlayItem {
private int price=12;
@Override
public int ticket() {
System.out.println("购买积木乐园项目票,票价"+price);
return price;
}
@Override
public void play() {
System.out.println("堆积木咯...");
}
}
public class BumperCar implements ChildrenPlayItem {
private int price=88;
@Override
public int ticket() {
System.out.println("购买碰碰车项目票,票价"+price);
return price;
}
@Override
public void play() {
System.out.println("玩碰碰车咯...");
}
}
public class Carousel implements ChildrenPlayItem {
private int price=39;
@Override
public int ticket() {
System.out.println("购买旋转木马项目票,票价"+price);
return price;
}
@Override
public void play() {
System.out.println("玩旋转木马咯...");
}
}
步骤3:创建外观类PlayEntry作为游玩入口
public class PlayEntry {
private ChildrenPlayItem blockPark; //积木乐园
private ChildrenPlayItem bumperCar; //碰碰车
private ChildrenPlayItem carousel; //旋转木马
private int charge;
public PlayEntry() {
this.blockPark = new BlockPark();
this.bumperCar = new BumperCar();
this.carousel = new Carousel();
this.charge=0;
}
//玩积木
public void playBlockPark(){
int itemPrice=blockPark.ticket();
blockPark.play();
charge+=itemPrice;
}
//玩碰碰车
public void playBumperCar(){
int itemPrice=bumperCar.ticket();
bumperCar.play();
charge+=itemPrice;
}
//玩旋转木马
public void playCarousel(){
int itemPrice=carousel.ticket();
carousel.play();
charge+=itemPrice;
}
//计费
public void pay(){
System.out.println("一共要支付的项目票价总额为:"+charge);
}
}
步骤4:客户端,调用外观类中声明的方法,分别进行三个项目的游玩体验
public class Client {
public static void main(String[] args) {
PlayEntry entry=new PlayEntry();
entry.playBlockPark();
entry.playBumperCar();
entry.playCarousel();
entry.pay();
}
}
测试结果
三、总结
外观模式为复杂子系统提供了一个简化的接口,让客户端更轻松地操控子系统,同时降低了客户端与子系统之间的耦合度。外观模式也有一些缺点,比如不符合开闭原则,可能导致功能未被充分利用。当然,在合适的场景下,这些缺点就是仁者见仁智者见智了。希望大家在平常的软件开发过程中从能源码中多多发现和学习,这样才可以真正学会设计模式的精髓。
感谢大家阅读这篇关于外观模式讲解的文章,设计模式之美专栏文章目前已经更新了一半,如果大家正在学习设计模式,可以订阅我的专栏,我尽量用最经典的案例、最简洁的代码帮助大家更好地理解和掌握设计模式。如果专栏文章有所疏漏或者不当之处,非常欢迎大家留言指正,希望大家共同进步。