设计模式第五章(门面模式)
门面模式(Facade Pattern)是一种结构型设计模式,它为子系统中的一组接口提供了一个统一的高层接口,使得子系统更容易使用。
门面模式主要包含以下几个角色:
- Facade(门面角色):是门面模式的核心,为子系统提供统一接口,屏蔽子系统的复杂性。它知道哪些子系统负责处理请求,并将请求代理给适当的子系统对象。
- SubSystem Classes(子系统角色):实现子系统的功能,这些子系统对客户端来说是黑盒,客户端通过门面角色与它们交互。
- Client(客户角色):与门面角色交互,无需了解子系统的具体实现。
例如,在一个电商系统中,订单处理涉及库存系统、支付系统、物流系统等多个子系统。可以设计一个订单处理门面,将这些子系统的功能整合在一起,客户端只需与订单处理门面交互,无需了解库存、支付、物流等子系统的具体实现细节,就能完成订单处理的操作。
门面模式的优点包括简化客户端调用、减少系统依赖、提高代码的可维护性和可读性等。缺点是不符合开闭原则,当增加子系统和扩展子系统行为时,可能需要修改门面类,并且在某些情况下,可能会导致门面类变得庞大,承担过多的责任。
引言
我们模拟一个mysql 和tomcat 的启动过程,mysql 的启动过程是不是分为好几个步骤,tomcat 启动是不是也分为多个步骤,接下来我们用代码表示一下。
mysql 启动过程
java
public class Mysql {
public void initData() {
System.out.println("初始化mysql......");
}
public void checkLog() {
System.out.println("校验日志,恢复未提交的数据......");
}
public void unlock() {
System.out.println("释放锁");
}
public void listerPort() {
System.out.println("监听端口 3306 ");
}
}
tomcat 启动过程
java
public class MyTomcat {
public void initEngine() {
System.out.println("初始化tomcat引擎....");
}
public void initWeb() {
System.out.println("加载web应用。。。");
}
}
客户端调用
java
public class Main {
public static void main(String[] args) {
Mysql mysql = new Mysql();
mysql.initData();
mysql.checkLog();
mysql.unlock();
mysql.listerPort();
System.out.println("-------------->");
MyTomcat tomcat = new MyTomcat();
tomcat.initEngine();
tomcat.initWeb();
}
}

问题点
我们看到,作为第一个客户端,我们调用需要经过多个步骤,有没有一种简单的方法,我们只需要调用,某一个方法就能实现这种场景呢,那么接下来就是一个门面的封装了。
门店模式封装启动过程
- 抽象启动类
- 实现类
抽象类
我们定义了一个抽象启动的门店接口,里面只有一个方法,start()
java
public interface ServiceFace {
void start();
}
mysql 实现
java
public class MySql1 implements ServiceFace {
@Override
public void start() {
Mysql mysql = new Mysql();
mysql.initData();
mysql.checkLog();
mysql.unlock();
mysql.listerPort();
}
}
tomcat实现
java
public class MyTomcat1 implements ServiceFace {
@Override
public void start() {
MyTomcat tomcat = new com.fashion.ori.MyTomcat();
tomcat.initEngine();
tomcat.initWeb();
}
}
客户端
java
public class Main {
public static void main(String[] args) {
ServiceFace tomcat = new MyTomcat1();
tomcat.start();
ServiceFace mysql = new MySql1();
mysql.start();
}
}

我们看到,调用的客户端只需要知道门面接口定义的抽象方法即可,这样调用的过程封装到实现里面,使客户端使用更简单。
自定义门面插件类
需求背景,我需要统计一个接口每次被调用都进行一个计次数的动作,当然我们可以直接写到方法中,但是如果这是一个插件呢,我们需要如何实现。
- 插件api 模块
- 插件实现类
- 调用方
插件api
java
public interface MyPlugin {
void beforeGetTime();
}
xml
<modelVersion>4.0.0</modelVersion>
<groupId>com.fashion</groupId>
<artifactId>my_plugin_api</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
插件实现类
java
public class CountPlugin implements MyPlugin {
AtomicInteger count = new AtomicInteger(0);
@Override
public void beforeGetTime() {
System.out.println("访问次数:"+count.incrementAndGet());
}
}
xml
<artifactId>count_plugin</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.fashion</groupId>
<artifactId>my_plugin_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
客户端
java
@RestController
public class TimeController {
private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private MyPlugin myPlugin;
@GetMapping("/time")
public String getTime() {
if (myPlugin != null) {
myPlugin.beforeGetTime();
}
return LocalDateTime.now().format(dateTimeFormatter);
}
/**
* 加载插件 必须有一个文件叫 liuqiang.plug
*/
@GetMapping("/load/plugin/{path}")
public String loadPlugin(@PathVariable("path") String path) {
File file = new File("face-pattern\\face-boot\\"+path);
try(
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{file.toPath().toUri().toURL()});
InputStream lqPlugin = urlClassLoader.getResourceAsStream("chajian.plugin");
) {
//插件的实现类
String myPluginClassName = new String(lqPlugin.readAllBytes());
Class<?> aClass = urlClassLoader.loadClass(myPluginClassName.trim());
// 生成构建方法
Constructor<?> constructor = aClass.getConstructor();
myPlugin = (MyPlugin)constructor.newInstance();
return "插件加载成功----"+aClass.getName();
} catch (Exception e) {
return "插件记载失败"+e.getMessage();
}
}
}
pom文件
xml
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fashion</groupId>
<artifactId>my_plugin_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.fashion.face.FaceBootApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
启动流程
我们先加载插件,当插件加载后,那么我们访问 time 接口就会调用插件中的实现方法


调用接口

