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就是一个经典的外观
@Repository
public class UserRepository {@Autowiredprivate JdbcTemplate jdbcTemplate; // 外观public void saveUser(User user) {// 极其简单的接口,隐藏了所有JDBC的复杂性jdbcTemplate.update("INSERT INTO users (name, email) VALUES (?, ?)",user.getName(), user.getEmail());}public List<User> 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();}}
}
最佳实践和注意事项
-
不要过度使用:如果子系统本身很简单,就不需要外观模式,否则会增加不必要的抽象层。
-
保持外观简洁:外观应该提供真正简化的接口,而不是成为另一个复杂的层。
-
考虑使用接口:可以为外观定义接口,这样更容易替换不同的实现或进行测试。
总结
外观模式其核心价值在于简化接口,降低复杂度。当你有一个复杂的子系统,并且希望为其提供一个更简单、更清晰的接口时,或者当你想将子系统与客户端解耦时,外观模式是一个绝佳的选择。
它在 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