目录
前言
关于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 {
@Autowired
private 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 的日志系统、框架和工具库中得到了广泛的应用。
参考文章: