外观模式详解

外观模式

外观模式基本介绍

外观模式(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. 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好 。要以让系统有层次,利于维

    护为目的。

相关推荐
喜欢便码23 分钟前
JS小练习0.1——弹出姓名
java·前端·javascript
王磊鑫2 小时前
重返JAVA之路-初识JAVA
java·开发语言
半兽先生2 小时前
WebRtc 视频流卡顿黑屏解决方案
java·前端·webrtc
南星沐3 小时前
Spring Boot 常用依赖介绍
java·前端·spring boot
代码不停4 小时前
Java中的异常
java·开发语言
何似在人间5754 小时前
多级缓存模型设计
java·jvm·redis·缓存
未定义.2214 小时前
UML-银行取款序列图
设计模式·流程图·软件工程·需求分析·uml
多云的夏天4 小时前
ubuntu24.04-MyEclipse的项目导入到 IDEA中
java·intellij-idea·myeclipse
Fanxt_Ja4 小时前
【数据结构】红黑树超详解 ---一篇通关红黑树原理(含源码解析+动态构建红黑树)
java·数据结构·算法·红黑树
Aphelios3804 小时前
TaskFlow开发日记 #1 - 原生JS实现智能Todo组件
java·开发语言·前端·javascript·ecmascript·todo