> 技术文档 > Java设计模式之《外观模式》

Java设计模式之《外观模式》

目录

1、外观模式

1.1、定义

1.2、核心思想:

1.3、角色

1.4、优缺点

2、实现

3、使用场景


前言

关于Java的设计模式分类如下:

关于亨元模式的结构如下:


1、外观模式

1.1、定义

        外观模式是一种结构型设计模式,它为一组复杂的子系统接口提供了一个更简洁、统一的接口。这个模式定义了一个更高层次的接口,使得子系统更容易使用。

1.2、核心思想:

         隐藏系统的复杂性,并向客户端提供一个可以访问系统的、简化的接口。它就像是整个子系统的一个“门面”或“接待员”,客户端不需要了解系统内部的细节,只需要和这个“门面”打交道即可。

类比:

        想象一下餐厅的点餐过程。作为一个顾客(客户端),你不需要直接与后厨的厨师、切菜工、洗碗工(各个子系统)进行复杂的交互。你只需要和服务员(外观)沟通,告诉他你想要什么菜。服务员接收你的简单订单,然后他负责去协调后厨各个部门完成一系列复杂的工作(准备食材、烹饪、装盘等),最后将美味的菜肴送到你面前。服务员就充当了这个“外观”的角色。

1.3、角色

外观模式通常包含以下角色:

1、外观:

知道哪些子系统类负责处理客户端的请求。将客户端的请求代理给相应的子系统对象。

2、附加外观:

(可选)当一个外观变得过于复杂时,可以创建多个附加外观,将职责划分得更清晰。客户端和子系统都可以使用附加外观。

3、子系统:

由多个类或组件组成,实现子系统的功能。

处理由外观对象指派的任务,但对客户端一无所知(即不持有外观的引用)。

4、客户端:

通过调用外观提供的方法来与子系统进行交互,而不是直接调用子系统对象。

1.4、优缺点

1、优点:

简化客户端代码: 

客户端不再需要直接与复杂的子系统交互,代码变得清晰简洁。

降低耦合度: 

        将客户端与子系统解耦,使子系统的变化更容易管理。只要外观接口不变,子系统内部的修改不会影响到客户端。

提供了一个清晰的入口点:

 特别是对于层次化的结构,外观定义了系统的入口点,简化了系统的使用和理解。

2、缺点:

不符合开闭原则:

       当子系统增加新功能或改变时,通常需要修改外观类。不过,可以通过引入抽象外观和具体子外观来缓解这个问题,但这会增加系统的复杂性。

可能成为“上帝对象”: 

如果设计不当,外观类可能会承担过多的职责,变成一个庞大而难以维护的类。


2、实现

        用一个家庭影院系统来举例。开启家庭影院看电影需要一系列复杂的操作:开灯、开投影仪、开音响、设置投影仪输入模式、放下屏幕等。

1、没有外观模式的情况:

代码示例如下:

// 子系统类:电灯class Light { public void on() { System.out.println(\"打开电灯\"); } public void off() { System.out.println(\"关闭电灯\"); } public void dim(int level) { System.out.println(\"调暗电灯到 \" + level + \"%\"); }}// 子系统类:投影仪class Projector { public void on() { System.out.println(\"打开投影仪\"); } public void off() { System.out.println(\"关闭投影仪\"); } public void setInput(String input) { System.out.println(\"设置投影仪输入为: \" + input); }}// 子系统类:音响class Amplifier { public void on() { System.out.println(\"打开音响\"); } public void off() { System.out.println(\"关闭音响\"); } public void setVolume(int level) { System.out.println(\"设置音量为: \" + level); }}// 子系统类:屏幕class Screen { public void up() { System.out.println(\"收起屏幕\"); } public void down() { System.out.println(\"放下屏幕\"); }}// 客户端代码 - 非常复杂!public class ClientWithoutFacade { public static void main(String[] args) { Light light = new Light(); Projector projector = new Projector(); Amplifier amp = new Amplifier(); Screen screen = new Screen(); // 想看电影,需要一步步操作 System.out.println(\"准备看电影...\"); light.dim(10); // 调暗灯光 screen.down(); // 放下屏幕 projector.on(); // 打开投影仪 projector.setInput(\"HDMI\"); amp.on();  // 打开音响 amp.setVolume(5); // ... 还有其他操作 System.out.println(\"开始播放电影...\"); // 看完电影还要一步步关闭 System.out.println(\"\\n电影结束,关闭设备...\"); // ... 反向操作所有步骤 amp.off(); projector.off(); screen.up(); light.on(); }}

2、使用外观模式后:

代码示例:

// 外观类:家庭影院外观class HomeTheaterFacade { private Light light; private Projector projector; private Amplifier amp; private Screen screen; public HomeTheaterFacade(Light light, Projector projector, Amplifier amp, Screen screen) { this.light = light; this.projector = projector; this.amp = amp; this.screen = screen; } // 提供一个高度简化的“一键观影”方法 public void watchMovie() { System.out.println(\"准备看电影...\"); light.dim(10); screen.down(); projector.on(); projector.setInput(\"HDMI\"); amp.on(); amp.setVolume(5); System.out.println(\"影院已就绪,开始享受电影吧!\"); } // 提供一个高度简化的“一键结束”方法 public void endMovie() { System.out.println(\"关闭家庭影院...\"); amp.off(); projector.off(); screen.up(); light.on(); System.out.println(\"影院已关闭。\"); }}// 客户端代码 - 变得极其简单!public class ClientWithFacade { public static void main(String[] args) { // 初始化子系统组件(通常由依赖注入框架完成) Light light = new Light(); Projector projector = new Projector(); Amplifier amp = new Amplifier(); Screen screen = new Screen(); // 创建外观,并组合所需的子系统 HomeTheaterFacade homeTheater = new HomeTheaterFacade(light, projector, amp, screen); // 使用外观提供的高级接口 homeTheater.watchMovie(); // 一键开启 System.out.println(\"\\n正在播放《教父》...\\n\"); homeTheater.endMovie(); // 一键关闭 }}

3、使用场景

1. Spring Framework 中的外观模式

代码示例如下:

// Spring的JdbcTemplate就是一个经典的外观@Repositorypublic class UserRepository { @Autowired private JdbcTemplate jdbcTemplate; // 外观 public void saveUser(User user) { // 极其简单的接口,隐藏了所有JDBC的复杂性 jdbcTemplate.update( \"INSERT INTO users (name, email) VALUES (?, ?)\", user.getName(), user.getEmail() ); } public List findAll() { // 简化的查询接口 return jdbcTemplate.query( \"SELECT * FROM users\", (rs, rowNum) -> new User( rs.getInt(\"id\"), rs.getString(\"name\"), rs.getString(\"email\") ) ); }}

2. SLF4J 日志框架

代码示例如下:

import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class MyService { // 获取外观接口 private static final Logger logger = LoggerFactory.getLogger(MyService.class); public void doSomething() { try { // 业务逻辑 logger.info(\"开始执行操作\"); // 简化的日志接口 // ... logger.debug(\"调试信息\"); } catch (Exception e) { logger.error(\"操作失败\", e); // 统一的错误日志接口 } }}

3. 微服务中的API网关

代码示例如下:

// 模拟API网关外观public class ApiGateway { private UserService userService; private OrderService orderService; private PaymentService paymentService; private AuthService authService; public ApiGateway() { this.userService = new UserService(); this.orderService = new OrderService(); this.paymentService = new PaymentService(); this.authService = new AuthService(); } // 提供统一的API入口 public Response handleRequest(Request request) { // 身份验证 if (!authService.validateToken(request.getToken())) { return Response.unauthorized(); } // 路由到相应的服务 switch (request.getPath()) { case \"/users\": return userService.handle(request); case \"/orders\": return orderService.handle(request); case \"/payments\": return paymentService.handle(request); default: return Response.notFound(); } }}

最佳实践和注意事项

  1. 不要过度使用:如果子系统本身很简单,就不需要外观模式,否则会增加不必要的抽象层。

  2. 保持外观简洁:外观应该提供真正简化的接口,而不是成为另一个复杂的层。

  3. 考虑使用接口:可以为外观定义接口,这样更容易替换不同的实现或进行测试。


总结

        外观模式其核心价值在于简化接口,降低复杂度。当你有一个复杂的子系统,并且希望为其提供一个更简单、更清晰的接口时,或者当你想将子系统与客户端解耦时,外观模式是一个绝佳的选择。

        它在 Java 的日志系统、框架和工具库中得到了广泛的应用。


参考文章:

1、设计模式结构型——外观模式-CSDN博客https://blog.csdn.net/tszc95/article/details/131817470?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522bba385534f448f909cb3ef323553d396%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=bba385534f448f909cb3ef323553d396&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~top_click~default-2-131817470-null-null.nonecase&utm_term=%E5%A4%96%E8%A7%82%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4450