概念
- 外观模式是一种结构型模式,为复杂的子系统提供一个统一的接口,使得子系统的功能对外界更加简单、易用。
与真实世界的类比
- 当你通过打电话给商店下达订单时,接线员就是该商店所有服务和部门的外观。 接线员为你提供了一个包含购物系统、支付网关、送货等服务的简单语言接口。
外观模式结构图
// 空调
public class AirCondition {
public void on() {
System.out.println("AirCondition is on");
}
public void off() {
System.out.println("AirCondition is off");
}
}
// 灯
public class Light {
public void on() {
System.out.println("Light is on");
}
public void off() {
System.out.println("Light is off");
}
}
// 电视
public class TV {
public void on() {
System.out.println("TV on");
}
public void off() {
System.out.println("TV off");
}
}
// 外观类,封装了子系统的功能
public class SmartAppliancesFacade {
private TV tv;
private AirCondition airCondition;
private Light light;
public SmartAppliancesFacade() {
tv = new TV();
airCondition = new AirCondition();
light = new Light();
}
public void say(String message){
if(message.contains("打开")){
allOn();
}else if(message.contains("关闭")){
allOff();
}else{
System.out.println("没有这个指令");
}
}
public void allOn() {
tv.on();
airCondition.on();
light.on();
}
public void allOff() {
tv.off();
airCondition.off();
light.off();
}
}
// 客户端类
public class Client {
public static void main(String[] args) {
SmartAppliancesFacade facade = new SmartAppliancesFacade();
facade.say("打开家电");
System.out.println("-----------------------------------------");
facade.say("关闭家电");
System.out.println("-----------------------------------------");
facade.say("shfsahf");
System.out.println("-----------------------------------------");
}
}
适用的应用场景
-
- 如果一个系统由多个模块组成,对外提供多个复杂接口,可以通过外观模式提供一个简单的接口调用。
- 例如:支付系统涉及签名、网络请求、结果处理,通过 Facade Pattern 封装为一个简单的支付接口。
-
- 在分层系统种,可以在子系统的每一层使用 Facade Pattern ,减少高层模块对低层模块的依赖。
-
- 当需要对已有系统添加新的功能或优化时,但不想破坏原有设计,可以通过 Facade Pattern 封装已有系统,提供新的接口。
优点
- 降低客户端与子系统的耦合:
-
- Client 无需了解 Subsystem 的具体实现。
- 提高子系统的灵活性:
-
- Subsystem 可以在不影响 Client 的情况下自由修改。
- 提高可维护性:
-
- Subsystem 的接口变化只需要修改 Facade,而不需要修改 Client 代码
缺点
- 外观类可能会成为上帝对象 (了解过多或者负责过多的对象)
- 在一定程度上违反开闭原则,子系统添加新功能,可能需要修改外观类。
-
- 使用抽象外观类:将
Facade
定义为一个 abstract class
或 interface
,提供基本的功能接口。每次扩展时,可以通过创建新的外观类来实现拓展,替代了直接修改原有的 Facade
。
// 抽象外观类
public abstract class AbstractSmartHomeFacade {
public abstract void startMode();
public abstract void stopMode();
}
// 具体实现类
public class SmartHomeFacade extends AbstractSmartHomeFacade {
private Light light;
private SoundSystem soundSystem;
private AirConditioner airConditioner;
public SmartHomeFacade() {
this.light = new Light();
this.soundSystem = new SoundSystem();
this.airConditioner = new AirConditioner();
}
@Override
public void startMode() {
light.turnOn();
soundSystem.playMusic();
airConditioner.turnOn();
}
@Override
public void stopMode() {
light.turnOff();
soundSystem.stopMusic();
airConditioner.turnOff();
}
}
// 新增扩展外观类
public class AdvancedSmartHomeFacade extends SmartHomeFacade {
private Curtain curtain;
public AdvancedSmartHomeFacade() {
super();
this.curtain = new Curtain();
}
@Override
public void startMode() {
super.startMode();
curtain.close();
}
@Override
public void stopMode() {
super.stopMode();
curtain.open();
}
}
-
- 使用组合代替继承:通过组合的方式,将新增的功能封装成独立的类,再将其组合进外观类,替代直接
Facade
的代码。
public class SmartHomeFacade {
private List<Object> subsystems = new ArrayList<>();
public void addSubsystem(Object subsystem) {
subsystems.add(subsystem);
}
public void startAll() {
for (Object subsystem : subsystems) {
// 统一调用子系统的 start 方法
// 可以通过接口或反射实现
}
}
public void stopAll() {
for (Object subsystem : subsystems) {
// 统一调用子系统的 stop 方法
}
}
}
public class Client {
public static void main(String[] args) {
// 创建外观类
SmartHomeFacade facade = new SmartHomeFacade();
// 添加子系统
facade.addSubsystem(new Light());
facade.addSubsystem(new SoundSystem());
facade.addSubsystem(new AirConditioner());
// 省略
// …………………………………………
// 启动所有子系统
System.out.println("Starting all subsystems...");
facade.startAll();
// 停止所有子系统
System.out.println("\nStopping all subsystems...");
facade.stopAll();
}
}
案例实现
在源码中的应用——Tomcat
- 使用 Tomcat 作为 Web 容器时,Coyote 接受浏览器发送的请求,封装为
Request
。为了符合 Servlet API 标准,Tomcat 使用 RequestFacade
(实现了 HttpServletRequest
接口)对内部的 Request
对象进行包装。
- 所以,在 Servlet 中开发者接触到的
Request
其实是 RequestFacade
,通过 Facade 模式,隐藏了实现细节,确保安全性和封装性。
与其他设计模式的关系
- 外观模式为现有对象定义了一个新接口, 适配器模式则会试图运用已有的接口。适配器通常只封装一个对象,外观通常会作用于整个对象子系统上。
- 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂模式来代替外观。
- 享元模式展示了如何生成大量的小型对象, 外观则展示了如何用一个对象来代表整个子系统。
- 外观和中介者模式的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。
-
- 外 观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
- 中 介 者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。
- 外观类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。
- 外观与代理模式的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。