设计模式第五章(门面模式)
设计模式第五章(门面模式)
门面模式(Facade Pattern)是一种结构型设计模式,它为子系统中的一组接口提供了一个统一的高层接口,使得子系统更容易使用。
门面模式主要包含以下几个角色:
- Facade(门面角色):是门面模式的核心,为子系统提供统一接口,屏蔽子系统的复杂性。它知道哪些子系统负责处理请求,并将请求代理给适当的子系统对象。
- SubSystem Classes(子系统角色):实现子系统的功能,这些子系统对客户端来说是黑盒,客户端通过门面角色与它们交互。
- Client(客户角色):与门面角色交互,无需了解子系统的具体实现。
例如,在一个电商系统中,订单处理涉及库存系统、支付系统、物流系统等多个子系统。可以设计一个订单处理门面,将这些子系统的功能整合在一起,客户端只需与订单处理门面交互,无需了解库存、支付、物流等子系统的具体实现细节,就能完成订单处理的操作。
门面模式的优点包括简化客户端调用、减少系统依赖、提高代码的可维护性和可读性等。缺点是不符合开闭原则,当增加子系统和扩展子系统行为时,可能需要修改门面类,并且在某些情况下,可能会导致门面类变得庞大,承担过多的责任。
引言
我们模拟一个mysql 和tomcat 的启动过程,mysql 的启动过程是不是分为好几个步骤,tomcat 启动是不是也分为多个步骤,接下来我们用代码表示一下。
mysql 启动过程
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 启动过程
public class MyTomcat {public void initEngine() {System.out.println("初始化tomcat引擎....");}public void initWeb() {System.out.println("加载web应用。。。");}
}
客户端调用
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()
public interface ServiceFace {void start();
}
mysql 实现
public class MySql1 implements ServiceFace {@Overridepublic void start() {Mysql mysql = new Mysql();mysql.initData();mysql.checkLog();mysql.unlock();mysql.listerPort();}
}
tomcat实现
public class MyTomcat1 implements ServiceFace {@Overridepublic void start() {MyTomcat tomcat = new com.fashion.ori.MyTomcat();tomcat.initEngine();tomcat.initWeb();}
}
客户端
public class Main {public static void main(String[] args) {ServiceFace tomcat = new MyTomcat1();tomcat.start();ServiceFace mysql = new MySql1();mysql.start();}
}
我们看到,调用的客户端只需要知道门面接口定义的抽象方法即可,这样调用的过程封装到实现里面,使客户端使用更简单。
自定义门面插件类
需求背景,我需要统计一个接口每次被调用都进行一个计次数的动作,当然我们可以直接写到方法中,但是如果这是一个插件呢,我们需要如何实现。
- 插件api 模块
- 插件实现类
- 调用方
插件api
public interface MyPlugin {void beforeGetTime();
}
<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>
插件实现类
public class CountPlugin implements MyPlugin {AtomicInteger count = new AtomicInteger(0);@Overridepublic void beforeGetTime() {System.out.println("访问次数:"+count.incrementAndGet());}
}
<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>
客户端
@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文件
<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 接口就会调用插件中的实现方法