设计模式-外观模式

文章目录

  • 一、概述
    • [1.1 结构与角色](#1.1 结构与角色)
    • [1.2 适用场景](#1.2 适用场景)
  • 二、实现方式
    • [2.1 基础实现------智能家居系统](#2.1 基础实现——智能家居系统)
    • [2.2 使用外观前后的对比](#2.2 使用外观前后的对比)
    • [2.3 实际应用示例------应用启动引导系统](#2.3 实际应用示例——应用启动引导系统)
  • [三、外观模式 vs 中介者模式](#三、外观模式 vs 中介者模式)
    • [3.1 意图对比](#3.1 意图对比)
    • [3.2 结构对比](#3.2 结构对比)
    • [3.3 代码对比](#3.3 代码对比)
    • [3.4 选择指南](#3.4 选择指南)
  • 四、总结

一、概述

在软件开发中,经常会遇到这样的场景:一个复杂的子系统由多个组件组成,客户端需要依次调用这些组件的方法才能完成一个业务操作。客户端直接与子系统中的众多组件交互,不仅代码复杂、耦合度高,而且难以维护。例如,启动一台家庭影院需要先打开电视、再打开音响、再打开投影仪、再调暗灯光......客户端需要了解每个组件的细节。

外观模式(Facade Pattern)正是为了解决这个问题而诞生的------它为子系统中的一组接口提供一个一致的界面 ,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式不是新增功能,而是对已有功能的封装和简化

生活中的外观模式例子比比皆是:

  • 智能家居一键启动:按下一个"回家模式"按钮,系统自动开灯、开空调、拉窗帘、播放音乐,无需逐一操作每个设备
  • 餐厅点餐:顾客只需告诉服务员"来一份套餐A",服务员会在后厨协调各岗位(凉菜、热菜、甜点、饮料)完成出餐,顾客不需要与后厨直接沟通
  • 客服热线:拨打一个号码就能获得咨询、投诉、售后等多种服务,不需要记住各个部门的分机号

核心:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,使得子系统更加容易使用

1.1 结构与角色

外观模式包含以下角色:
Client 客户端
Facade 外观角色
SubsystemA 子系统A
SubsystemB 子系统B
SubsystemC 子系统C
组件A1
组件A2
组件B1
组件C1

  • Facade(外观角色):为子系统提供统一的对外接口,将客户端的请求委派给适当的子系统对象处理
  • Subsystem(子系统角色):子系统中的一组类,负责实际的功能实现,外观角色了解子系统角色的功能和职责,但子系统角色不了解外观角色的存在
  • Client(客户端):通过外观角色与子系统交互,无需直接与子系统中的各个组件通信

1.2 适用场景

  • 为复杂的子系统提供一个简单的对外接口,降低客户端与子系统之间的耦合度
  • 客户端不需要了解子系统内部的复杂逻辑,只需要通过外观角色完成操作
  • 需要构建多层结构的子系统,每层之间通过外观角色进行通信
  • 需要将子系统与客户端分离,使子系统的变化不会影响客户端

二、实现方式

外观模式的核心实现思路是:定义一个外观类,持有子系统中各组件的引用,并提供简化的方法将客户端的请求委派给子系统中适当的组件处理。

2.1 基础实现------智能家居系统

以智能家居系统为例,一个"回家模式"需要协调灯光、空调、窗帘、音乐等多个设备,外观角色将这一系列操作封装为一个简单的方法:
客户端
SmartHomeFacade 智能家居外观
Light 灯光
AirConditioner 空调
Curtain 窗帘
MusicPlayer 音乐播放器

(1)子系统------灯光

java 复制代码
/**
 * 子系统角色:灯光
 */
public class Light {

    /**
     * 开灯
     */
    public void on() {
        System.out.println("灯光:打开灯光");
    }

    /**
     * 关灯
     */
    public void off() {
        System.out.println("灯光:关闭灯光");
    }

    /**
     * 调节亮度
     *
     * @param level 亮度等级(0-100)
     */
    public void setBrightness(int level) {
        System.out.println("灯光:调节亮度为 " + level + "%");
    }
}

(2)子系统------空调

java 复制代码
/**
 * 子系统角色:空调
 */
public class AirConditioner {

    /**
     * 开空调
     */
    public void on() {
        System.out.println("空调:打开空调");
    }

    /**
     * 关空调
     */
    public void off() {
        System.out.println("空调:关闭空调");
    }

    /**
     * 设置温度
     *
     * @param temperature 目标温度
     */
    public void setTemperature(int temperature) {
        System.out.println("空调:设置温度为 " + temperature + "℃");
    }
}

(3)子系统------窗帘

java 复制代码
/**
 * 子系统角色:窗帘
 */
public class Curtain {

    /**
     * 打开窗帘
     */
    public void open() {
        System.out.println("窗帘:打开窗帘");
    }

    /**
     * 关闭窗帘
     */
    public void close() {
        System.out.println("窗帘:关闭窗帘");
    }
}

(4)子系统------音乐播放器

java 复制代码
/**
 * 子系统角色:音乐播放器
 */
public class MusicPlayer {

    /**
     * 播放音乐
     *
     * @param song 歌曲名
     */
    public void play(String song) {
        System.out.println("音乐:播放「" + song + "」");
    }

    /**
     * 停止播放
     */
    public void stop() {
        System.out.println("音乐:停止播放");
    }
}

(5)外观角色------智能家居外观

java 复制代码
/**
 * 外观角色:智能家居外观
 * 封装子系统的复杂操作,提供简化的接口
 */
public class SmartHomeFacade {

    private final Light light;
    private final AirConditioner airConditioner;
    private final Curtain curtain;
    private final MusicPlayer musicPlayer;

    public SmartHomeFacade() {
        this.light = new Light();
        this.airConditioner = new AirConditioner();
        this.curtain = new Curtain();
        this.musicPlayer = new MusicPlayer();
    }

    /**
     * 回家模式------一键启动
     */
    public void goHome() {
        System.out.println("====== 回家模式启动 ======");
        light.on();
        light.setBrightness(70);
        airConditioner.on();
        airConditioner.setTemperature(26);
        curtain.close();
        musicPlayer.play("轻松音乐");
        System.out.println("====== 回家模式就绪 ======");
    }

    /**
     * 离家模式------一键关闭
     */
    public void leaveHome() {
        System.out.println("====== 离家模式启动 ======");
        light.off();
        airConditioner.off();
        curtain.open();
        musicPlayer.stop();
        System.out.println("====== 离家模式完成 ======");
    }
}

(6)客户端调用

java 复制代码
public class SmartHomeDemo {
    public static void main(String[] args) {
        SmartHomeFacade smartHome = new SmartHomeFacade();

        // 回家模式------一键启动,无需关心子系统细节
        smartHome.goHome();
        // ====== 回家模式启动 ======
        // 灯光:打开灯光
        // 灯光:调节亮度为 70%
        // 空调:打开空调
        // 空调:设置温度为 26℃
        // 窗帘:关闭窗帘
        // 音乐:播放「轻松音乐」
        // ====== 回家模式就绪 ======

        // 离家模式------一键关闭
        smartHome.leaveHome();
        // ====== 离家模式启动 ======
        // 灯光:关闭灯光
        // 空调:关闭空调
        // 窗帘:打开窗帘
        // 音乐:停止播放
        // ====== 离家模式完成 ======
    }
}

关键点 :客户端只需要调用 goHome()leaveHome() 即可完成复杂的操作,无需了解灯光、空调、窗帘、音乐播放器的具体接口和调用顺序。外观角色封装了子系统的复杂性,提供了简洁的对外接口。

2.2 使用外观前后的对比

如果不使用外观模式,客户端需要直接与子系统中每个组件交互:

java 复制代码
/**
 * 不使用外观模式------客户端直接操作子系统
 */
public class WithoutFacadeDemo {
    public static void main(String[] args) {
        // 客户端需要了解每个子系统的接口和调用顺序
        Light light = new Light();
        AirConditioner airConditioner = new AirConditioner();
        Curtain curtain = new Curtain();
        MusicPlayer musicPlayer = new MusicPlayer();

        // 回家------需要逐一操作每个设备
        light.on();
        light.setBrightness(70);
        airConditioner.on();
        airConditioner.setTemperature(26);
        curtain.close();
        musicPlayer.play("轻松音乐");

        // 如果需要修改回家模式的逻辑,需要在所有客户端代码中修改
    }
}
对比维度 不使用外观模式 使用外观模式
客户端代码 冗长,需要了解所有子系统 简洁,只需调用外观方法
耦合度 高,客户端与子系统紧耦合 低,客户端只与外观耦合
可维护性 差,逻辑变更需修改所有客户端 好,只需修改外观类
可读性 低,操作步骤分散 高,语义化的方法名

2.3 实际应用示例------应用启动引导系统

以应用启动引导系统为例,一个应用的启动需要初始化配置、数据库连接、缓存、日志等子系统,外观角色将这一系列初始化操作封装为简单的方法:
客户端
AppStartupFacade 应用启动外观
ConfigManager 配置管理器
DatabaseConnector 数据库连接器
CacheManager 缓存管理器
LogManager 日志管理器

(1)子系统------配置管理器

java 复制代码
/**
 * 子系统角色:配置管理器
 */
public class ConfigManager {

    /**
     * 加载配置
     */
    public void loadConfig() {
        System.out.println("配置:加载应用配置文件");
    }

    /**
     * 验证配置
     */
    public void validateConfig() {
        System.out.println("配置:验证配置项完整性");
    }
}

(2)子系统------数据库连接器

java 复制代码
/**
 * 子系统角色:数据库连接器
 */
public class DatabaseConnector {

    /**
     * 初始化连接池
     */
    public void initConnectionPool() {
        System.out.println("数据库:初始化连接池");
    }

    /**
     * 测试连接
     */
    public void testConnection() {
        System.out.println("数据库:测试连接是否正常");
    }
}

(3)子系统------缓存管理器

java 复制代码
/**
 * 子系统角色:缓存管理器
 */
public class CacheManager {

    /**
     * 初始化缓存
     */
    public void initCache() {
        System.out.println("缓存:初始化本地缓存");
    }

    /**
     * 预热缓存
     */
    public void warmUp() {
        System.out.println("缓存:预热热点数据");
    }
}

(4)子系统------日志管理器

java 复制代码
/**
 * 子系统角色:日志管理器
 */
public class LogManager {

    /**
     * 初始化日志
     */
    public void initLogger() {
        System.out.println("日志:初始化日志框架");
    }

    /**
     * 设置日志级别
     *
     * @param level 日志级别
     */
    public void setLogLevel(String level) {
        System.out.println("日志:设置日志级别为 " + level);
    }
}

(5)外观角色------应用启动外观

java 复制代码
/**
 * 外观角色:应用启动外观
 * 封装应用启动的复杂流程,提供简化的接口
 */
public class AppStartupFacade {

    private final LogManager logManager;
    private final ConfigManager configManager;
    private final DatabaseConnector databaseConnector;
    private final CacheManager cacheManager;

    public AppStartupFacade() {
        this.logManager = new LogManager();
        this.configManager = new ConfigManager();
        this.databaseConnector = new DatabaseConnector();
        this.cacheManager = new CacheManager();
    }

    /**
     * 启动应用------按顺序初始化所有子系统
     */
    public void startup() {
        System.out.println("====== 应用启动开始 ======");

        // 1. 初始化日志(最先,后续流程需要记录日志)
        logManager.initLogger();
        logManager.setLogLevel("INFO");

        // 2. 加载并验证配置
        configManager.loadConfig();
        configManager.validateConfig();

        // 3. 初始化数据库连接
        databaseConnector.initConnectionPool();
        databaseConnector.testConnection();

        // 4. 初始化缓存
        cacheManager.initCache();
        cacheManager.warmUp();

        System.out.println("====== 应用启动完成 ======");
    }

    /**
     * 关闭应用------按逆序释放资源
     */
    public void shutdown() {
        System.out.println("====== 应用关闭开始 ======");

        cacheManager.initCache();
        databaseConnector.initConnectionPool();
        configManager.loadConfig();
        logManager.initLogger();

        System.out.println("====== 应用关闭完成 ======");
    }
}

(6)客户端调用

java 复制代码
public class AppStartupDemo {
    public static void main(String[] args) {
        AppStartupFacade app = new AppStartupFacade();

        // 一键启动------无需关心子系统初始化的顺序和细节
        app.startup();
        // ====== 应用启动开始 ======
        // 日志:初始化日志框架
        // 日志:设置日志级别为 INFO
        // 配置:加载应用配置文件
        // 配置:验证配置项完整性
        // 数据库:初始化连接池
        // 数据库:测试连接是否正常
        // 缓存:初始化本地缓存
        // 缓存:预热热点数据
        // ====== 应用启动完成 ======

        // 一键关闭
        app.shutdown();
    }
}

优势体现:启动流程的顺序至关重要(日志→配置→数据库→缓存),外观角色封装了这一顺序逻辑。如果未来需要调整启动顺序或新增子系统,只需修改外观类,客户端代码无需任何改动。


三、外观模式 vs 中介者模式

外观模式和中介者模式都属于结构型模式,而且都涉及"封装多个对象之间的交互",但它们的意图交互方式截然不同。

3.1 意图对比

对比维度 外观模式 中介者模式
核心意图 简化子系统的接口,提供统一的对外入口 将对象之间的交互集中到一个中介者对象中,避免对象之间相互引用
解决的问题 客户端与子系统之间的耦合 对象之间复杂的网状引用关系
交互方式 单向:客户端 → 外观 → 子系统(子系统不知道外观的存在) 双向:同事对象 ↔ 中介者 ↔ 同事对象
对象感知 子系统不知道外观的存在 同事对象知道中介者的存在
新增行为 外观可以提供新的接口组合 中介者可以定义新的交互逻辑
结构关系 外观是子系统的入口,一层封装 中介者是同事对象的枢纽,居中协调

3.2 结构对比

中介者模式
ColleagueA
Mediator
ColleagueB
外观模式
无感知
Client
Facade
SubsystemA
SubsystemB

外观模式:客户端通过外观角色访问子系统,外观角色单向地委派请求给子系统,子系统不知道外观角色的存在,交互是单向的。

中介者模式:同事对象通过中介者与其他同事对象交互,中介者与同事对象之间是双向关系,同事对象知道中介者的存在,交互是双向的。

3.3 代码对比

以"智能家居"为例,对比两种模式的应用方式:

外观模式的用法------客户端通过外观一键操作设备:

java 复制代码
// 外观角色:封装设备操作,提供简化接口
public class SmartHomeFacade {

    private final Light light = new Light();
    private final AirConditioner ac = new AirConditioner();

    /**
     * 回家模式
     */
    public void goHome() {
        light.on();       // 外观单向调用子系统
        ac.on();
        ac.setTemperature(26);
    }
}

// 客户端:只需调用外观
SmartHomeFacade facade = new SmartHomeFacade();
facade.goHome();

中介者模式的用法------设备之间通过中介者协调交互:

java 复制代码
// 中介者接口
public interface SmartHomeMediator {
    void notify(Device device, String event);
}

// 具体中介者:协调设备之间的联动
public class SmartHomeMediatorImpl implements SmartHomeMediator {

    private Light light;
    private AirConditioner ac;

    @Override
    public void notify(Device device, String event) {
        if ("温度过高".equals(event)) {
            // 空调报告温度过高 → 中介者联动开灯、降温
            ac.on();
            ac.setTemperature(24);
            light.on();
            light.setBrightness(50);
        } else if ("离开房间".equals(event)) {
            // 传感器报告离开 → 中介者联动关灯、关空调
            light.off();
            ac.off();
        }
    }
}

// 同事对象:知道中介者的存在
public class AirConditioner extends Device {

    public AirConditioner(SmartHomeMediator mediator) {
        super(mediator);
    }

    /**
     * 检测温度变化
     */
    public void checkTemperature(int currentTemp) {
        if (currentTemp > 30) {
            mediator.notify(this, "温度过高");
        }
    }
}

关键区别 :外观模式是单向调用 ------外观调用子系统,子系统不知道外观的存在;中介者模式是双向交互------同事对象与中介者互相感知,中介者根据一个同事对象的状态变化协调其他同事对象的响应。

3.4 选择指南

场景 选择
需要为复杂子系统提供简化的对外接口 外观模式
需要解耦客户端与子系统的直接依赖 外观模式
需要按固定流程编排多个子系统的操作 外观模式
对象之间存在复杂的网状引用关系 中介者模式
对象之间需要互相通信但不想直接引用 中介者模式
一对象的状态变化需要联动其他多个对象 中介者模式

四、总结

外观模式的核心思想是为子系统中的一组接口提供一个一致的界面,定义一个高层接口使子系统更加容易使用,从而降低客户端与子系统之间的耦合度。

优点:

  • 简化客户端调用:客户端只需要与外观角色交互,无需了解子系统的复杂性
  • 降低耦合度:客户端与子系统解耦,子系统的变化不会影响客户端
  • 符合迪米特法则:客户端只需要知道外观角色的存在,不需要知道子系统的内部结构
  • 分层设计:可以为子系统构建多层外观,每层提供不同粒度的接口
  • 不影响子系统:子系统本身不感知外观的存在,可以独立使用

缺点:

  • 可能成为上帝对象:如果外观类承担了过多职责,会变得臃肿,违背单一职责原则
  • 不能替代子系统:外观模式只是简化接口,不能提供子系统没有的功能
  • 可能引入不必要的简单性:对于需要灵活使用子系统高级功能的场景,外观可能反而成为一种限制

适用场景:

  • 为复杂的子系统提供简单的对外接口
  • 客户端与子系统之间存在强耦合,需要解耦
  • 需要构建多层结构的子系统,每层之间通过外观角色通信
  • 需要将遗留系统或第三方库的复杂接口封装为简单接口

外观 vs 中介者 :外观模式的意图是简化接口,客户端通过外观单向调用子系统,子系统不知道外观的存在;中介者模式的意图是解耦对象间的交互,同事对象与中介者双向通信,中介者居中协调。两者虽然都涉及封装多个对象的交互,但交互方向和意图截然不同。


参考博客:

外观模式 | 菜鸟教程:https://www.runoob.com/design-pattern/facade-pattern.html

相关推荐
咖啡八杯2 小时前
GoF设计模式——抽象工厂模式
java·后端·spring·设计模式·抽象工厂模式
是个西兰花2 小时前
单列模式和C++中的类型转换
c++·单例模式·设计模式·rtti
多加点辣也没关系2 小时前
设计模式-享元模式
数据库·设计模式·享元模式
熠熠仔4 小时前
《Agentic Design Patterns》概览
学习·设计模式
geovindu5 小时前
python: Mutex Pattern
开发语言·python·设计模式·互斥锁模式
Carl_奕然5 小时前
【智能体】Agent的四种设计模式之:Plan-and-Execute
人工智能·python·设计模式
mit6.8245 小时前
[Panyim] C++ 比 C 更好吗
设计模式
nnsix6 小时前
设计模式 - 单例模式 笔记
笔记·单例模式·设计模式
雪度娃娃6 小时前
结构型设计模式——外观模式
c++·设计模式·外观模式