外观模式(Facade Pattern),即提供一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。

当一个系统的功能越来越强大时,子系统也会越来越多。客户端对系统的访问也变得越来越复杂,这时如果系统内部发生了改变,客户端也要跟着变化,违背了开闭原则,也违背了迪米特法则,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。

外观模式的组成为:

组成 作用
Facade(外观角色) 为多个子系统对外提供一个共同的接口
Sub System(子系统) 实现系统的部分功能,客户可以通过外观角色访问它
Client(客户端) 通过一个外观角色访问各个子系统的功能

设计模式—外观模式 - 图1

图片来自外观模式(Facade Pattern) - 最易懂的设计模式解析

外观模式的优点

  1. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户端;
  2. 对客户端很好的屏蔽了子系统组件,减少了客户端处理的对象数量,使子系统的使用变得更加容易;
  3. 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他子系统,也不会影响外观;

外观模式的缺点

  1. 不能很好的限制客户端使用子系统,很容易带来未知风险;
  2. 增加新的子系统可能会需要修改外观类或客户端的代码,违背开闭原则,可以通过引入抽象外观类来解决这个问题

外观模式的应用场景

  1. 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系
  2. 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问
  3. 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性

外观模式使用步骤

  1. 创建子系统
  2. 创建外观角色,持有子系统并作为客户端操作子系统的中间桥梁
  3. 创建客户端,使用外观类操作子系统

外观模式最佳实践

假设一个软件系统中有多个模块:社区,休闲游戏,新闻资讯等。小明作为该软件的忠实粉丝,每天早上会浏览新闻资讯;中午会逛社区;下午会玩会游戏。正常来说,小明(客户端)都需要与任何一个模块直接交互,这样的话使得客户端与子模块产生了耦合,而且客户端调用这么多子系统会增加系统的使用复杂度,这时使用外观类即可解决这些问题。

  1. public class FacadePattern {
  2. public static class News {
  3. public void view() {
  4. System.out.println("早上浏览新闻,关心天下事...");
  5. }
  6. }
  7. public static class Community {
  8. public void findTopic() {
  9. System.out.println("中午逛社区,找共同话题...");
  10. }
  11. }
  12. public static class Game {
  13. public void play() {
  14. System.out.println("晚上打游戏,放松心情...");
  15. }
  16. }
  17. public static class Facade {
  18. private Game game;
  19. private Community community;
  20. private News news;
  21. public Facade(News news, Community community, Game game) {
  22. this.game = game;
  23. this.community = community;
  24. this.news = news;
  25. }
  26. public void visitTrack() {
  27. news.view();
  28. community.findTopic();
  29. game.play();
  30. }
  31. }
  32. public static void main(String[] args) {
  33. News news = new News();
  34. Community community = new Community();
  35. Game game = new Game();
  36. Facade facade = new Facade(news, community, game);
  37. facade.visitTrack();
  38. }
  39. }

程序运行结果:

早上浏览新闻,关心天下事… 中午逛社区,找共同话题… 晚上打游戏,放松心情…

如果不使用外观模式,则是如下调用方式:

  1. public static void main(String[] args) {
  2. News news = new News();
  3. Community community = new Community();
  4. Game game = new Game();
  5. news.view();
  6. community.findTopic();
  7. game.play();
  8. }

这样的话明显子系统与客户端的耦合更高了,而且每增加一个子系统,有可能还要修改客户端的源代码,违反了开闭原则。使用外观系统的话,可以对外观类进行抽象,这样的话就比较符合开闭原则了。

例如,假设系统新增了一个商城系统,小明的每日轨迹变成了每天早上会浏览新闻资讯;中午会逛商城;下午会玩会游戏。那么就可以通过扩展抽象外观类来实现:

  1. public class FacadePattern {
  2. public static class News {
  3. public void view() {
  4. System.out.println("早上浏览新闻,关心天下事...");
  5. }
  6. }
  7. public static class Community {
  8. public void findTopic() {
  9. System.out.println("中午逛社区,找共同话题...");
  10. }
  11. }
  12. public static class Mall {
  13. public void buy() {
  14. System.out.println("中午逛商城,买东西...");
  15. }
  16. }
  17. public static class Game {
  18. public void play() {
  19. System.out.println("晚上打游戏,放松心情...");
  20. }
  21. }
  22. /**
  23. * 抽象外观类类
  24. */
  25. public static abstract class AbstractFacade {
  26. public abstract void visitTrack();
  27. }
  28. /**
  29. * 正常模式:
  30. * 早上会浏览新闻资讯;
  31. * 中午会逛社区;
  32. * 下午会玩会游戏
  33. */
  34. public static class Facade extends AbstractFacade {
  35. private Game game;
  36. private Community community;
  37. private News news;
  38. public Facade(News news, Community community, Game game) {
  39. this.game = game;
  40. this.community = community;
  41. this.news = news;
  42. }
  43. @Override
  44. public void visitTrack() {
  45. news.view();
  46. community.findTopic();
  47. game.play();
  48. }
  49. }
  50. /**
  51. * 购物模式:
  52. * 早上会浏览新闻资讯;
  53. * 中午会逛商城;
  54. * 下午会玩会游戏
  55. */
  56. public static class BuyFacade extends AbstractFacade {
  57. private Game game;
  58. private Mall mall;
  59. private News news;
  60. public BuyFacade(News news, Mall mall, Game game) {
  61. this.game = game;
  62. this.mall = mall;
  63. this.news = news;
  64. }
  65. @Override
  66. public void visitTrack() {
  67. news.view();
  68. mall.buy();
  69. game.play();
  70. }
  71. }
  72. public static void main(String[] args) {
  73. News news = new News();
  74. Community community = new Community();
  75. Mall mall = new Mall();
  76. Game game = new Game();
  77. // 正常模式
  78. AbstractFacade facade = new Facade(news, community, game);
  79. facade.visitTrack();
  80. // 购物模式
  81. facade = new BuyFacade(news, mall, game);
  82. facade.visitTrack();
  83. }
  84. }

程序运行结果如下:

早上浏览新闻,关心天下事… 中午逛社区,找共同话题…

晚上打游戏,放松心情…

早上浏览新闻,关心天下事…

中午逛商城,买东西…

晚上打游戏,放松心情…