外观模式详解

外观模式

外观模式基本介绍

外观模式(Facade),也叫 "过程模式":外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了

一个高层接口,这个接口使得这一子系统更加容易使用。

模式核心

外观模式通过一个统一的接口(Facade)封装复杂子系统的调用逻辑,为客户端提供简单易用的操作接口。其核心是降低系统复杂度,避免客户端直接与多个子系统交互。

角色划分

  1. 子系统类
    • 指模块或者子系统,处理 Facade 对象指派的任务,他是功能的实际提供者
  2. 外观类
    • 为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
  3. 调用者(Client)
    • 外观接口的调用者

外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能达到目的。

比如:在 PC 上安装软件的时候经常有一键安装选项(省去选择安装目录、安装的组件等等),还有就是手机的重启功能(把关机和启动合为一个操作)。

外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用

代码示例

子系统类

java 复制代码
public class DVDPlayer {

    // 使用单例模式, 使用饿汉式(静态变量)
    @Getter
    private static DVDPlayer instance = new DVDPlayer();

    public void on() {
        System.out.println("DVD Player turned on");
    }

    public void off() {
        System.out.println("DVD Player turned off");
    }

    public void play() {
        System.out.println("DVD is playing");
    }

    public void pause() {
        System.out.println("DVD playback paused");
    }
}

public class Popcorn {
	@Getter
	private static Popcorn instance = new Popcorn();

	public void on() {
		System.out.println("Popcorn machine turned on");
	}

	public void off() {
		System.out.println("Popcorn machine turned off");
	}

	public void pop() {
		System.out.println("Popcorn is popping");
	}
}

public class Screen {

    @Getter
    private static Screen instance = new Screen();

    public void up() {
        System.out.println("Screen up ");
    }

    public void down() {
        System.out.println("Screen down ");
    }
}

// ...

外观类(Facade)

java 复制代码
public class HomeTheaterFacade {

	// 定义各个子系统对象
	private final TheaterLight theaterLight;
	private final Popcorn popcorn;
	private final Stereo stereo;
	private final Projector projector;
	private final Screen screen;
	private final DVDPlayer dvdPlayer;


	// 构造器:初始化所有子系统单例
	public HomeTheaterFacade() {
		super();
		this.theaterLight = TheaterLight.getInstance();
		this.popcorn = Popcorn.getInstance();
		this.stereo = Stereo.getInstance();
		this.projector = Projector.getInstance();
		this.screen = Screen.getInstance();
		this.dvdPlayer = DVDPlayer.getInstance();
	}

	// 操作分成 4 步

	/**
	 * 准备观影:初始化所有设备
	 */
	public void ready() {
		// 初始化爆米花机
		popcorn.on();
		popcorn.pop();

		// 降下屏幕
		screen.down();

		// 启动投影仪
		projector.on();
		projector.focus();

		// 开启音响
		stereo.on();
		stereo.setVolume(5);

		// 启动DVD播放器
		dvdPlayer.on();

		// 调暗灯光
		theaterLight.dim();
	}

	/**
	 * 开始播放电影
	 */
	public void play() {
		dvdPlayer.play();
	}

	/**
	 * 暂停播放
	 */
	public void pause() {
		dvdPlayer.pause();
	}

	/**
	 * 结束观影:关闭所有设备并恢复环境
	 */
	public void end() {
		popcorn.off();
		theaterLight.bright();
		screen.up();
		projector.off();
		stereo.off();
		dvdPlayer.off();
	}
}

调用者(Client)

java 复制代码
public class Client {

	public static void main(String[] args) {
		// 创建外观对象,简化客户端操作
		HomeTheaterFacade facade = new HomeTheaterFacade();

		System.out.println("准备观影...");
		facade.ready();

		System.out.println("\n开始播放电影...");
		facade.play();

		System.out.println("\n暂停观影...");
		facade.pause();

		System.out.println("\n结束观影...");
		facade.end();
	}

}

外观模式在 MyBatis 框架应用的源码分析

MyBatis 中的 Configuration 去创建 MetaObject 对象使用到外观模式。

在 MyBatis 中,

  • MetaObject 是一个用于 封装对象并提供便捷操作(如反射访问字段、方法调用) 的类,
  • Configuration 负责管理 MyBatis 的整体配置信息,其中就包括 MetaObject 的创建。

代码分析

1、Configuration 如何创建 MetaObject
  • 在 MyBatis 源码 org.apache.ibatis.session.Configuration 类中,可以看到 MetaObject 是通过 MetaObject.forObject() 方法创建的:
java 复制代码
public class Configuration {
    protected final ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    protected final ObjectFactory objectFactory = new DefaultObjectFactory();
    protected final ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

    // 创建 MetaObject 的方法
    public MetaObject newMetaObject(Object object) {
        return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
    }
}

调用 newMetaObject(Object object) 这个方法时:

  • 隐藏了 MetaObject 创建的细节 ,只暴露了 newMetaObject() 这个简单接口。
  • MetaObject.forObject() 方法内部会自动调用 ObjectWrapperFactoryObjectFactoryReflectorFactory 处理对象。
  • Configuration 作为外观(Facade),统一封装 MetaObject 的创建过程。
2、MetaObject.forObject() 方法内部逻辑
java 复制代码
public class MetaObject {
    private final Object originalObject;
    private final ObjectWrapper objectWrapper;
    private final ObjectFactory objectFactory;
    private final ObjectWrapperFactory objectWrapperFactory;
    private final ReflectorFactory reflectorFactory;

    private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
        this.originalObject = object;
        this.objectFactory = objectFactory;
        this.objectWrapperFactory = objectWrapperFactory;
        this.reflectorFactory = reflectorFactory;
        if (object instanceof ObjectWrapper) {
            this.objectWrapper = (ObjectWrapper)object;
        } else if (objectWrapperFactory.hasWrapperFor(object)) {
            this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
        } else if (object instanceof Map) {
            this.objectWrapper = new MapWrapper(this, (Map)object);
        } else if (object instanceof Collection) {
            this.objectWrapper = new CollectionWrapper(this, (Collection)object);
        } else {
            this.objectWrapper = new BeanWrapper(this, object);
        }

    }

    public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
        return object == null ? SystemMetaObject.NULL_META_OBJECT : new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
    }

    // ...
}

核心功能:

  • 屏蔽了 MetaObject 的复杂构造逻辑 ,无论 object 是普通 JavaBean、Map 还是 Collection,都会选择合适的 Wrapper 进行封装。
  • 通过 ObjectWrapperFactory 判断是否有自定义封装 ,如果有,则交给 getWrapperFor() 处理。
  • 代码中 new BeanWrapper(...)new MapWrapper(...)new CollectionWrapper(...) 隐藏了具体的封装策略 ,调用者不需要关心对象的具体类型,直接获取 MetaObject 即可。
3、体现了外观模式的地方

外观模式 的核心思想 ------ 对外提供简单接口,隐藏复杂细节

Configuration 中:

  1. 对外提供 newMetaObject() 方法 ,让 Configuration 作为 Facade(外观),对 MetaObject 的创建过程进行封装。
  2. 屏蔽 MetaObject 内部的 ObjectWrapper 处理逻辑 (Bean、Map、Collection),调用者只需传入一个对象,即可得到 MetaObject,无需关心内部逻辑。
  3. MetaObject.forObject() 进一步封装细节 ,内部选择合适的 Wrapper 处理对象,使得 Configuration 只需要调用 newMetaObject(),就能获得封装好的 MetaObject

外观模式的注意事项和细节

  1. 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性

  2. 外观模式对客户端与子系统的耦合关系 - 解耦,让子系统内部的模块更易维护和扩展

  3. 通过合理的使用外观模式,可以帮我们更好的划分访问的层次

  4. 系统需要进行分层设计时,可以考虑使用 Facade 模式

  5. 在维护一个遗留的大型系统时,可能这个系统己经变得非常难以维护和扩展,此时可以考虑为新系统开发一个

    Facade 类,来提供遗留系统的比较清晰简单的接口,让新系统与 Facade 类交互,提高复用性

  6. 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好 。要以让系统有层次,利于维

    护为目的。

相关推荐
电商数据girl1 小时前
【Python爬虫电商数据采集+数据分析】采集电商平台数据信息,并做可视化演示
java·开发语言·数据库·爬虫·python·数据分析
夏季疯1 小时前
学习笔记:黑马程序员JavaWeb开发教程(2025.3.30)
java·笔记·学习
LUCIAZZZ1 小时前
简单介绍分布式定时任务XXL-JOB
java·spring boot·分布式·spring·操作系统·定时任务
bing_1581 小时前
Spring MVC Controller 方法的返回类型有哪些?
java·spring·mvc
奔驰的小野码2 小时前
SpringAI实现AI应用-内置顾问
java·人工智能·后端·spring
普通人zzz~2 小时前
SpringBoot记录用户操作日志
java·spring boot·后端
大三开学菜鸟2 小时前
记录一下spring-cloud-starter-alibaba-nacos-config 2023.0.3.2与springboot版本及配置问题
java·spring boot·后端·spring·intellij-idea
zfj3212 小时前
Lucene多种数据类型使用说明
java·mybatis·lucene
qqxhb3 小时前
零基础学Java——第九章:数据库编程(三)
java·数据库·spring·mybatis
桃林春风一杯酒3 小时前
Listremove数据时报错:Caused by: java.lang.UnsupportedOperationException
java·开发语言