笔记来源:尚硅谷Java设计模式(图解+框架源码剖析)

适配器模式

泰国旅游使用插座问题

现实生活中的适配器例子

泰国插座用的是两孔的(欧标),可以买个多功能转换插头(适配器),这样就可以使用了

image.png

基本介绍

  • 1)适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
  • 2)适配器模式属于结构型模式
  • 3)主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

工作原理

  • 1)适配器模式:将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容
  • 2)从用户的角度看不到被适配者,是解耦的
  • 3)用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
  • 4)用户收到反馈结果,感觉只是和目标接口交互,如图

image.png

1、类适配器模式

案例

基本介绍:Adapter 类,通过继承 src 类,实现 dst 类接口,完成 src -> dst 的适配

以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src(即被适配者),我们的 dst(即目标)是 5V 直流电

image.png

UML 类图

image.png

核心代码

  1. // 被适配的类
  2. public class Voltage220V {
  3. public Integer output220V() {
  4. int src = 220;
  5. System.out.println("电压=" + src + "伏");
  6. return src;
  7. }
  8. }
  9. // 适配接口
  10. public interface IVoltage5V {
  11. Integer output5V();
  12. }
  13. // 适配器
  14. public class VoltageAdapter extends Voltage220V implements IVoltage5V {
  15. @Override
  16. public Integer output5V() {
  17. int src = output220V();
  18. int dst = src / 44;
  19. System.out.println("电压=" + dst + "伏");
  20. return dst;
  21. }
  22. }
  23. // 使用适配器方法
  24. public class Phone {
  25. public void charing(IVoltage5V iVoltage5V) {
  26. if (iVoltage5V.output5V() == 5) {
  27. System.out.println("电压=5伏,正在充电~");
  28. } else {
  29. System.out.println("电压!=5伏,无法充电~");
  30. }
  31. }
  32. }
  33. // 客户端
  34. public class Client {
  35. public static void main(String[] args) {
  36. Phone phone = new Phone();
  37. phone.charing(new VoltageAdapter());
  38. }
  39. }

注意事项和细节

  • 1)Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点,因为这要求 dst 必须是接口,有一定局限性
  • 2)src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本
  • 3)由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了

2、对象适配器模式

  • 1)基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。即:持有 src 类,实现 dst 类接口,完成 src->dst 的适配
  • 2)根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系
  • 3)对象适配器模式是适配器模式常用的一种

以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src(即被适配者),我们的 dst(即目标)是 5V 直流电,使用对象适配器模式完成

UML 类图

image.png

核心代码

只需修改 Adapter 类即可

  1. public class VoltageAdapter implements IVoltage5V {
  2. private Voltage220V voltage220V;
  3. public VoltageAdapter(Voltage220V voltage220V) {
  4. this.voltage220V = voltage220V;
  5. }
  6. @Override
  7. public Integer output5V() {
  8. if (voltage220V == null) {
  9. return 0;
  10. }
  11. int src = voltage220V.output220V();
  12. int dst = src / 44;
  13. System.out.println("电压=" + dst + "伏");
  14. return dst;
  15. }
  16. }
  17. public class Client {
  18. public static void main(String[] args) {
  19. Phone phone = new Phone();
  20. phone.charing(new VoltageAdapter(new Voltage220V()));
  21. }
  22. }

注意事项和细节

  • 1)对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst 必须是接口
  • 2)使用成本更低,更灵活

3、接口适配器模式

  • 1)一些书籍称为:适配器模式或缺省适配器模式(Default Adapter Pattern)
  • 2)当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
  • 3)适用于一个接口不想使用其所有的方法的情况

实例

1)Android 中的属性动画 ValueAnimator 类可以通过 addListener(AnimatorListener listener)方法添加监听器,那么常规写法如下

  1. ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);
  2. valueAnimator.addListener(new Animator.AnimatorListener() {
  3. @Override
  4. public void onAnimatorStart(Animator animation) {
  5. }
  6. @Override
  7. public void onAnimatorEnd(Animator animation) {
  8. }
  9. @Override
  10. public void onAnimatorCancel(Animator animation) {
  11. }
  12. @Override
  13. public void onAnimatorRepeat(Animator animation) {
  14. }
  15. });
  16. valueAnimator.start();

2)有时候我们不想实现 Animator.AnimatorListener 接口的全部方法,我们只想监听 onAnimationStart,写法如下

  1. ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);
  2. valueAnimator.addListener(new AnimatorListenerAdapter() {
  3. @Override
  4. public void onAnimatorStart(Animator animation) {
  5. // XXXX具体实现
  6. }
  7. });
  8. valueAnimator.start();

3)AnimatorListenerAdapter 类就是一个接口适配器,它空实现了 Animator.AnimatorListener 类(src)的所有方法

  1. public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener, Animator.AnimatorPauseListener {
  2. @Override
  3. public void onAnimationCancel(Animator animation) {
  4. }
  5. @Override
  6. public void onAnimationEnd(Animator animation) {
  7. }
  8. @Override
  9. public void onAnimationRepeat(Animator animation) {
  10. }
  11. @Override
  12. public void onAnimationStart(Animator animation) {
  13. }
  14. @Override
  15. public void onAnimationPause(Animator animation) {
  16. }
  17. @Override
  18. public void onAnimationResume(Animator animation) {
  19. }
  20. }

4)AnimatorListener 是一个接口

  1. public static interface AnimatorListener {
  2. void onAnimationStart(Animator animation);
  3. void onAnimationEnd(Animator animation);
  4. void onAnimationCancel(Animator animation);
  5. void onAnimationRepeat(Animator animation);
  6. }

5)程序里的匿名内部类就是 Listener 具体实现类

  1. new AnimatorListenerAdapter() {
  2. @Override
  3. public void onAnimationStart(Animator animation){
  4. // xxxx具体实现
  5. }
  6. }

现在我们按照上述步骤,自己去实现一下

UML 类图

image.png

核心代码

  1. public interface Interface4 {
  2. void operation1();
  3. void operation2();
  4. void operation3();
  5. void operation4();
  6. }
  7. public abstract class AbsAdapter implements Interface4 {
  8. @Override
  9. public void operation1() {
  10. }
  11. @Override
  12. public void operation2() {
  13. }
  14. @Override
  15. public void operation3() {
  16. }
  17. @Override
  18. public void operation4() {
  19. }
  20. }
  21. public class Client {
  22. public static void main(String[] args) {
  23. AbsAdapter absAdapter = new AbsAdapter() {
  24. @Override
  25. public void operation1() {
  26. System.out.println("调用operation1方法");
  27. }
  28. };
  29. absAdapter.operation1();
  30. }
  31. }

4、SpringMVC 框架源码分析

1)SpringMVC 中的 HandlerAdapter,就使用了适配器模式

2)SpringMVC 处理请求的流程回顾

image.png

image.png

3)使用 HandlerAdapter 的原因分析

在 DispatcherServlet 中,有一个 doDispatch 方法,其中便使用到了 HandlerAdapter 适配器

image.png

通过 request 可以获得一个 Handler,再根据这个 Handler 获得不同的 HandlerAdapter 进行处理

image.png

HandlerAdapter 本质上是一个适配器接口,具体的适配器实现类有多种,其中有我们较为熟悉的 HttpRequestHandlerAdapter 和 RequestMappingHandlerAdapter

image.png

HandlerAdapter 的实现子类是的每一种 Controller 有一种对应的适配器实现类,每种 Controller 有不同的实现方式

言归正传,拿到 HandlerAdapter 适配器之后,便会调用其中的 handle 方法, 此方法便是具体的适配器实现类需要实现的方法

image.png

可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的。如果需要直接调用 Controller 方法,需要调用的时候就得不断使用if-else来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller,就得修改原来的代码,这样违背了 OCP 原则

4)为了更深刻地理解其中运用的模式思想,我们自己动手写 SpringMVC,通过适配器设计模式获取到对应的 Controller 的源码

5、自己动手写 SpringMVC

UML 类图

image.png

核心代码

  1. public interface Controller {
  2. }
  3. public class AnnotationController implements Controller {
  4. public void doAnnotationHandler() {
  5. System.out.println("annotation...");
  6. }
  7. }
  8. public class HttpController implements Controller {
  9. public void doHttpHandler() {
  10. System.out.println("http...");
  11. }
  12. }
  13. public class SimpleController implements Controller {
  14. public void doSimplerHandler() {
  15. System.out.println("simple...");
  16. }
  17. }
  18. //定义一个Adapter接口
  19. public interface HandlerAdapter {
  20. boolean supports(Object handler);
  21. void handle(Object handler);
  22. }
  23. public class AnnotationHandlerAdapter implements HandlerAdapter {
  24. @Override
  25. public void handle(Object handler) {
  26. ((AnnotationController) handler).doAnnotationHandler();
  27. }
  28. @Override
  29. public boolean supports(Object handler) {
  30. return (handler instanceof AnnotationController);
  31. }
  32. }
  33. public class HttpHandlerAdapter implements HandlerAdapter {
  34. @Override
  35. public void handle(Object handler) {
  36. ((HttpController) handler).doHttpHandler();
  37. }
  38. @Override
  39. public boolean supports(Object handler) {
  40. return (handler instanceof HttpController);
  41. }
  42. }
  43. public class SimpleHandlerAdapter implements HandlerAdapter {
  44. @Override
  45. public void handle(Object handler) {
  46. ((SimpleController) handler).doSimplerHandler();
  47. }
  48. @Override
  49. public boolean supports(Object handler) {
  50. return (handler instanceof SimpleController);
  51. }
  52. }
  53. public class DispatchServlet {
  54. public static List<HandlerAdapter> handlerAdapters = new ArrayList<>();
  55. public DispatchServlet() {
  56. handlerAdapters.add(new AnnotationHandlerAdapter());
  57. handlerAdapters.add(new HttpHandlerAdapter());
  58. handlerAdapters.add(new SimpleHandlerAdapter());
  59. }
  60. public void doDispatch() {
  61. // 此处模拟 SpringMVC 从 request 取 handler 的对象,适配器可以获取到希望的 Controller
  62. //HttpController controller = new HttpController();
  63. SimpleController controller = new SimpleController();
  64. //AnnotationController controller = new AnnotationController();
  65. // 得到对应适配器
  66. HandlerAdapter adapter = getHandler(controller);
  67. //通过适配器执行对应的controller对应方法
  68. adapter.handle(controller);
  69. }
  70. public HandlerAdapter getHandler(Controller controller) {
  71. //遍历:根据得到的controller(handler),返回对应适配器
  72. for (HandlerAdapter adapter : this.handlerAdapters) {
  73. if (adapter.supports(controller)) {
  74. return adapter;
  75. }
  76. }
  77. return null;
  78. }
  79. }

说明

  • Spring 定义了一个适配接口,使得每一种 Controller 有一种对应的适配器实现类
  • 适配器代替 Controller 执行相应的方法
  • 扩展 Controller 时,只需要增加一个适配器类就完成了 SpringMVC 的扩展了
  • 这就是设计模式的力量

注意事项和细节

  • 1)三种命名方式,是根据 src 是以怎样的形式给到 Adapter(在Adapter里的形式)来命名的
  • 2)三种适配器模式
    • 类适配器:以类给到,在 Adapter 里将 src 作为一个类,继承
    • 对象适配器:以对象给到,在Adapter 里将 src 作为一个对象,持有
    • 接口适配器:以接口给到,在 Adapter 里将 src 作为一个接口,实现
  • 3)Adapter 模式最大的作用还是将原本不兼容的接口融合在一起工作
  • 4)实际开发中,实现起来不拘泥于我们讲解的三种经典形式

笔记来源:尚硅谷Java设计模式(图解+框架源码剖析)

桥接模式

1、传统方式解决手机操作问题

现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:

image.png

UML 类图

image.png

问题分析

  1. 扩展性问题(类爆炸):如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类;同样如果我们增加一个手机品牌,也要在各个手机样式类下增加
  2. 违反了单一职责原则:当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本
  3. 解决方案——使用桥接模式

2、桥接模式基本介绍

  1. 桥接模式(Bridge模式):一种结构型设计模式:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变
  2. Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责
  3. 它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展

原理类图

image.png

原理类图说明

  • Client:桥接模式的调用者
  • Abstraction:Abstraction 充当桥接类,维护了 Implementor,即 ConcreteImplementorA / ConcreteImplementorB
  • RefinedAbstraction:Abstraction 抽象类的子类
  • Implementor:行为实现类的接口
  • ConcreteImplementorA / ConcreteImplementorB:行为的具体实现类
  • 这里的抽象类和接口是聚合的关系,也是调用者和被调用者的关系

3、桥接模式解决手机操作问题

UML 类图

image.png

核心代码

  1. // 行为接口——品牌接口
  2. public interface Branch {
  3. void open();
  4. void call();
  5. void close();
  6. }
  7. // 行为实现类——华为品牌
  8. public class Huawei implements Branch {
  9. @Override
  10. public void open() {
  11. System.out.println("华为手机开机");
  12. }
  13. @Override
  14. public void call() {
  15. System.out.println("华为手机打电话");
  16. }
  17. @Override
  18. public void close() {
  19. System.out.println("华为手机关机");
  20. }
  21. }
  22. // 行为实现类——小米品牌
  23. public class Xiaomi implements Branch {
  24. @Override
  25. public void open() {
  26. System.out.println("小米手机开机");
  27. }
  28. @Override
  29. public void call() {
  30. System.out.println("小米手机打电话");
  31. }
  32. @Override
  33. public void close() {
  34. System.out.println("小米手机关机");
  35. }
  36. }
  37. // 行为实现类——苹果品牌
  38. public class iPhone implements Branch {
  39. @Override
  40. public void open() {
  41. System.out.println("苹果手机开机");
  42. }
  43. @Override
  44. public void call() {
  45. System.out.println("苹果手机打电话");
  46. }
  47. @Override
  48. public void close() {
  49. System.out.println("苹果手机关机");
  50. }
  51. }
  52. // 桥接类——手机抽象类
  53. public abstract class Phone {
  54. private Branch branch;
  55. public Phone(Branch branch) {
  56. this.branch = branch;
  57. }
  58. public void open() {
  59. branch.open();
  60. }
  61. public void call() {
  62. branch.call();
  63. }
  64. public void close() {
  65. branch.close();
  66. }
  67. }
  68. // 桥接子类——翻盖式手机
  69. public class FlipPhone extends Phone {
  70. public FlipPhone(Branch branch) {
  71. super(branch);
  72. System.out.println("翻盖式手机");
  73. }
  74. @Override
  75. public void open() {
  76. super.open();
  77. }
  78. @Override
  79. public void call() {
  80. super.call();
  81. }
  82. @Override
  83. public void close() {
  84. super.close();
  85. }
  86. }
  87. // 桥接子类——滑盖式手机
  88. public class SlidePhone extends Phone {
  89. public SlidePhone(Branch branch) {
  90. super(branch);
  91. System.out.println("滑盖式手机");
  92. }
  93. @Override
  94. public void open() {
  95. super.open();
  96. }
  97. @Override
  98. public void call() {
  99. super.call();
  100. }
  101. @Override
  102. public void close() {
  103. super.close();
  104. }
  105. }
  106. // 桥接子类——直立式手机
  107. public class UprightPhone extends Phone {
  108. public UprightPhone(Branch branch) {
  109. super(branch);
  110. System.out.println("直立式手机");
  111. }
  112. @Override
  113. public void open() {
  114. super.open();
  115. }
  116. @Override
  117. public void call() {
  118. super.call();
  119. }
  120. @Override
  121. public void close() {
  122. super.close();
  123. }
  124. }
  125. public class Client {
  126. public static void main(String[] args) {
  127. //获取折叠式手机(样式+品牌)
  128. FoldPhone foldPhone = new FoldPhone(new xiaomi());
  129. foldPhone.open();
  130. foldPhone.call();
  131. foldPhone.close();
  132. System.out.println("===========");
  133. foldPhone=new FoldPhone(new vivo());
  134. foldPhone.open();
  135. foldPhone.call();
  136. foldPhone.close();
  137. System.out.println("=======调用直立式手机====");
  138. UpRighrPhone phone = new UpRighrPhone(new vivo());
  139. phone.open();
  140. phone.call();
  141. phone.close();
  142. phone=new UpRighrPhone(new xiaomi());
  143. phone.open();
  144. phone.call();
  145. phone.close();
  146. }
  147. }

4、JDK 源码分析

JDBC 的 Driver 接口:如果从桥接模式来看,Driver 就是一个接口,下面可以有 MySQL 的 Driver、Oracle 的 Driver,这些就可以当做实现接口类

image.png

Connection 继承体系

image.png

Driver源码

  1. public class Driver extends NonRegisteringDriver implements java.sql.Driver {
  2. static {
  3. try {
  4. java.sql.DriverManager.registerDriver(new Driver());
  5. } catch (SQLException E) {
  6. throw new RuntimeException("Can't register driver!");
  7. }
  8. }
  9. public Driver() throws SQLException {
  10. // Required for Class.forName().newInstance()
  11. }
  12. }

DriverManager 结构

image.png

说明

  • MySQL 有自己的 Connectionlmpl 类,同样 Oracle 也有对应的实现类
  • Driver 和 Connection 之间是通过 DriverManager 类进行桥连接的

5、注意事项和细节

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来。这有助于系统进行分层设计,从而产生更好的结构化系统
  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
  4. 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
  5. 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的后限性,即需要有这样的应用场景

6、桥接模式其他应用场景

对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用

常见的应用场景

  1. JDBC 驱动程序
  2. 银行转账系统
    • 转账分类:网上转账、柜台转账、AMT 转账
    • 转账用户类型:普通用户、银卡用户、金卡用户
  3. 消息管理
    • 消息类型:即时消息、延时消息
    • 消息分类:手机短信、邮件消息、QQ消息…

      装饰者模式

1、星巴克咖啡订单项目

星巴克咖啡订单项目(咖啡馆):

  • 1)咖啡种类/单品咖啡:Espresso(意大利浓咖)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
  • 2)调料:Mik、Soy(豆浆)、Chocolate
  • 3)要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
  • 4)使用 OO 的来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合

2、方案 1-解决星巴克咖啡订单项目(较差的方案)

image.png

方案 1-解决星巴克咖啡订单问题分析

  • 1)Drink 是一个抽象类,表示饮料
  • 2)description 就是对咖啡的描述,比如咖啡的名字
  • 3)cost 方法就是计算费用,Drink 类中做成一个抽象方法
  • 4)Decaf 就是单品咖啡,继承 Drink,并实现 cost
  • 5)Espresso && Milk 就是单品咖啡+调料,这个组合很多
  • 6)问题:这样设计,会有很多类。当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,出现类爆炸

3、方案 2-解决星巴克咖啡订单项目(好点的方案)

前面分析到方案 1 因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多。从而提高项目的维护性(如图)

image.png

说明:Milk、Soy、Chocolate 可以设计为 Boolean,表示是否要添加相应的调料

方案 2-解决星巴克咖啡订单问题分析

  • 1)方案 2 可以控制类的数量,不至于造成很多的类
  • 2)在增加或者删除调料种类时,代码的维护量很大
  • 3)考虑到用户可以添加多份调料时,可以将 hasMilk 返回一个对应 int
  • 4)考虑使用装饰者模式

4、装饰者模式

定义

1)装饰者模式:动态地将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式体现了开闭原则(OCP)

2)这里提到的动态的将新功能附加到对象和 OCP 原则,在后面的应用实例上会以代码的形式体现,请同学们注意体会

原理

  • 1)装饰者模式就像打包一个快递
    • 主体:比如陶瓷、衣服(Component)
    • 包装:比如报纸填充、塑料泡沫、纸板、木板(Decorator)
  • 2)主体(Component):比如前面的 Drink
  • 3)具体的主体(ConcreteComponent):比如前面的各个单品咖啡
  • 4)装饰者(Decorator):比如各调料
  • 4)Component 与 ConcreteComponent 之间,如果 ConcreteComponent 类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象成一个类

image.png

5、装饰者模式解决星巴克咖啡订单项目

image.png

说明

  • 1)Drink 就是抽象类 Component
  • 2)ShortBlack 单品咖啡就是具体的主体
  • 3)Decorator 是一个装饰类,含有一个被装饰的对象(Drink)
  • 4)Decorator 的 cost 方法进行一个费用的叠加,递归地计算价格

装饰者模式下的订单:2份巧克力 + 一份牛奶的 LongBlack

image.png

说明

  • 1)Milk 包含了 LongBlack
  • 2)一份 Chocolate 包含了 Milk + LongBlack
  • 3)一份 Chocolate 包含了 Chocolate + Milk + LongBlack
  • 4)这样不管是什么形式的单品咖啡 + 调料组合,通过递归方式可以方便的组合和维护

UML类图

image.png

核心代码

  1. // 抽象主体
  2. public abstract class Drink {
  3. private String desc;
  4. private Float price;
  5. public String getDesc() {
  6. return desc;
  7. }
  8. protected void setDesc(String desc) {
  9. this.desc = desc;
  10. }
  11. public Float getPrice() {
  12. return price;
  13. }
  14. protected void setPrice(Float price) {
  15. this.price = price;
  16. }
  17. public abstract Float cost();
  18. }
  19. // 具体主体
  20. public class Coffee extends Drink {
  21. @Override
  22. public Float cost() {
  23. return super.getPrice();
  24. }
  25. }
  26. public class Decaf extends Coffee {
  27. public Decaf() {
  28. setDesc("无因咖啡");
  29. setPrice(20.0f);
  30. }
  31. }
  32. public class Espresso extends Coffee {
  33. public Espresso() {
  34. setDesc("意大利浓咖");
  35. setPrice(30.0f);
  36. }
  37. }
  38. public class ShortBlack extends Coffee {
  39. public ShortBlack() {
  40. setDesc("短黑咖啡");
  41. setPrice(40.0f);
  42. }
  43. }
  44. public class LongBlack extends Coffee {
  45. public LongBlack() {
  46. setDesc("美式咖啡");
  47. setPrice(50.0f);
  48. }
  49. }
  50. //装饰者
  51. public class Decorator extends Drink {
  52. private Drink drink;
  53. public Decorator(Drink drink) {
  54. this.drink = drink;
  55. }
  56. @Override
  57. public Float cost() {
  58. return super.getPrice() + drink.cost();
  59. }
  60. }
  61. public class Milk extends Decorator {
  62. public Milk(Drink drink) {
  63. super(drink);
  64. setDesc("牛奶");
  65. setPrice(3.0f);
  66. }
  67. }
  68. public class Soy extends Decorator {
  69. public Soy(Drink drink) {
  70. super(drink);
  71. setDesc("豆浆");
  72. setPrice(4.0f);
  73. }
  74. }
  75. public class Chocolate extends Decorator {
  76. public Chocolate(Drink drink) {
  77. super(drink);
  78. setDesc("巧克力");
  79. setPrice(5.0f);
  80. }
  81. }
  82. // 调用者
  83. public class CoffeeBar {
  84. public static void main(String[] args) {
  85. Drink drink = new Espresso();
  86. System.out.println("意大利浓咖:" + drink.cost() + "美元"); // 意大利浓咖:30.0美元
  87. drink = new Milk(drink);
  88. System.out.println("意大利浓咖 + 1份牛奶:" + drink.cost() + "美元"); // 意大利浓咖 + 1份牛奶:33.0美元
  89. drink = new Chocolate(drink);
  90. System.out.println("意大利浓咖 + 1份牛奶 + 1份巧克力:" + drink.cost() + "美元"); // 意大利浓咖...:38.0美元
  91. drink = new Chocolate(drink);
  92. System.out.println("意大利浓咖 + 1份牛奶 + 2份巧克力:" + drink.cost() + "美元"); // 意大利浓咖...:43.0美元
  93. }
  94. }

6、JDK 源码分析

Java 的 IO 结构,FilterlnputStream 就是一个装饰者

image.png

核心代码

  1. // 是一个抽象类,即Component
  2. public abstract class InputStream implements Closeable {}
  3. // 是一个装饰类,即Decorator
  4. public class FilterInputStream extends InputStream {
  5. protected volatile InputStream in;
  6. protected FilterInputStream(InputStream in) {
  7. this.in = in;
  8. }
  9. }
  10. // FilterInputStream子类,也继承了被装饰的对象 in
  11. public class DataInputStream extends FilterInputStream implements DataInput {
  12. public DataInputStream(InputStream in) {
  13. super(in);
  14. }

分析

  • 1)InputStream 是抽象类,类似我们前面讲的 Drink
  • 2)FileInputStream 是 InputStream 子类,类似我们前面的 DeCaf、LongBlack
  • 3)FilterInputStream 是 InputStream 子类,类似我们前面的 Decorator,修饰者
  • 4)DataInputStream 是 FilterInputStream 子类,类似前面的Milk,Soy等,具体的修饰者
  • 5)FilterInputStream 类有protected volatile InputStream in;,即含被装饰者
  • 6)分析得出在 JDK 的 IO 体系,就是使用装饰者模式

笔记来源:尚硅谷Java设计模式(图解+框架源码剖析)

组合模式

1、学校院系展示需求

编写程序展示一个学校院系结构:

需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。如图:

image.png

传统方式解决学校院系展示(类图)

image.png

问题分析

  • 1)将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
  • 2)实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。因此这种方案,不能很好实现的 管理 的操作,比如对学院、系的添加、删除、遍历等
  • 3)解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作 ==> 组合模式

2、组合模式基本介绍

  • 1)组合模式(Composite Pattern),又叫部分整体模式。它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系
  • 2)组合模式依据树形结构来组合对象,用来表示部分以及整体层次
  • 3)这种类型的设计模式属于结构型模式
  • 4)组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象

原理类图

结构型模式 - 图30

对原理结构图的说明一即组合模式的角色及职责

  • 1)Component:这是组合中对象声明接口。在适当情况下,实现所有类共有的接口默认行为,用于访问和管理 Component子部件。Component可以是抽象类或者接口
  • 2)Leaf:在组合中表示叶子结点,叶子结点没有子节点
  • 3)Composite:非叶子结点,用于存储子部件,在Component接口中实现子部件的相关操作。比如增加、删除

解决的问题

组合模式解决这样的问题,当我们的要处理的对象可以生成一棵树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子

image.png

3、组合模式解决学校院系展示

UML 类图

image.png
核心代码

  1. // Component 抽象类
  2. public abstract class OrganizationComponent {
  3. private String name;
  4. public OrganizationComponent(String name) {
  5. this.name = name;
  6. }
  7. public String getName() {
  8. return name;
  9. }
  10. public void setName(String name) {
  11. this.name = name;
  12. }
  13. public void add(OrganizationComponent organizationComponent) {
  14. throw new UnsupportedOperationException();
  15. }
  16. public void remove(OrganizationComponent organizationComponent) {
  17. throw new UnsupportedOperationException();
  18. }
  19. public abstract void print();
  20. }
  21. // Composite 非叶子节点
  22. public class University extends OrganizationComponent {
  23. List<OrganizationComponent> organizationComponentList = new ArrayList<>();
  24. public University(String name) {
  25. super(name);
  26. }
  27. @Override
  28. public void add(OrganizationComponent organizationComponent) {
  29. organizationComponentList.add(organizationComponent);
  30. }
  31. @Override
  32. public void remove(OrganizationComponent organizationComponent) {
  33. organizationComponent.remove(organizationComponent);
  34. }
  35. @Override
  36. public void print() {
  37. for (OrganizationComponent organizationComponent : organizationComponentList) {
  38. organizationComponent.print();
  39. }
  40. }
  41. }
  42. public class College extends OrganizationComponent {
  43. List<OrganizationComponent> organizationComponentList = new ArrayList<>();
  44. public College(String name) {
  45. super(name);
  46. }
  47. @Override
  48. public void add(OrganizationComponent organizationComponent) {
  49. organizationComponentList.add(organizationComponent);
  50. }
  51. @Override
  52. public void remove(OrganizationComponent organizationComponent) {
  53. organizationComponent.remove(organizationComponent);
  54. }
  55. @Override
  56. public void print() {
  57. System.out.println("=============" + getName() + "=============");
  58. for (OrganizationComponent organizationComponent : organizationComponentList) {
  59. organizationComponent.print();
  60. }
  61. }
  62. }
  63. // Leaf 叶子结点
  64. public class Major extends OrganizationComponent {
  65. public Major(String name) {
  66. super(name);
  67. }
  68. @Override
  69. public void print() {
  70. System.out.println(getName());
  71. }
  72. }
  73. // 客户端
  74. public class Client {
  75. public static void main(String[] args) {
  76. //大学
  77. OrganizationComponent university = new University("清华大学");
  78. //学院
  79. OrganizationComponent computerCollege = new College("计算机学院");
  80. OrganizationComponent infoEngineerCollege = new College("信息工程学院");
  81. //专业
  82. computerCollege.add(new Major("软件工程"));
  83. computerCollege.add(new Major("网络工程"));
  84. computerCollege.add(new Major("计算机科学与技术"));
  85. infoEngineerCollege.add(new Major("通信工程"));
  86. infoEngineerCollege.add(new Major("信息工程"));
  87. university.add(computerCollege);
  88. university.add(infoEngineerCollege);
  89. university.print();
  90. //=============计算机学院=============
  91. //软件工程
  92. //网络工程
  93. //计算机科学与技术
  94. //=============信息工程学院=============
  95. //通信工程
  96. //信息工程
  97. }
  98. }

4、JDK 源码分析

Java 的集合类—— HashMap 就使用了组合模式

UML 类图

image.png

核心代码

  1. // Component
  2. public interface Map<K,V> {
  3. interface Entry<K,V> {}
  4. }
  5. public abstract class AbstractMap<K,V> implements Map<K,V> {}
  6. // Composite
  7. public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
  8. // Leaf
  9. static class Node<K,V> implements Map.Entry<K,V> {}
  10. }

说明

  • 1)Map 就是一个抽象的构建,类似Component
  • 2)HashMap 是一个中间的构建,类似Composite,实现 / 继承了相关方法 put、putAll
  • 3)Node 是 HashMap 的静态内部类,类似Leaf叶子节点,这里就没有 put

5、注意事项和细节

  • 1)简化客户端操作:客户端只需要面对一致的对象,而不用考虑整体部分或者节点叶子的问题
  • 2)具有较强扩展性:当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动
  • 3)方便创建复杂的层次结构:客户端不用理会组合里面的组成细节,容易添加节点或者叶子,从而创建出复杂的树形结构
  • 4)需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式
  • 5)要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式

外观模式

1、影院管理项目

组建一个家庭影院:

DVD 播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为:

  • 直接用遥控器:统筹各设备开关
  • 开爆米花机
  • 放下屏幕
  • 开投影仪
  • 开音响
  • 开DVD,选dvd
  • 去拿爆米花
  • 调暗灯光
  • 播放
  • 观影结束后,关闭各种设备

传统方式解决影院管理

image.png

  1. ClientTest{
  2. public static void main(String[] args){
  3. // 1、创建相关的对象
  4. // 2、调用创建的各个对象的一系列方法
  5. // 3、调用DVDPlayer对象的play方法
  6. }
  7. }

传统方式解决影院管理问题分析

  • 1)在 ClientTest 的 main 方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程
  • 2)不利于在 ClientTest 中去维护对子系统的操作
  • 3)解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法ready,play,pause,end),用来访问子系统中的一群接口
  • 4)也就是说就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节 ==》外观模式

2、外观模式基本介绍

外观模式(Facade),也叫过程模式

外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节,这个接口使得这一子系统更加容易使用

原理类图

image.png

原理类图的说明(外观模式的角色)

  • 1)外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
  • 2)调用者(Client):外观接口的调用者
  • 3)子系统的集合:指模块或者子系统,处理 Facade 对象指派的任务,是功能的实际提供者

3、外观模式解决影院管理

  • 1)外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能达到目的,比如:
    • 在 PC 上安装软件的时候经常有一键安装选项(省去选择安装目录、安装的组件等等)
    • 手机的重启功能(把关机和启动合为一个操作)
  • 2)外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用

image.png

使用外观模式来完成家庭影院项目

image.png

UML 类图

image.png

核心代码

【投影仪】

  1. public class Projector {
  2. private static Projector projector = new Projector();
  3. public static Projector getInstance() {
  4. return projector;
  5. }
  6. public void on() {
  7. System.out.println("打开投影仪...");
  8. }
  9. public void off() {
  10. System.out.println("关闭投影仪...");
  11. }
  12. public void focus() {
  13. System.out.println("投影仪聚焦...");
  14. }
  15. public void zoom() {
  16. System.out.println("投影仪放大...");
  17. }
  18. }

【DVD 播放器】

  1. public class DVDPlayer {
  2. private static DVDPlayer player = new DVDPlayer();
  3. public static DVDPlayer getInstance() {
  4. return player;
  5. }
  6. public void on() {
  7. System.out.println("打开DVD播放器...");
  8. }
  9. public void off() {
  10. System.out.println("关闭DVD播放器...");
  11. }
  12. public void play() {
  13. System.out.println("播放DVD播放器...");
  14. }
  15. public void pause() {
  16. System.out.println("暂停DVD播放器...");
  17. }
  18. public void setDvd(String dvd) {
  19. System.out.println("选dvd:" + dvd + "...");
  20. }
  21. }

【荧幕】

  1. public class Screen {
  2. private static Screen screen = new Screen();
  3. public static Screen getInstance() {
  4. return screen;
  5. }
  6. public void up() {
  7. System.out.println("升起荧幕...");
  8. }
  9. public void down() {
  10. System.out.println("拉下荧幕...");
  11. }
  12. }

【立体声】

  1. public class Stereo {
  2. private static Stereo stereo = new Stereo();
  3. public static Stereo getInstance() {
  4. return stereo;
  5. }
  6. public void on() {
  7. System.out.println("打开立体声...");
  8. }
  9. public void off() {
  10. System.out.println("关闭立体声...");
  11. }
  12. public void setVolume(Integer volume) {
  13. System.out.println("立体声音量+" + volume + "...");
  14. }
  15. }

【灯光】

  1. public class TheaterLights {
  2. private static TheaterLights lights = new TheaterLights();
  3. public static TheaterLights getInstance() {
  4. return lights;
  5. }
  6. public void on() {
  7. System.out.println("打开灯光...");
  8. }
  9. public void off() {
  10. System.out.println("关闭灯光...");
  11. }
  12. public void dim() {
  13. System.out.println("调暗灯光...");
  14. }
  15. public void bright() {
  16. System.out.println("调亮灯光...");
  17. }
  18. }

【爆米花机器】

  1. public class Popcorn {
  2. private static Popcorn popcorn = new Popcorn();
  3. public static Popcorn getInstance() {
  4. return popcorn;
  5. }
  6. public void on() {
  7. System.out.println("打开爆米花机器...");
  8. }
  9. public void off() {
  10. System.out.println("关闭爆米花机器...");
  11. }
  12. public void pop() {
  13. System.out.println("取出爆米花...");
  14. }
  15. }

【家庭影院 Facade】

  1. public class HomeTheaterFacade {
  2. private Popcorn popcorn;
  3. private Screen screen;
  4. private Stereo stereo;
  5. private TheaterLights lights;
  6. private Projector projector;
  7. private DVDPlayer player;
  8. public HomeTheaterFacade() {
  9. this.popcorn = Popcorn.getInstance();
  10. this.screen = Screen.getInstance();
  11. this.stereo = Stereo.getInstance();
  12. this.lights = TheaterLights.getInstance();
  13. this.projector = Projector.getInstance();
  14. this.player = DVDPlayer.getInstance();
  15. }
  16. public void ready() {
  17. lights.on(); // 打开灯光
  18. popcorn.on(); // 开爆米花机
  19. screen.down(); // 放下屏幕
  20. projector.on(); // 开投影仪
  21. projector.focus();
  22. projector.zoom();
  23. stereo.on(); // 开音响,设置音量
  24. stereo.setVolume(8);
  25. player.on(); // 开DVD,选dvd
  26. player.setDvd("坦塔尼克号");
  27. popcorn.pop(); // 去拿爆米花,关闭机器
  28. popcorn.off();
  29. lights.dim(); // 调暗灯光
  30. }
  31. public void play() {
  32. player.play();
  33. }
  34. public void pause() {
  35. player.pause();
  36. }
  37. public void end() {
  38. player.off();
  39. projector.off();
  40. stereo.off();
  41. lights.bright();
  42. screen.up();
  43. }
  44. }

【客户端】

  1. public class Client {
  2. public static void main(String[] args) throws InterruptedException {
  3. HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
  4. System.out.println("===========家庭影院初始化============");
  5. homeTheaterFacade.ready();
  6. System.out.println("===========家庭影院沉浸式播放============");
  7. homeTheaterFacade.play();
  8. Thread.sleep(1000);
  9. System.out.println("===========家庭影院暂停============");
  10. homeTheaterFacade.pause();
  11. Thread.sleep(1000);
  12. System.out.println("===========家庭影院沉浸式播放============");
  13. homeTheaterFacade.play();
  14. Thread.sleep(1000);
  15. System.out.println("===========家庭影院结束============");
  16. homeTheaterFacade.end();
  17. }
  18. }

4、MyBatis 框架源码分析

MyBatis 中 Configuration 去创建 MetaObject 对象时使用到了外观模式

代码分析

image.png

  1. private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
  2. this.originalObject = object;
  3. this.objectFactory = objectFactory;
  4. this.objectWrapperFactory = objectWrapperFactory;
  5. this.reflectorFactory = reflectorFactory;
  6. if (object instanceof ObjectWrapper) {
  7. this.objectWrapper = (ObjectWrapper)object;
  8. } else if (objectWrapperFactory.hasWrapperFor(object)) {
  9. this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
  10. } else if (object instanceof Map) {
  11. this.objectWrapper = new MapWrapper(this, (Map)object);
  12. } else if (object instanceof Collection) {
  13. this.objectWrapper = new CollectionWrapper(this, (Collection)object);
  14. } else {
  15. this.objectWrapper = new BeanWrapper(this, object);
  16. }
  17. }

示意图

image.png

5、外观模式的注意事项和细节

  • 1)外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
  • 2)外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
  • 3)通过合理的使用外观模式,可以帮我们更好的划分访问的层次
  • 4)当系统需要进行分层设计时,可以考虑使用 Facade 模式
  • 5)在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个 Facade 类,来提供遗留系统的比较清晰简单的接口,让新系统与 Facade 类交互,提高复用性
  • 6)不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的

享元模式

1、展示网站项目需求

小型的外包项目,给客户 A 做一个产品展示网站,客户 A 的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:

  • 1)有客户要求以新闻的形式发布
  • 2)有客户人要求以博客的形式发布
  • 3)有客户希望以微信公众号的形式发布

传统方案解决网站展现项目

  • 1)直接复制粘贴一份,然后根据客户不同要求,进行定制修改
  • 2)给每个网站租用一个空间
  • 3)方案设计示意图

image.png

传统方案解决网站展现项目-问题分析

  • 1)需要的网站结构相似度很高,而且都不是高问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
  • 2)解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源
  • 3)对于代码来说,由于是一份实例,维护和扩展都更加容易
  • 4)上面的解决思路就可以使用享元模式来解决

2、享元模式基本介绍

  • 1)享元模式(Flyweight Pattern)也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象
  • 2)常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
  • 3)享元模式能够解决重复对象的内存浪费的问题。当系统中有大量相似对象,需要缓冲池时,不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
  • 4)享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式

image.png

3、享元模式的原理类图

image.png

对原理图的说明——即模式的角色和职责

  • 1)Flyweight:抽象的享元角色,是抽象的产品类,同时定义出对象的外部状态和内部状态的接口和实现
  • 2)ConcreteFlyweight:具体的享元角色,是具体的产品类,实现抽象角色定义的相关业务
  • 3)UnsharedConcreteFlyweight:不可共享的角色,一般不会出现在享元工厂中
  • 4)FlyweightFactory:享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象的方法

4、内部状态和外部状态

比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点。所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同。当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态

  • 1)享元模式提出了两个要求:细粒度和共享对象。即将对象的信息分为两个部分:内部状态和外部状态
  • 2)内部状态:指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
  • 3)外部状态:指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态

举个例子:围模理论上有 361 个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生。因为内存空间有限,一台服务器很难支持更多的玩家玩围模游戏。如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题

5、享元模式解决网站展现项目

原理类图

image.png

UML 类图

image.png

核心代码

  1. /**
  2. * 内部状态,共享角色
  3. */
  4. public enum Type {
  5. 新闻,
  6. 博客,
  7. 微信公众号
  8. }
  9. /**
  10. * 外部状态,非共享角色
  11. */
  12. public class User {
  13. private String name;
  14. public User(String name) {
  15. this.name = name;
  16. }
  17. public String getName() {
  18. return name;
  19. }
  20. public void setName(String name) {
  21. this.name = name;
  22. }
  23. }
  24. /**
  25. * 抽象的享元角色
  26. */
  27. public abstract class Website {
  28. public abstract void use(User user);
  29. }
  30. /**
  31. * 具体的享元角色
  32. */
  33. public class ConcreteWebsite extends Website {
  34. private Type type;
  35. public ConcreteWebsite(Type type) {
  36. this.type = type;
  37. }
  38. @Override
  39. public void use(User user) {
  40. System.out.println("网站正在使用中:类型为" + type.name() + ",使用者为" + user.getName());
  41. }
  42. }
  43. /**
  44. * 享元工厂类
  45. */
  46. public class WebsiteFactory {
  47. private static Map<Type, Website> pool = new HashMap<>();
  48. public static Website getWebsiteCategory(Type type) {
  49. if (pool.get(type) == null) {
  50. pool.put(type, new ConcreteWebsite(type));
  51. }
  52. return pool.get(type);
  53. }
  54. public static Integer getSize() {
  55. return pool.size();
  56. }
  57. }

测试

  1. Website newsWebsite = WebsiteFactory.getWebsiteCategory(Type.新闻);
  2. newsWebsite.use(new User("Tom"));
  3. Website blogWebsite1 = WebsiteFactory.getWebsiteCategory(Type.博客);
  4. blogWebsite1.use(new User("Jerry"));
  5. Website blogWebsite2 = WebsiteFactory.getWebsiteCategory(Type.博客);
  6. blogWebsite2.use(new User("John"));
  7. Website blogWebsite3 = WebsiteFactory.getWebsiteCategory(Type.博客);
  8. blogWebsite3.use(new User("Smith"));
  9. Website wxWebsite = WebsiteFactory.getWebsiteCategory(Type.微信公众号);
  10. wxWebsite.use(new User("Mack"));
  11. System.out.println(WebsiteFactory.getSize());
  12. //网站正在使用中:类型为新闻,使用者为Tom
  13. //网站正在使用中:类型为博客,使用者为Jerry
  14. //网站正在使用中:类型为博客,使用者为John
  15. //网站正在使用中:类型为博客,使用者为Smith
  16. //网站正在使用中:类型为微信公众号,使用者为Mack
  17. //3

6、Integer 源码分析

首先先看一段代码测试

  1. //小结:
  2. //1.在valueof中先判断i的值是否在-128-127之间,如果在,就从IntegerCache中去取
  3. //否则,据从缓冲池中返回,在valueof中,使用到享元模式
  4. Integer x = Integer.valueOf(127);
  5. Integer y = new Integer(127);
  6. Integer z = Integer.valueOf(127);
  7. Integer w = new Integer(127);
  8. System.out.println(x.equals(y)); // true
  9. System.out.println(x == y); // false
  10. System.out.println(x == z); // true
  11. System.out.println(w == x); // false
  12. System.out.println(w == y); // false

我们知道:equals比较的是对象的内容,==比较的是对象的实例

  • x.equals(y)结果为true:比较的是大小,所以结果为true
  • x == yw == xw == y结果为false:由于 y 是 new 出来的,所以结果为false
  • x == z结果为true这是为什么呢???

我们追踪一下Integer对象的valueOf方法,看一下源码

image.png

这里的lowhigh是多少呢?

image.png

我们通过IntegerCache中源码大概基本分析出

  • low-128
  • high127

所以当Integer[-128, 127]时,会返回IntegerCachecache[]数组内容;否则,valueOf方法相当于new Integer

也就是说,Integer.valueOf(x)方法使用的就是享元模式

另外,我们也可以分析出:

  • 当数值范围在时,使用方法执行速度比更快[-128, 127]``valueOf``new

7、享元模式的注意事项和细节

  • 1)在享元模式这样理解,“享”就表示共享,“元”表示对象
  • 2)系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
  • 3)用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储
  • 4)享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
  • 5)享元模式提高了系统的复杂度,需要分离出内部状态和外部状态。而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方
  • 6)使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制
  • 7)享元模式经典的应用场景是需要缓冲池的场景,比如 String 常量池、数据库连接池

笔记来源:尚硅谷Java设计模式(图解+框架源码剖析)

代理模式

1、代理模式的基本介绍

  • 1)代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象
  • 2)这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能
  • 3)被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
  • 4)代理模式有不同的形式,主要有三种:
    • 静态代理
    • 动态代理:JDK 代理、接口代理
    • Cglib 代理:可以在内存动态的创建对象,而不需要实现接口,它是属于动态代理的范畴

image.png

2、静态代理

2.1、基本介绍

静态代理在使里时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现租同的接口或者是继承和一父类

2.2、应用实例

  • 1)定义一个接口:ITeacherDao
  • 2)目标对象TeacherDAO实现接口ITeacherDAO
  • 3)使用静态代理方式,就需要在代理对象TeacherDAOProxy中也实现ITeacherDAO
  • 4)调用的时候通过调用代理对象的方法来调用目标对象
  • 5)特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法

UML 类图

image.png

image.png

核心代码

  1. /**
  2. * 代理接口
  3. */
  4. public interface ITeacherDao {
  5. void teach();
  6. }
  7. /**
  8. * 被代理对象
  9. */
  10. public class TeacherDao implements ITeacherDao {
  11. @Override
  12. public void teach() {
  13. System.out.println("老师授课中...");
  14. }
  15. }
  16. /**
  17. * 代理对象
  18. */
  19. public class TeacherDaoProxy implements ITeacherDao {
  20. private ITeacherDao iTeacherDao;
  21. public TeacherDaoProxy(ITeacherDao iTeacherDao) {
  22. this.iTeacherDao = iTeacherDao;
  23. }
  24. @Override
  25. public void teach() {
  26. System.out.println("准备授课...");
  27. iTeacherDao.teach();
  28. System.out.println("结束授课...");
  29. }
  30. }

调用代理

  1. //创建被代理对象
  2. TeacherDao teacherDao = new TeacherDao();
  3. //创建代理对象,聚合被代理对象
  4. TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
  5. //通过代理对象,调用被代理对象的方法
  6. teacherDaoProxy.teach();

2.3、静态代理优缺点

  • 1)优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
  • 2)缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
  • 3)缺点:一旦接口增加方法,目标对象与代理对象都要维护

3、动态代理

3.1、基本介绍

  • 1)代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
  • 2)代理对象的生成,是利用 JDK 的 APl,动态的在内存中构建代理对象
  • 3)动态代理也叫做:JDK 代理、接口代理

3.2、JDK 中生成代理对象的 API

  • 1)代理类所在包:java.lang.reflect.Proxy
  • 2)JDK 实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
    1. static Object newProxylnstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

UML 类图

image.png

核心代码

  1. // ITeacherDao与TeacherDao同上
  2. /**
  3. * 代理工厂
  4. */
  5. public class TeacherFactory {
  6. /**
  7. * 目标对象
  8. */
  9. private Object target;
  10. public TeacherFactory(Object target) {
  11. this.target = target;
  12. }
  13. public Object newProxyInstance() {
  14. return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
  15. @Override
  16. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  17. System.out.println("JDK代理授课开始...");
  18. Object returnVal = method.invoke(target, args);
  19. System.out.println("JDK代理授课结束...");
  20. return returnVal;
  21. }
  22. });
  23. }
  24. }

其中几个参数

  • 1)ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法固定
  • 2)Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方法确认类型
  • 3)InvocationHandler h:事情处理,执行目标对象的方法时触发事情处理器方法,把当前执行的目标对象方法作为参数传入

4、Cglib 代理

4.1、基本介绍

  • 1)静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——这就是 Cglib 代理
  • 2)Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将 Cglib 代理归属到动态代理。
  • 3)Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截
  • 4)在 AOP 编程中如何选择代理模式:
    • 目标对象需要实现接口,用 JDK 代理
    • 目标对象不需要实现接口,用 Cglib 代理
  • 5)Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类

4.2、实现步骤

  • 1)需要引入cglib的 jar 文件
    image.png
  • 2)在内存中动态构建子类,注意代理的类不能为final,否则报错java.lang.IllegalArgumentException
  • 3)目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

4.3、应用实例

UML 类图

image.png

image.png

核心代码

  1. /**
  2. * 被代理对象
  3. */
  4. public class TeacherDao {
  5. public String teach() {
  6. System.out.println("老师授课中...");
  7. return "Good";
  8. }
  9. }
  10. /**
  11. * 代理工厂类
  12. */
  13. public class ProxyFactory implements MethodInterceptor {
  14. /**
  15. * 目标对象
  16. */
  17. private Object target;
  18. /**
  19. * 构造函数
  20. *
  21. * @param target
  22. */
  23. public ProxyFactory(Object target) {
  24. this.target = target;
  25. }
  26. /**
  27. * 返回代理对象
  28. *
  29. * @return
  30. */
  31. public Object getProxyInstance() {
  32. // 1、创建工具类
  33. Enhancer enhancer = new Enhancer();
  34. // 2、设置父类
  35. enhancer.setSuperclass(target.getClass());
  36. // 3、设置回调函数
  37. enhancer.setCallback(this);
  38. // 4、创建子类对象,即代理对象
  39. return enhancer.create();
  40. }
  41. @Override
  42. public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  43. System.out.println("cglib代理开始...");
  44. Object retVal = method.invoke(target, args);
  45. System.out.println("cglib代理结束...");
  46. return retVal;
  47. }
  48. }

调用代理

  1. //创建目标对象
  2. TeacherDao teacherDao = new TeacherDao();
  3. //通过代理工厂创建代理对象
  4. TeacherDao proxyInstance = (TeacherDao) new ProxyFactory(teacherDao).getProxyInstance();
  5. //通过代理对象调用目标对象方法
  6. String retVal = proxyInstance.teach();
  7. System.out.println("retVal=" + retVal);

5、代理模式的变体

几种常见的代理模式介绍一几种变体

  • 1)防火墙代理:内网通过代理穿透防火墙,实现对公网的访问
  • 2)缓存代理:比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok;如果取不到资源,再到公网或者数据库取,然后缓存
  • 3)远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息
    image.png
  • 4)同步代理:主要使用在多线程编程中,完成多线程间同步工作