外观模式是什么?

外观模式(Facade Pattern)是一种结构型的设计模式,它为子系统的一组接口提供一个统一的高层次接口,让子系统更容易使用。外观模式的本质就是封装,它屏蔽了一组复杂接口的调用,而只暴露一个简单的接口,简化了使用者的操作。生活中,运用外观模式的例子比比皆是,比如使用手机拍照,我们只要轻轻地点下按键,就自动地生成一张照片。而手机的感光器件是如何运作的、光线是怎么转换成数字信号的、图像数据是如何保存的等等这些细节,我们通通都不需要了解。

UML 类图

用 UML 类图来描述外观模式的结构,在模式中各个角色之间的关系:
image.png
根据上图,总结了模式中各个角色的职责以及它们之间的关系:

  • 外观提供了一个便捷的方式完成一组复杂的子系统操作,它知道如何串联子系统的各种部件来完成外部的请求。
  • 附加的外观可以避免不相关的功能污染单一的外观,使其变成又一个复杂的结构。
  • 复杂的子系统由多个不同的对象构成,要运用这些对象完成一件有意义的工作,你需要深入了解它们的细节,并使用正确的方式来调度它们。
  • 客户端通过访问外观代替对子系统的直接调用。

案例

让我们通过一个案例来帮助我们进一步理解外观模式。一般的软件或网站都有登录的功能,用户在登录的时候,通常只需要输入用户名和密码即可。但是软件系统要做的事可就多了,比如:校验用户名密码是否正确、校验用户状态是否正常、设置会话、记录日志等。这些操作一般是分散在软件系统的各个模块之中,登录界面在接收到用户的请求时(用户输入用户名、密码后点击了登录按钮),它需要一一调用相关的模块,才能完成登录操作,例如:

  1. userModule.checkUserIsExistent(username);
  2. userModule.checkPasswordIsMatched(username, password);
  3. userModule.checkUserIsAvailable(user);
  4. sessionModule.generateSessionId();
  5. sessionModule.saveSession(sessionId, user);
  6. loggerModule.log(loginEvent);

但是,这样登录界面就要与系统的多个模块紧密耦合在一起,而它其实并不关心这些操作具体是如何实现的,它其实就只关注一件事:登录的结果。我们可以为登录操作创建一个外观,仅暴露出一个简单的接口,由它来完成这个复杂的登录流程,并返回登录界面想要的结果。

  1. public class LoginFacade {
  2. private UserModule userModule = new UserModuleImpl();
  3. private LoggerModule loggerModule = new LoggerModuleImpl();
  4. private SessionModule sessionModule = new SessionModuleImpl();
  5. public LoginResult login(String username, String password) {
  6. if (!userModule.isUserExistent(username)) {
  7. return LoginResult.fail("Counld not find the user[" + username + "].");
  8. }
  9. Optional<User> userOpt = userModule.isPasswordMatched(username, password);
  10. if (!userOpt.isPresent()) {
  11. return LoginResult.fail("The password is not matched.");
  12. }
  13. User user = userOpt.get();
  14. if (!user.isNormal()) {
  15. return LoginResult.fail("The user is unavailable.");
  16. }
  17. String sessionId = sessionModule.generateSessionId();
  18. sessionModule.saveSession(sessionId, user);
  19. loggerModule.log(new LoginEvent(username));
  20. return LoginResult.success("Login successfully.");
  21. }
  22. }

现在,登录界面在接收到用户输入的用户名、密码之后,只需要调用登录外观的接口,就可以完成登录的一系列操作。

  1. LoginFacade loginFacade = new LoginFacade();
  2. LoginResult loginResult = loginFacade.login("Huey", "123456");;

案例源码

可在 GitHub 上查看上述案例的完整代码。

参考资料

本文参考的资料如下: