系列文章目录
肝一肝设计模式【一】-- 单例模式 传送门
肝一肝设计模式【二】-- 工厂模式 传送门
肝一肝设计模式【三】-- 原型模式 传送门
肝一肝设计模式【四】-- 建造者模式 传送门
肝一肝设计模式【五】-- 适配器模式 传送门
肝一肝设计模式【六】-- 装饰器模式 传送门
肝一肝设计模式【七】-- 代理模式 传送门
文章目录
- 系列文章目录
- 前言
- 一、什么是外观模式
- 二、外观模式中的角色
- 三、举个栗子
- 四、在开源框架中的使用
- 写在最后
前言
本节我们继续分析设计模式中的结构型模式,前文中我们已经分析了适配器模式、装饰器模式、代理模式,本节我们来学习一下——外观模式。
一、什么是外观模式
外观模式(Facade Pattern),它为复杂子系统提供了一个简单的接口,使得客户端可以更容易地使用该子系统,同时避免了直接访问子系统可能带来的复杂性。
外观模式的核心思想是为子系统提供一个高层次的接口,以便客户端可以通过这个接口访问子系统的功能,而无需了解子系统的内部实现细节。外观模式封装了子系统的复杂性,提供了一个简单的界面,使得客户端可以更加方便地使用子系统的功能。
二、外观模式中的角色
通常情况下,外观模式的实现需要创建一个外观类,该类将客户端的请求委派给子系统中的相应对象进行处理。外观类对客户端隐藏了子系统的复杂性,同时还可以为子系统提供额外的功能,以满足客户端的需求。
外观模式主要由三个角色:
- 外观(Facade):外观是外观模式的核心,它封装了子系统的复杂性,并为客户端提供一个简单的接口。外观类通常包含一个或多个操作方法,这些方法将客户端的请求委派给子系统中的相应对象进行处理。外观类还可以为客户端提供一些额外的功能,以便满足客户端的需求。
- 子系统(Subsystem):子系统是外观模式中的核心组成部分,它是指实现系统功能的一组类。子系统包含一些相互关联的类,这些类共同实现了系统的一些功能。在外观模式中,客户端通常不会直接访问子系统中的类,而是通过外观类来访问子系统。
- 客户端(Client):客户端是使用外观模式的应用程序的组成部分。客户端通过外观类来访问子系统中的功能,从而实现系统的需求。客户端并不了解子系统的内部实现细节,它只需要调用外观类中的方法即可完成所需功能。
三、举个栗子
又到了举例子环节,比如单独一个人去医院看病,往往会因为挂号、化验、取报告、收费、取药等这些流程搞得焦头烂额,当你刚刚花了半个小时排完队交完费,转头看到取药窗口的长队时,当下的我崩溃如下图。。
实际生活中像我经历的这种情况不在少数,而这就催生出一种服务叫陪诊服务,用户只需要专注看病这件事就行了,其他都交给陪诊员,这种形式的服务就和程序中外观模式的概念就很类似了
上代码:
我们把取报告、交费、取药这几个步骤当做子系统
public class StepA {
public void getReport() {
System.out.println("帮我取化验报告");
}
}
public class StepB {
public void pay() {
System.out.println("帮我去交费");
}
}
public class StepC {
public void getMedicine() {
System.out.println("帮我把药取回来");
}
}
陪诊员相当于外观角色
public class AccompanyFacade {
private StepA stepA;
private StepB stepB;
private StepC stepC;
public AccompanyFacade() {
stepA = new StepA();
stepB = new StepB();
stepC = new StepC();
}
public void work() {
stepA.getReport();
stepB.pay();
stepC.getMedicine();
}
}
客户端调用
public class FacadeClient {
public static void main(String[] args) {
AccompanyFacade facade= new AccompanyFacade();
facade.work();
}
}
四、在开源框架中的使用
SLF4J(Simple Logging Facade for Java)是一个常用的日志框架,它使用了外观模式来封装不同的日志实现库,提供了统一的接口供开发人员使用。SLF4J允许开发人员在代码中使用一致的日志API,而无需关注底层的日志实现细节。
SLF4J的核心组件是Logger接口,它定义了常见的日志方法,如debug()、info()、error()等。开发人员可以通过获取Logger实例并调用这些方法来记录日志。
SLF4J的外观模式实现的关键在于它提供了适配器(Adapter)和桥接(Bridge)两个重要的概念:
-
适配器(Adapter):SLF4J提供了适配器模块,例如slf4j-jdk14、slf4j-log4j12等,用于适配不同的日志实现库,将它们转换为SLF4J接口的调用。这样,开发人员可以使用SLF4J的接口,而实际的日志实现则由相应的适配器来负责。
-
桥接(Bridge):SLF4J还提供了桥接模块,例如jul-to-slf4j、log4j-to-slf4j等,用于将其他日志库的日志输出重定向到SLF4J接口。这样,即使在使用其他日志库的应用中,开发人员仍然可以通过SLF4J接口来统一管理日志。
通过适配器和桥接的机制,SLF4J实现了外观模式,封装了不同的日志实现库,提供了统一的日志接口供开发人员使用。这样,开发人员可以轻松切换和管理日志库,而无需修改大量的代码。
SLF4J本身只提供了日志接口和相关的适配器和桥接模块,实际的日志输出仍需要依赖具体的日志实现库,例如Logback、Log4j等。开发人员需要在项目中同时引入SLF4J和所选的日志实现库。
以下是SLF4J的部分源代码,展示了它的外观模式实现:
Logger接口:
public interface Logger {
void debug(String message);
void info(String message);
void error(String message);
// ...
}
LoggerFactory类:
public final class LoggerFactory {
private LoggerFactory() {}
public static Logger getLogger(Class<?> clazz) {
// 获取适配器实例
ILoggerFactory loggerFactory = getILoggerFactory();
return loggerFactory.getLogger(clazz.getName());
}
private static ILoggerFactory getILoggerFactory() {
// 返回适配器实例
return StaticLoggerBinder.getSingleton().getLoggerFactory();
}
}
StaticLoggerBinder类:
public class StaticLoggerBinder {
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
private final ILoggerFactory loggerFactory = new MyLoggerFactory();
public static final StaticLoggerBinder getSingleton() {
return SINGLETON;
}
public ILoggerFactory getLoggerFactory() {
return loggerFactory;
}
}
适配器实现:
public class Log4jLoggerFactory implements ILoggerFactory {
public Logger getLogger(String name) {
return new Log4jLogger(org.apache.log4j.Logger.getLogger(name));
}
}
在上述代码中,Logger接口定义了常见的日志方法,LoggerFactory类是获取Logger实例的工厂类。LoggerFactory通过调用getILoggerFactory()方法获取适配器实例,然后返回相应的Logger实例。
StaticLoggerBinder类是一个单例类,提供了getLoggerFactory()方法来获取适配器实例。在示例中,适配器实现为Log4jLoggerFactory,它实现了ILoggerFactory接口,并通过Log4j库来实现日志功能。
这样,当开发人员通过LoggerFactory.getLogger()获取Logger实例时,SLF4J会根据适配器的实现来选择相应的日志实现库,然后返回封装好的Logger实例。开发人员可以直接使用Logger接口的方法来记录日志,而无需关心具体的日志实现细节。
写在最后
外观模式的优点:
-
简化接口:外观模式通过提供一个统一的接口,将底层子系统的复杂性封装起来。这样,使用者只需要与外观对象交互,无需了解底层子系统的具体实现细节,简化了接口和调用过程。
-
提供易用性:外观模式可以为使用者提供一个简单而易用的接口,降低了学习和使用系统的难度。使用者无需深入了解系统的内部结构和复杂性,可以更快地上手和开发。
-
解耦和降低依赖:外观模式将系统的子系统和使用者之间进行解耦,通过外观对象进行交互。这样,子系统的变化不会影响到使用者,使用者只需要关注外观对象的接口,减少了依赖关系。
-
提供灵活性和可维护性:通过外观模式,可以隔离系统的变化和演化。当底层子系统发生变化时,只需要调整外观对象的实现,而不需要修改使用者的代码。这提供了灵活性和可维护性,降低了系统的耦合度。
外观模式的缺点:
-
违背开闭原则:外观模式的一个潜在缺点是当系统的需求变化时,可能需要修改外观对象的接口或实现。这可能导致外观对象的修改,违背了开闭原则。但通常情况下,外观模式的修改范围相对较小,不会对系统的其他部分产生太大影响。
-
可能引入性能问题:在某些情况下,外观模式可能会引入性能问题。由于外观对象需要处理复杂的子系统调用,可能会导致一定的性能开销。但在大多数情况下,这种开销是可以接受的,并且可以通过优化和缓存等技术进行改进。