1、Java设计模式内容介绍

先看几个经典的面试题
原型设计模式问题
1) 有请使用UML类图画出原型模式核心角色
2) 原型设计模式的深拷贝和浅拷贝是什么,并写出深拷贝的两种方式的源码(重写
clone方法实现深拷贝、使用序列化来实现深拷贝)
3) 在Spring框架中哪里使用到原型模式,并对源码进行分析
beans.xml

4) Spring中原型bean的创建,就是原型模式的应用
5) 代码分析+Debug源码
image.png
先看几个经典的设计模式面试题
设计模式的七大原则: 要求:
1) 七大设计原则核心思想
2) 能够以类图的说明设计原则
3) 在项目实际开发中,你在哪里使用到了ocp原则
设计模式常用的七大原则有:
1) 单一职责原则
2) 接口隔离原则
3) 依赖倒转原则
4) 里氏替换原则
5) 开闭原则 ocp
6) 迪米特法则
7) 合成复用原则
image.png
金融借贷平台项目
借贷平台的订单,有审核- 发布-抢单 等等 步骤,随着操作的不同,会改 变订单的状态, 项目中的这个模块实现就会使用 到状态模式,请你使用状态模式进行设计,并 完成实际代码
问题分析
这类代码难以应对变化,在添加一种状态时, 我们需要手动添加if/else,在添加一种功能时, 要对所有的状态进行判断。因此代码会变得越 来越臃肿,并且一旦没有处理某个状态,便会 发生极其严重的BUG,难以维护
image.png
解释器设计模式
1) 介绍解释器设计模式是什么?
2) 画出解释器设计模式的UML类图, 分析设计模式中的各个角色是什 么?
3) 请说明Spring的框架中,哪里 使用到了解释器设计模式,并 做源码级别的分析
解释器模式在Spring框架应用的源码剖析
1) Spring框架中 SpelExpressionParser就使用到解释器模式
2) 代码分析+Debug源码+模式角色分析说明
image.png
单例设计模式一共有几种实现方式?请分别用代码实现,并说明各个实现方式的 优点和缺点?
单例设计模式一共有8种写法,后面我们会依次讲到

  1. 饿汉式 两种
  2. 懒汉式 三种
  3. 双重检查
  4. 静态内部类
  5. 枚举

设计模式的重要性
拿实际工作经历来说, 当一个项目开发完后,如果客户提出增新功能,怎么办?。

  1. 如果项目开发完后,原来程序员离职,你接手维护该项目怎么办? (维护性[可读性、规范性])
  2. 目前程序员门槛越来越高,一线IT公司(大厂),都会问你在实际项目中使用过什么设计模式,怎样使用的,解决了什么问题。
  3. 设计模式在软件中哪里?面向对象(oo)=>功能模块[设计模式+算法(数据结构)]=>框架[使用到多种设计模式]=>架构 [服务器集群]
  4. 如果想成为合格软件工程师,那就花时间来研究下设计模式是非常必要的

    2、设计模式七大原则

    2.1、设计模式的目的

    编写软件过程中,程序员面临着来自 耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性 等多方面的挑战,设计模式是为了让程序(软件),具有更好:

  5. 代码重用性(即:相同功能的代码,不用多次编写)

  6. 可读性(即:编程规范性,便于其他程序员的阅读和理解)
  7. 可扩展性(即:当需要增加新的功能时,非常的方便,称为可维护)
  8. 可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
  9. 使程序呈现高内聚,低耦合的特性
  10. 分享金句:
    1. 设计模式包含了面向对象的精髓,“懂了设计模式,你就懂了面向对象分析和设计(OOA/D)的精要”
    2. Scott Mayers在其巨著《Effective C++》就曾经说过:C++老手和C++新手的区别就是前者手背上有很多伤疤

2.2、单一接口原则

单一职责(Simple Responsibility Pinciple,SRP)是指不要存在多于一个导致类变更 的原因。
核心:就是解耦和增强内聚性。
对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。 当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2
优点:

  • 提高类的可维护性和可读写性
  • 提高系统的可维护性
  • 降低变更的风险

1) 降低类的复杂度,一个类只负责一项职责。
2) 提高类的可读性,可维护性
3) 降低变更引起的风险
4) 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则
假设我们有一个 Class 负责两个职责,一旦发生需求变更,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障。这样一来,这个 Class 存在两个导 致类变更的原因。如何解决这个问题呢?我们就要给两个职责分别用两个 Class 来实现, 进行解耦。后期需求变更维护互不影响。这样的设计,可以降低类的复杂度,提高类的 可读性,提高系统的可维护性,降低变更引起的风险。总体来说就是一个 Class/Interface/Method 只负责一项职责。
下面我们来举一个实例

  1. public interface UserOperate {
  2. void updateUserName(UserInfo userInfo);
  3. void updateUserPassword(UserInfo userInfo);
  4. }
  5. public class UserOperateImpl implements UserOperate {
  6. @Override
  7. public void updateUserName(UserInfo userInfo) {
  8. // 修改用户名逻辑
  9. }
  10. @Override
  11. public void updateUserPassword(UserInfo userInfo) {
  12. // 修改密码逻辑
  13. }
  14. }

修改用户名和修改密码逻辑分开,各自执行各自的职责,互不干扰,功能清晰明了。
具体比较实现
方式一:

  1. /**
  2. * 做家务
  3. */
  4. public interface HouseWork {
  5. // 扫地
  6. void sweepFloor();
  7. // 购物
  8. void shopping();
  9. }
  10. public class Xiaocheng implements HouseWork{
  11. @Override
  12. public void sweepFloor() {
  13. // 扫地
  14. }
  15. @Override
  16. public void shopping() {
  17. }
  18. }
  19. public class Xiaoming implements HouseWork{
  20. @Override
  21. public void sweepFloor() {
  22. }
  23. @Override
  24. public void shopping() {
  25. // 买菜
  26. }
  27. }

说是妈妈在出门前嘱咐小明和小陈做家务(定义一个做家务接口),小明去买菜(实现买菜接口),小陈去扫地(实现扫地接口)。到这里其实你就发现,小明不需要扫地却要重写扫地的方法,小陈不需要买菜但因为实现了做家务的接口,就不得不也重写买菜方法。

这样的设计是不合理的,他违背了单一原则,也不符合开闭原则。

方式二:

  1. public interface Cooking extends Hoursework{
  2. void cooking();
  3. }
  4. public class Xiaoming implements Shopping, Cooking{
  5. @Override
  6. public void shopping() {
  7. // 小明购物
  8. }
  9. @Override
  10. public void cooking() {
  11. // 小明做饭
  12. }
  13. }

现在我们将扫地和做家务这个接口拆分,小陈扫地,那就实现扫地接口,小明购物就实现买菜接口。
接下来小明买完菜回来做饭,那么就只需要新增一个做法接口,让小明实现就可以了
一个接口实现一个功能就好。
接口层面,子类不实现多余的接口,这就符合单一原则:一个类只做一件事,并且修改它不会带来其他变化

2.3、接口隔离原则

接口隔离原则(Interface Segregation Principle),又称ISP原则
1、 客户端不应该依赖它不需要的接口
2、 类间的依赖关系应该建立在最小的接口上

介绍:
通俗的来讲,不要在一个接口中定义多个方法,接口应该尽量细化
案例实现接口隔离

  1. public class SegregationDemo {
  2. public static void main(String[] args) {
  3. new FruitShop().cutApple(new CookZhang());
  4. new FruitShop().cutTomato(new CookZhang());
  5. new VegetableShop().cutTomato(new CookLi());
  6. new VegetableShop().cutPotato(new CookLi());
  7. }
  8. }
  9. interface Knife {
  10. void cutApple();
  11. void cutTomato();
  12. void cutPotato();
  13. }
  14. class CookZhang implements Knife {
  15. @Override
  16. public void cutApple() {
  17. System.out.println("张师傅在切苹果");
  18. }
  19. @Override
  20. public void cutTomato() {
  21. System.out.println("张师傅在切土豆");
  22. }
  23. @Override
  24. public void cutPotato() {
  25. System.out.println("张师傅在切番茄");
  26. }
  27. }
  28. class CookLi implements Knife {
  29. @Override
  30. public void cutApple() {
  31. System.out.println("李师傅在切苹果");
  32. }
  33. @Override
  34. public void cutTomato() {
  35. System.out.println("李师傅在切土豆");
  36. }
  37. @Override
  38. public void cutPotato() {
  39. System.out.println("李师傅在切番茄");
  40. }
  41. }
  42. class FruitShop {
  43. public void cutApple(Knife knife) {
  44. knife.cutApple();
  45. }
  46. public void cutTomato(Knife knife) {
  47. knife.cutTomato();
  48. }
  49. }
  50. class VegetableShop {
  51. public void cutPotato(Knife knife) {
  52. knife.cutPotato();
  53. }
  54. public void cutTomato(Knife knife) {
  55. knife.cutTomato();
  56. }
  57. }

这里会实现过多的多余的实现方法
修改改接口后的代码:

  1. public class SegregationDemo {
  2. public static void main(String[] args) {
  3. new FruitShop().cutApple(new CookZhang());
  4. new FruitShop().cutTomato(new CookZhang());
  5. new VegetableShop().cutTomato(new CookLi());
  6. new VegetableShop().cutPotato(new CookLi());
  7. }
  8. }
  9. interface AppleKnife {
  10. void cutApple();
  11. }
  12. interface TomatoKnife {
  13. void cutTomato();
  14. }
  15. interface PotatoKnife {
  16. void cutPotato();
  17. }
  18. class CookZhang implements AppleKnife, TomatoKnife {
  19. @Override
  20. public void cutApple() {
  21. System.out.println("张师傅在切苹果");
  22. }
  23. @Override
  24. public void cutTomato() {
  25. System.out.println("张师傅在切番茄");
  26. }
  27. }
  28. class CookLi implements TomatoKnife, PotatoKnife {
  29. @Override
  30. public void cutTomato() {
  31. System.out.println("李师傅在切土豆");
  32. }
  33. @Override
  34. public void cutPotato() {
  35. System.out.println("李师傅在切番茄");
  36. }
  37. }
  38. class FruitShop {
  39. public void cutApple(AppleKnife knife) {
  40. knife.cutApple();
  41. }
  42. public void cutTomato(TomatoKnife knife) {
  43. knife.cutTomato();
  44. }
  45. }
  46. class VegetableShop {
  47. public void cutPotato(PotatoKnife knife) {
  48. knife.cutPotato();
  49. }
  50. public void cutTomato(TomatoKnife knife) {
  51. knife.cutTomato();
  52. }
  53. }

2.4、依赖倒置原则

依赖倒置原则的原始定义为
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象
核心就是面向接口编程;
让细节的具体实现类去依赖(使用)抽象类或者接口;而不是让接口或抽象类去依赖细节的具体实现类.
依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。

依赖倒置原则的主要作用如下

  • 依赖倒置原则可以降低类间的耦合性。
  • 依赖倒置原则可以提高系统的稳定性。
  • 依赖倒置原则可以减少并行开发引起的风险。
  • 依赖倒置原则可以提高代码的可读性和可维护性。

依赖倒置原则的实现方法
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则。

  1. 每个类尽量提供接口或抽象类,或者两者都具备。
  2. 变量的声明类型尽量是接口或者是抽象类。
  3. 任何类都不应该从具体类派生。
  4. 使用继承时尽量遵循里氏替换原则。

下面以“顾客购物程序”为例来说明依赖倒置原则的应用。
【例1】依赖倒置原则在“顾客购物程序”中的应用。
分析:本程序反映了 “顾客类”与“商店类”的关系。商店类中有 sell() 方法,顾客类通过该方法购物以下代码定义了顾客类通过韶关网店 ShaoguanShop 购物:

  1. class Customer {
  2. public void shopping(ShaoguanShop shop) {
  3. //购物
  4. System.out.println(shop.sell());
  5. }
  6. }

但是,这种设计存在缺点,如果该顾客想从另外一家商店(如婺源网店 WuyuanShop)购物,就要将该顾客的代码修改如下:(上面的实现方法写死了店铺地址, 我们需要实现动态实现)
顾客每更换一家商店,都要修改一次代码,这明显违背了开闭原则。存在以上缺点的原因是:顾客类设计时同具体的商店类绑定了,这违背了依赖倒置原则。解决方法是:定义“婺源网店”和“韶关网店”的共同接口 Shop顾客类面向该接口编程,其代码修改如下:

  1. class Customer {
  2. public void shopping(Shop shop) {
  3. //购物
  4. System.out.println(shop.sell());
  5. }
  6. }

这样,不管顾客类 Customer 访问什么商店,或者增加新的商店,都不需要修改原有代码了。
程序代码如下:
1、接口传递实现:

  1. public class DIPtest {
  2. public static void main(String[] args) {
  3. Customer wang = new Customer();
  4. System.out.println("顾客购买以下商品:");
  5. wang.shopping(new ShaoguanShop());
  6. wang.shopping(new WuyuanShop());
  7. }
  8. }
  9. //商店
  10. interface Shop {
  11. public String sell(); //卖
  12. }
  13. //韶关网店
  14. class ShaoguanShop implements Shop {
  15. public String sell() {
  16. return "韶关土特产:香菇、木耳……";
  17. }
  18. }
  19. //婺源网店
  20. class WuyuanShop implements Shop {
  21. public String sell() {
  22. return "婺源土特产:绿茶、酒糟鱼……";
  23. }
  24. }
  25. //顾客
  26. class Customer {
  27. public void shopping(Shop shop) {
  28. //购物
  29. System.out.println(shop.sell());
  30. }
  31. }

再来一个例子:

  1. public class DependenceInversion02 {
  2. public static void main(String[] args) {
  3. Person person=new Person();
  4. person.getMes(new QQ());
  5. person.getMes(new Message());
  6. }
  7. }
  8. //接收消息的接口;
  9. interface GetMes{
  10. //看看收到的消息;
  11. String show();
  12. }
  13. //短信类;
  14. class Message implements GetMes{
  15. public String show(){
  16. return "收到短信了";
  17. }
  18. }
  19. //QQ消息;
  20. class QQ implements GetMes{
  21. public String show(){
  22. return "收到QQ消息了";
  23. }
  24. }
  25. //具体的人;
  26. class Person{
  27. //接收消息;
  28. public void getMes(GetMes getMes){
  29. System.out.println(getMes.show());
  30. }
  31. }

2.5、里氏替换原则

里氏替换原则(Liskov Substitution Principle)(LSP)
定义:所有引用基类的地方必须能透明的使用子类对象
就是说当在程序中将一个对象替换成他的子类时,程序可以继续原有的行为,他察觉不出符类和子类的区别。但是反过来却不成立,如果一个程序使用的是一个子类的话,他不一定适用于父类。
以电脑举例:电脑有CPU,电脑就是程序实体,CPU就是它使用的基类,CPU又有子类IntelCpu。

  1. public class Cpu {
  2. public void work(){
  3. System.out.println("CPU在工作");
  4. }
  5. }
  6. public class IntelCpu extends Cpu {
  7. @Override
  8. public void work() {
  9. System.out.println("英特尔CPU工作");
  10. }
  11. }
  12. public class Computer {
  13. private Cpu cpu;
  14. public void run() {
  15. this.cpu.work();
  16. }
  17. public Cpu getCpu() {
  18. return cpu;
  19. }
  20. public void setCpu(Cpu cpu) {
  21. this.cpu = cpu;
  22. }
  23. }

电脑依赖的是父类,此时将Cpu换成Intel类型的,电脑仍能正常工作,它察觉不到任何改变,这符合里氏替换原则。
而反过来,假设现在有一台电脑,它只能使用IntelCpu才能工作。 就是只能子类替代父类而不能父类替代子类。

实例二:
让类A和类B公共去继承同一个类,这样A与B之间的耦合性就降低了;
若类B还想用类A的方法,可使用组合/聚合/依赖的方式将类A的方法调用过来.

  1. public class LiskovSubstitution02 {
  2. public static void main(String[] args) {
  3. System.out.println("类A调用方法");
  4. A a = new A();
  5. System.out.println("11-3=" + a.func1(11, 3));
  6. System.out.println("1-8=" + a.func1(1, 8));
  7. System.out.println("类B调用方法");
  8. B b = new B();
  9. System.out.println("11-3=" + b.func3(11, 3));
  10. System.out.println("1-8=" + b.func3(1, 8));
  11. System.out.println("11+3+9=" + b.func2(11, 3));
  12. }
  13. }
  14. //创建一个公共类;让类A和类B去继承它
  15. class PubClass{
  16. }
  17. //类 A 继承公共类 ;完成两数相减的任务;
  18. class A extends PubClass{
  19. public int func1(int num1, int num2) {
  20. return num1 - num2;
  21. }
  22. }
  23. //类B 继承 公共类; 计划完成两数相加的任务;
  24. class B extends PubClass {
  25. //若类B还想用类A的方法;可使用组合的方式;
  26. private A a=new A();
  27. public int func3(int num1,int num2){
  28. //这里调用的就是类A的方法了;
  29. return this.a.func1(num1, num2);
  30. }
  31. //这里的方法func1就和类A没关系了;
  32. public int func1(int a, int b) {
  33. return a + b;
  34. }
  35. //在这里就是两数相加 + 9 ;
  36. public int func2(int a, int b) {
  37. return func1(a, b) + 9;
  38. }
  39. }
  1. A调用方法
  2. 11-3=8
  3. 1-8=-7
  4. B调用方法
  5. 11-3=8
  6. 1-8=-7
  7. 11+3+9=23

2.6、开闭原则(Open Closed Principle)

类,作用域,方法 应该做到 对扩展开放 (扩展开放是对提供方来说的) ;对修改关闭(修改关闭是对于使用方而言;例如说扩展了一个新的功能,使用方的代码不用去修改);
接口抽象地构建框架;然后通过实现类扩展实现功能.

案例:不同的图形继承图形类;得到 m_type 属性;
在绘制图形类(使用方)GraphicEditor中,根据不同的 m_type 去绘制图形,而每次要添加新的图形绘制时,需要定义图形后;然后还要在使用方GraphicEditor类中进行定义方法;

  1. public class OpenClosed02 {
  2. public static void main(String[] args) {
  3. GraphicEditor graphicEditor = new GraphicEditor();
  4. graphicEditor.drawShape(new Rectangle());//我要矩形
  5. graphicEditor.drawShape(new Circle());//我要圆形
  6. graphicEditor.drawShape(new Square());//我要正方形
  7. }
  8. }
  9. //绘制图形类;
  10. class GraphicEditor {
  11. //根据不同的 m_type 绘图;
  12. public void drawShape(Shape s) {
  13. s.willDraw();
  14. }
  15. }
  16. //图形类 (父类)==>作为抽象类;
  17. abstract class Shape {
  18. int m_type;
  19. //抽象方法;
  20. public abstract void willDraw();
  21. }
  22. //矩形类,继承图形类
  23. class Rectangle extends Shape {
  24. Rectangle() {
  25. super.m_type = 1;
  26. }
  27. public void willDraw() {
  28. System.out.println("我要矩形");
  29. }
  30. }
  31. //圆形类;继承图形类;
  32. class Circle extends Shape {
  33. Circle() {
  34. super.m_type = 2;
  35. }
  36. public void willDraw() {
  37. System.out.println("我要原形");
  38. }
  39. }
  40. //如果需要新添加一个绘图方式;
  41. class Square extends Shape{
  42. Square(){
  43. super.m_type =3;
  44. }
  45. public void willDraw() {
  46. System.out.println("我要正方形");
  47. }
  48. }

2.7、迪米特法原则

类和类之间的关系越是复杂,他们的耦合性就会越高.
比如说 类 A依赖类B;那么类A就在内部 把类B的逻辑封装起来;不对外泄露信息.
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

只要两个对象之间有耦合关系,那么这两个对象就是朋友关系;
出现成员变量,方法参数,方法返回值中的类为直接的朋友;
只与直接的朋友通信.

一般不要将其他类作为局部变量使用.
这里老师是通过班长这个第三者来清点学生的数量 而不是直接去清点 请求班长执行

  1. public interface ITeacher {
  2. void command(IGroupLeader groupLeader);
  3. }
  4. public class Teacher implements ITeacher {
  5. @Override
  6. public void command(IGroupLeader groupLeader) {
  7. // 班长清点人数
  8. groupLeader.count();
  9. }
  10. }
  11. /**
  12. * 班长类
  13. */
  14. public interface IGroupLeader {
  15. // 班长清点人数
  16. void count();
  17. }
  18. /**
  19. * 班长类
  20. */
  21. public class GroupLeader implements IGroupLeader {
  22. private List<Student> students;
  23. public GroupLeader(List<Student> students) {
  24. this.students = students;
  25. }
  26. /**
  27. * 班长清点人数
  28. */
  29. @Override
  30. public void count() {
  31. // 班长清点人数
  32. System.out.println("上课的学生人数是: " + students.size());
  33. }
  34. }
  35. /**
  36. * 学生类
  37. */
  38. public interface IStudent {
  39. }
  40. /**
  41. * 学生类
  42. */
  43. public class Student implements IStudent {
  44. }
  45. /**
  46. * 客户端
  47. */
  48. public class Client {
  49. public static void main(String[] args) {
  50. // 老师类
  51. ITeacher wangTeacher = new Teacher();
  52. List<Student> allStudent = new ArrayList(10);
  53. allStudent.add(new Student());
  54. allStudent.add(new Student());
  55. allStudent.add(new Student());
  56. allStudent.add(new Student());
  57. // 班长
  58. IGroupLeader zhangBanzhang = new GroupLeader(allStudent);
  59. wangTeacher.command(zhangBanzhang);
  60. }
  61. }

减少对外的暴露 Man只需要一个咖啡机 其他操作内部解决

案例二:
分析:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,其类图如图 1 所示。
image.png

  1. public class LoDtest {
  2. public static void main(String[] args) {
  3. Agent agent = new Agent();
  4. agent.setStar(new Star("林心如"));
  5. agent.setFans(new Fans("粉丝韩丞"));
  6. agent.setCompany(new Company("中国传媒有限公司"));
  7. agent.meeting();
  8. agent.business();
  9. }
  10. }
  11. //经纪人
  12. class Agent {
  13. private Star myStar;
  14. private Fans myFans;
  15. private Company myCompany;
  16. public void setStar(Star myStar) {
  17. this.myStar = myStar;
  18. }
  19. public void setFans(Fans myFans) {
  20. this.myFans = myFans;
  21. }
  22. public void setCompany(Company myCompany) {
  23. this.myCompany = myCompany;
  24. }
  25. public void meeting() {
  26. System.out.println(myFans.getName() + "与明星" + myStar.getName() + "见面了。");
  27. }
  28. public void business() {
  29. System.out.println(myCompany.getName() + "与明星" + myStar.getName() + "洽淡业务。");
  30. }
  31. }
  32. //明星
  33. class Star {
  34. private String name;
  35. Star(String name) {
  36. this.name = name;
  37. }
  38. public String getName() {
  39. return name;
  40. }
  41. }
  42. //粉丝
  43. class Fans {
  44. private String name;
  45. Fans(String name) {
  46. this.name = name;
  47. }
  48. public String getName() {
  49. return name;
  50. }
  51. }
  52. //媒体公司
  53. class Company {
  54. private String name;
  55. Company(String name) {
  56. this.name = name;
  57. }
  58. public String getName() {
  59. return name;
  60. }
  61. }

2.8、合成复用原则Composite Reuse Principle

尽量不要使用继承关系

如果说仅仅是让类B去使用类A的方法,这时使用继承,会让A类和B类之间的耦合性增强
方式一:
image.png
方式二:
A 类聚合到B类
image.png
方式三:
A组合到B类中
image.png

3、23种设计模式

设计模式的分类
创建型模式:(描述怎样去创建一个对象,创建和使用分离)

  • 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式

结构型模式:(描述如何将类或对象安装某种类型组成更大的结构)

  • 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

行为型模式:(描述类和对象如何可以相互协作)

  • 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式

    3.1、单例模式

    核心作用:保证一个类只有一个实例,并且提供一个访问该实例的 全局访问点
    比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session 对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory就够,这是就会使用到单例模式。
    饿汉式单例模式:
    执行步骤:
    1) 构造器私有化 (防止 new )
    2) 类的内部创建对象
    3) 向外暴露一个静态的公共方法。getInstance
    4) 代码实现 ```java // 饿汉式单例 public class Hungry {

    // 可能会浪费空间 private byte[] data1 = new byte[10241024]; private byte[] data2 = new byte[10241024]; private byte[] data3 = new byte[10241024]; private byte[] data4 = new byte[10241024];

    // 单例模式核心思想:构造器私有 private Hungry(){

    } // 转载类的时候就完了实例化 一直存在 静态常量法 private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){

    1. return HUNGRY;

    }

}

  1. **优点**:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
  2. **缺点**:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。**如果从始至终从未使用过这个实例,则会造成内存的浪费**
  3. **静态代码块懒汉式**
  4. ```java
  5. // 饿汉式单例
  6. public class Hungry {
  7. // 可能会浪费空间
  8. private byte[] data1 = new byte[1024*1024];
  9. private byte[] data2 = new byte[1024*1024];
  10. private byte[] data3 = new byte[1024*1024];
  11. private byte[] data4 = new byte[1024*1024];
  12. // 单例模式核心思想:构造器私有
  13. private Hungry(){
  14. }
  15. // 静态代码块创建单例
  16. private static Hungry HUNGRY;
  17. static{
  18. HUNGRY = new Hungry();
  19. }
  20. public static Hungry getInstance(){
  21. return HUNGRY;
  22. }
  23. }

DCL懒汉式单例模式:

  1. // 懒汉式单例 线程安全的情况下
  2. public class LazyMan {
  3. private LazyMan() {
  4. System.out.println(Thread.currentThread().getName() + "ok");
  5. }
  6. private volatile static LazyMan lazyMan; // volatile 为了避免指令重排
  7. // 双重检测锁模式的懒汉式单例 DCL 懒汉式
  8. public static LazyMan getInstance() {
  9. if (lazyMan == null) {
  10. synchronized (LazyMan.class) {
  11. if (lazyMan == null) {
  12. lazyMan = new LazyMan();// 不是一个原子性操作
  13. /*
  14. 1、分配内存空间
  15. 2、执行构造方法,初始化对象
  16. 3、把这个对象指向这个空间
  17. 123
  18. 132 A
  19. B // 此时B线程进来会认为lazyman不为null
  20. // 直接返回 此时lazyman 还没有完成构造
  21. // 为了避免指令重排
  22. */
  23. }
  24. }
  25. }
  26. return lazyMan;
  27. }
  28. //
  29. // public static LazyMan getInstance() {
  30. // if (lazyMan == null) {
  31. // lazyMan = new LazyMan();
  32. // }
  33. // return lazyMan;
  34. // }
  35. // 单线程下确实单例ok,但是多线程并发
  36. public static void main(String[] args) {
  37. for (int i = 0; i < 10; i++) {
  38. new Thread(() -> {
  39. LazyMan.getInstance();
  40. }).start();
  41. }
  42. }
  43. }

优点:解决了线程不安全问题
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行 同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例, 直接return就行了。方法进行同步效率太低

反射 可以破环这种单例

  1. // 懒汉式单例
  2. // 道高一尺,魔高一丈
  3. public class LazyMan {
  4. private static boolean qinjiang = false;
  5. private LazyMan() {
  6. if(qinjiang == false){
  7. qinjiang=true;
  8. }else{
  9. throw new RuntimeException("不要试图使用反射破坏异常");
  10. }
  11. // synchronized (LazyMan.class){
  12. // if (lazyMan!=null){
  13. // throw new RuntimeException("不要试图使用反射破坏异常");
  14. // }
  15. // }
  16. // System.out.println(Thread.currentThread().getName() + "ok");
  17. }
  18. private volatile static LazyMan lazyMan; // volatile 为了避免指令重排多线程的情况下防止线程相互影响的
  19. // 双重检测锁模式的懒汉式单例 DCL 懒汉式
  20. public static LazyMan getInstance() {
  21. if (lazyMan == null) {
  22. synchronized (LazyMan.class) {
  23. if (lazyMan == null) {
  24. lazyMan = new LazyMan();// 不是一个原子性操作
  25. /*
  26. 1、分配内存空间
  27. 2、执行构造方法,初始化对象
  28. 3、把这个对象指向这个空间
  29. 123
  30. 132 A
  31. B // 此时B线程进来会认为lazyman不为null
  32. // 直接返回 此时lazyman 还没有完成构造
  33. // 为了避免指令重排
  34. */
  35. }
  36. }
  37. }
  38. return lazyMan;
  39. }
  40. // 单线程下确实单例ok,但是多线程并发
  41. public static void main(String[] args) throws Exception {
  42. // 反射 可以破环这种单例
  43. // LazyMan instance = LazyMan.getInstance();
  44. Field qinjiang = LazyMan.class.getDeclaredField("qinjiang");
  45. qinjiang.setAccessible(true);
  46. Constructor<LazyMan> declaredConstructor =LazyMan.class.getDeclaredConstructor(null);
  47. declaredConstructor.setAccessible(true);//无视私有构造器
  48. LazyMan instance=declaredConstructor.newInstance();
  49. qinjiang.set(instance,false);
  50. LazyMan instance2=declaredConstructor.newInstance();
  51. System.out.println(instance);
  52. System.out.println(instance2);
  53. }
  54. }

静态内部类

  1. // 静态内部类实现单例模式 不安全
  2. public class Holder {
  3. private Holder(){
  4. }
  5. public static Holder getInstance(){
  6. return InnerClass.HOLDER;
  7. }
  8. public static class InnerClass{
  9. private static final Holder HOLDER = new Holder();
  10. }
  11. }

单例不安全,因为有反射
优缺点:
1) 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
2) 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的 实例化。
3) 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
4) 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
5) 结论:推荐使用
枚举

  1. // enum 本身也是一个 class 类
  2. public enum EnumSingle {
  3. INSTANCE;
  4. public EnumSingle getInstance(){
  5. return INSTANCE;
  6. }
  7. }
  8. class Test{
  9. public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  10. EnumSingle instance1 = EnumSingle.INSTANCE;
  11. //Constructor<EnumSingle> declaredConstructor=EnumSingle.class.getDeclaredConstructor(null);
  12. // 枚举没有无参构造,只有有参构造
  13. Constructor<EnumSingle> declaredConstructor=EnumSingle.class.getDeclaredConstructor(String.class,int.class);
  14. declaredConstructor.setAccessible(true);
  15. EnumSingle instance2 = declaredConstructor.newInstance();
  16. // java.lang.NoSuchMethodException: com.kuang.single.EnumSingle.<init> 没有空参的构造方法
  17. // java.lang.IllegalArgumentException: Cannot reflectively create enum objects 反射不能破坏枚举的单例
  18. System.out.println(instance1);
  19. System.out.println(instance2);
  20. }
  21. }

枚举没有无参构造,只有有参构造
优缺点:
1) 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
2) 这种方式是Effective Java作者Josh Bloch 提倡的方式
3) 结论:推荐使用

单例模式在JDK应用的源码分析
1) 我们JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)
2) 代码分析+Debug源码+代码说明
image.png
单例模式注意事项和细节说明
1) 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2) 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
3) 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。

3.2、工厂模式

3.2.1、作用

  • 实现了创建者和调用者的分离
  • 详细分类:
    • 简单工厂模式:用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
    • 工厂方法模式:用来生产同一等级结构中的固定产品(支持增加任意产品)
    • 抽象工厂模式:围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。

我们常用的工厂模式主要使用简单工厂模式 抽象工厂模式比较少用

工厂设计模式的原则(OOP七大原则):

  • 开闭原则:一个软件的实体应当对扩展开放,对修改关闭
  • 依赖倒转原则:要针对接口编程,不要针对实现编程
  • 迪米特法则:只与你直接的朋友通信,而避免和陌生人通信

核心本质:

  • 实例化对象不使用new,用工厂方法代替 factory
  • 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦

    3.2.2、简单工厂模式(静态工厂模式)

    用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码) ```java public interface Car { void name(); }

public class WuLing implements Car{

  1. @Override
  2. public void name() {
  3. System.out.println("五菱宏光");
  4. }

}

public class Tesla implements Car{

  1. @Override
  2. public void name() {
  3. System.out.println("特斯拉");
  4. }

}

// 静态工厂模式 // 开闭原则 public class CarFactory {

  1. // 方法一: 不满足开闭原则
  2. public static Car getCar(String car){
  3. if(car.equals("wuling")){
  4. return new WuLing();
  5. }else if(car.equals("tesila")){
  6. return new Tesla();
  7. }else {
  8. return null;
  9. }
  10. }
  11. // 方法二:
  12. public static Car geyWuling(){
  13. return new WuLing();
  14. }
  15. public static Car geyTesla(){
  16. return new Tesla();
  17. }

}

public class Consumer { public static void main(String[] args) { // 接口,所有的实现类 // Car car = new WuLing(); // Car car1 = new Tesla();

  1. // 2、使用工厂创建
  2. Car car = CarFactory.getCar("wuling");
  3. Car car1 = CarFactory.getCar("tesila");
  4. car.name();
  5. car1.name();
  6. }

}

  1. 弊端:<br />增加一个新的产品,做不到不修改代码。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/25484710/1648367442717-99329dd8-956f-4587-9ac7-8cff6750a0b5.png#clientId=u5362e756-0f19-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=579&id=u80b4647a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=965&originWidth=1265&originalType=binary&ratio=1&rotation=0&showTitle=false&size=320503&status=done&style=none&taskId=u57669efa-cae4-4a47-9f45-a32d2355361&title=&width=758.5)<br />来一个披萨订购的demo
  2. ```java
  3. /**
  4. * 披萨接口
  5. */
  6. public abstract class Pizza {
  7. protected String name;
  8. // 不同的披萨原材料是不同的 因此做成抽象方法
  9. public abstract void prepare();
  10. public void bake(){
  11. System.out.println(name + " baking");
  12. }
  13. public void cut(){
  14. System.out.println(name + " cutting");
  15. }
  16. public void box(){
  17. System.out.println(name + " boxing");
  18. }
  19. public String getName() {
  20. return name;
  21. }
  22. public void setName(String name) {
  23. this.name = name;
  24. }
  25. }
  26. public class GreekPizza extends Pizza{
  27. @Override
  28. public void prepare() {
  29. System.out.println(" 给希腊披萨 准备原材料!");
  30. }
  31. }
  32. public class CheesePizza extends Pizza{
  33. @Override
  34. public void prepare() {
  35. System.out.println(" 给制作奶酪披萨 准备原材料!");
  36. }
  37. }
  38. /**
  39. 订购工厂
  40. **/
  41. public class OrderPizza {
  42. // 构造器
  43. public OrderPizza() {
  44. Pizza pizza = null;
  45. String orderType; // 订购披萨类型
  46. do {
  47. orderType = getType();
  48. if (orderType.equals("greek")){
  49. pizza = new GreekPizza();
  50. pizza.setName("greek");
  51. }else if (orderType.equals("cheese")){
  52. pizza = new CheesePizza();
  53. pizza.setName("cheese");
  54. }else {
  55. break;
  56. }
  57. // 输出披萨制作过程
  58. pizza.prepare();
  59. pizza.bake();
  60. pizza.cut();
  61. pizza.box();
  62. }while (true);
  63. }
  64. // 写一个方法 来获取用户希望订购的披萨种类
  65. private String getType(){
  66. Scanner scanner = new Scanner(System.in);
  67. String next = scanner.next();
  68. return next;
  69. }
  70. }
  71. /**
  72. * 客户端
  73. */
  74. public class PizzaStore {
  75. public static void main(String[] args) {
  76. new OrderPizza();
  77. }
  78. }

违反了设计模式的ocp原则,即对扩展开发 对修改关闭 当我们要增加新功能的时候要修改代码

修改一些代码:

  1. import java.util.Scanner;
  2. public class OrderPizza {
  3. // 定义一个简单工厂
  4. SimpleFactory simpleFactory;
  5. Pizza pizza = null;
  6. public void setSimpleFactory(SimpleFactory simpleFactory){
  7. String orderType = null;
  8. this.simpleFactory = simpleFactory;
  9. do {
  10. orderType = getType();
  11. pizza = simpleFactory.createPizza(orderType);
  12. if (pizza != null){
  13. pizza.setName(orderType);
  14. pizza.prepare();
  15. pizza.bake();
  16. pizza.cut();
  17. pizza.box();
  18. }
  19. }while (true);
  20. }
  21. // 写一个方法 来获取用户希望订购的披萨种类
  22. private String getType(){
  23. Scanner scanner = new Scanner(System.in);
  24. String next = scanner.next();
  25. return next;
  26. }
  27. }
  28. // 加一个管理全部pizza的工厂
  29. public class SimpleFactory {
  30. public Pizza createPizza(String orderType){
  31. Pizza pizza = null;
  32. System.out.println("使用了简单工厂模式");
  33. if (orderType.equals("greek")){
  34. pizza = new GreekPizza();
  35. pizza.setName("greek");
  36. }else if (orderType.equals("cheese")){
  37. pizza = new CheesePizza();
  38. pizza.setName("cheese");
  39. }
  40. return pizza;
  41. }
  42. }

3.2.3、工厂方法模式:

用来生产同一等级结构中的固定产品(支持增加任意产品)

  1. public interface Car {
  2. void name();
  3. }
  4. public class WuLing implements Car {
  5. @Override
  6. public void name() {
  7. System.out.println("五菱宏光");
  8. }
  9. }
  10. public class Tesla implements Car {
  11. @Override
  12. public void name() {
  13. System.out.println("特斯拉");
  14. }
  15. }
  16. // 工厂方法模式
  17. public interface CarFactory {
  18. Car getCar();
  19. }
  20. public class WulingFactory implements CarFactory{
  21. @Override
  22. public Car getCar() {
  23. return new WuLing();
  24. }
  25. }
  26. public class TeslaFactory implements CarFactory{
  27. @Override
  28. public Car getCar() {
  29. return new Tesla();
  30. }
  31. }
  32. public class Consumer {
  33. public static void main(String[] args) {
  34. Car car = new WulingFactory().getCar();
  35. Car car1 = new TeslaFactory().getCar();
  36. car.name();
  37. car1.name();
  38. Car car2 = new MoBaiFactory().getCar();
  39. car2.name();
  40. }
  41. }

image.png对比简单工厂模式
1、结构复杂度:simple>method
2、代码复杂度:simple>method
3、编程复杂度:simple>method
4、管理上的复杂度:simple>method
根据设计原则,使用工厂方法模式;根据实际业务,使用简单工厂模式

3.3、原型模式

3.3.1、模式介绍

  • 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  • 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()

在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
原型模式的定义和特点
原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。在生活中复制的例子非常多,这里不一一列举了。
优点:

  • Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

原型模式的缺点:

  • 需要为每一个类都配置一个 clone 方法
  • clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
  • 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。

原型模式结构和实现
由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。

3.3.2、 模式的结构

原型模式包含以下主要角色。

  1. 抽象原型类:规定了具体原型对象必须实现的接口。
  2. 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  3. 访问类:使用具体原型类中的 clone() 方法来复制新的对象。


image.png

3.3.3、模式的实现

原型模式的克隆分为浅克隆和深克隆。

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的的
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。其代码如下:

  1. //具体原型类
  2. class Realizetype implements Cloneable {
  3. Realizetype() {
  4. System.out.println("具体原型创建成功!");
  5. }
  6. public Object clone() throws CloneNotSupportedException {
  7. System.out.println("具体原型复制成功!");
  8. return (Realizetype) super.clone();
  9. }
  10. }
  11. //原型模式的测试类
  12. public class PrototypeTest {
  13. public static void main(String[] args) throws CloneNotSupportedException {
  14. Realizetype obj1 = new Realizetype();
  15. Realizetype obj2 = (Realizetype) obj1.clone();
  16. System.out.println("obj1==obj2?" + (obj1 == obj2));
  17. }
  18. }

实例:

  1. public class Sheep implements Cloneable{
  2. private String name;
  3. private int age;
  4. private String color;
  5. public Sheep(String name, int age, String color) {
  6. super();
  7. this.name = name;
  8. this.age = age;
  9. this.color = color;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public int getAge() {
  18. return age;
  19. }
  20. public void setAge(int age) {
  21. this.age = age;
  22. }
  23. public String getColor() {
  24. return color;
  25. }
  26. public void setColor(String color) {
  27. this.color = color;
  28. }
  29. @Override
  30. public String toString() {
  31. return "Sheep [name=" + name + ", age=" + age + ", color=" + color + "]";
  32. }
  33. @Override
  34. protected Object clone() throws CloneNotSupportedException {
  35. return super.clone();
  36. }
  37. }

传统方法(不适用原型模式)

  1. public static void main(String[] args) {
  2. Sheep sheep = new Sheep("tom", 1, "白色");
  3. Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
  4. System.out.println(sheep.hashCode());// 1735600054
  5. System.out.println(sheep2.hashCode());// 21685669
  6. }

原型模式

  1. public static void main(String[] args) throws CloneNotSupportedException {
  2. Sheep sheep = new Sheep("tom", 1, "白色");
  3. Sheep cloneSheep = (Sheep) sheep.clone();
  4. System.out.println(sheep.hashCode());// 1735600054
  5. System.out.println(cloneSheep.hashCode());// 21685669
  6. }

通过上述两种方式的对比,我们发现不管使用哪种方式,拷贝的方式都是深拷贝,那对于对象中的属性对象是什么拷贝呢?向 Sheep类中加入一个对象属性

  1. private String name;
  2. private int age;
  3. private String color;
  4. public Sheep friend; //是对象, 克隆是会如何处理
  5. .............
  1. public static void main(String[] args) throws CloneNotSupportedException {
  2. Sheep sheep = new Sheep("tom", 1, "白色");
  3. sheep.friend = new Sheep("jerry", 2, "黑色");
  4. Sheep cloneSheep = (Sheep) sheep.clone();
  5. // 哈希值:1735600054 sheep.friend 21685669
  6. System.out.println(sheep.hashCode()+" sheep.friend"+" "+sheep.friend.hashCode());
  7. // 哈希值:2133927002 cloneSheep.friend 21685669
  8. System.out.println(cloneSheep.hashCode()+" cloneSheep.friend"+" "+cloneSheep.friend.hashCode());
  9. }

经测试,我们发现,对于类中属性对象,采用的是浅拷贝方式,那有什么方法能让他实现深拷贝呢?有下面两种方法

  1. 通过重写clone()方法实现
  2. 通过序列化实现

一、通过重写clone()方法实现深拷贝

  1. 修改上述 Sheep类的克隆方法 ```java @Override protected Sheep clone() throws CloneNotSupportedException {
    1. Sheep sheep = null;
    2. // 先克隆Sheep对象
    3. sheep = (Sheep) super.clone();
    4. // 再克隆Sheep中的friend对象
    5. sheep.friend = friend.getclone();
    6. return sheep;
    } // 为对象中的对象即friend提供克隆方法 protected Sheep getclone() throws CloneNotSupportedException {
    1. return (Sheep) super.clone();
    }
  1. ```java
  2. public static void main(String[] args) throws CloneNotSupportedException {
  3. Sheep sheep = new Sheep("tom", 1, "白色");
  4. sheep.friend = new Sheep("jerry", 2, "黑色");
  5. Sheep cloneSheep = (Sheep) sheep.clone();
  6. // 1735600054 sheep.friend 21685669
  7. System.out.println(sheep.hashCode()+" sheep.friend"+" "+sheep.friend.hashCode());
  8. // 2133927002 cloneSheep.friend 1836019240
  9. System.out.println(cloneSheep.hashCode()+" cloneSheep.friend"+" "+cloneSheep.friend.hashCode());
  10. }

3.3.4、深拷贝

深拷贝通过多种方法实现
方式一: 通过clone方法实现深拷贝

  1. public class DeepProtoType implements Serializable, Cloneable {
  2. public String name;
  3. public DeepCloneableTarget deepCloneableTarget;
  4. public DeepProtoType() {
  5. super();
  6. }
  7. // 深拷贝 方式1 使用clone方法
  8. @Override
  9. protected Object clone() throws CloneNotSupportedException {
  10. Object deep = null;
  11. // 这里完成基本数据类型和String的克隆
  12. deep = super.clone();
  13. // 对引用类型进行单独处理
  14. DeepProtoType deepProtoType = (DeepProtoType) deep;
  15. deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
  16. return deepProtoType;
  17. }
  18. }

方式二: 通过序列化来实现深拷贝

  1. public class DeepProtoType implements Serializable, Cloneable {
  2. public String name;
  3. public DeepCloneableTarget deepCloneableTarget;
  4. public DeepProtoType() {
  5. super();
  6. }
  7. // 方式二 利用对象的 序列化来实现 推荐使用
  8. public Object deepClone(){
  9. // 创建流对象
  10. ByteArrayOutputStream bos = null;
  11. ObjectOutputStream oos = null;
  12. ByteArrayInputStream bis = null;
  13. ObjectInputStream ois = null;
  14. DeepProtoType deepProtoType = null;
  15. try {
  16. // 序列化
  17. bos = new ByteArrayOutputStream();
  18. oos = new ObjectOutputStream(bos);
  19. oos.writeObject(this); // 对当前的对象以对象流的方式输出
  20. // 反序列化
  21. bis = new ByteArrayInputStream(bos.toByteArray());
  22. ois = new ObjectInputStream(bis);
  23. deepProtoType = (DeepProtoType) ois.readObject();
  24. } catch (Exception e) {
  25. e.printStackTrace();
  26. }finally {
  27. try {
  28. bos.close();
  29. oos.close();
  30. bis.close();
  31. ois.close();
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. return deepProtoType;
  37. }
  38. }

3.3.5、在Spring框架的使用

1) Spring中原型bean的创建,就是原型模式的应用
2) 代码分析+Debug源码
image.png这里的scope是指定创建bean的方法 默认是单例模式,这里指定的是原型模式prototype

3.4、建造者模式

3.4.1、建造者概述

在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。例如,计算机是由 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。

生活中这样的例子很多,如游戏中的不同角色,其性别、个性、能力、脸型、体型、服装、发型等特性都有所差异;还有汽车中的方向盘、发动机、车架、轮胎等部件也多种多样;每封电子邮件的发件人、收件人、主题、内容、附件等内容也各不相同。

以上所有这些产品都是由多个部件构成的,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无法用前面介绍的工厂模式描述,只有建造者模式可以很好地描述该类产品的创建。

建造者(Builder)模式的定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。

该模式的主要优点如下:

  1. 封装性好,构建和表示分离。
  2. 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
  3. 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。


其缺点如下:

  1. 产品的组成部分必须相同,这限制了其使用范围。
  2. 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。


建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。

3.4.2、模式结构

建造者(Builder)模式的主要角色如下。

  1. 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
  2. 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
  3. 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
  4. 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

image.png

3.4.3、建造者含指挥

image.png

角色分析
image.png
代码实现

  1. package com.kuang.builder;
  2. //产品,房子
  3. public class Product {
  4. private String builderA;
  5. private String builderB;
  6. private String builderC;
  7. private String builderD;
  8. public String getBuilderA() {
  9. return builderA;
  10. }
  11. public void setBuilderA(String builderA) {
  12. this.builderA = builderA;
  13. }
  14. public String getBuilderB() {
  15. return builderB;
  16. }
  17. public void setBuilderB(String builderB) {
  18. this.builderB = builderB;
  19. }
  20. public String getBuilderC() {
  21. return builderC;
  22. }
  23. public void setBuilderC(String builderC) {
  24. this.builderC = builderC;
  25. }
  26. public String getBuilderD() {
  27. return builderD;
  28. }
  29. public void setBuilderD(String builderD) {
  30. this.builderD = builderD;
  31. }
  32. @Override
  33. public String toString() {
  34. return "Product{" +
  35. "builderA='" + builderA + '\'' +
  36. ", builderB='" + builderB + '\'' +
  37. ", builderC='" + builderC + '\'' +
  38. ", builderD='" + builderD + '\'' +
  39. '}';
  40. }
  41. }

Builder

  1. package com.kuang.builder;
  2. //抽象的建造者:方法
  3. public abstract class Builder {
  4. abstract void builderA();//地基
  5. abstract void builderB();//钢筋工程
  6. abstract void builderC();//铺电线
  7. abstract void builderD();//粉刷
  8. //完工:得到产品
  9. abstract Product getProduct();
  10. }

Worker

  1. package com.kuang.builder;
  2. //具体的建造者:工人
  3. public class Worker extends Builder {
  4. private Product product;
  5. public Worker() {
  6. product = new Product();
  7. }
  8. @Override
  9. void builderA() {
  10. product.setBuilderA("地基");
  11. System.out.println("地基");
  12. }
  13. @Override
  14. void builderB() {
  15. product.setBuilderB("钢筋工程");
  16. System.out.println("钢筋工程");
  17. }
  18. @Override
  19. void builderC() {
  20. product.setBuilderC("铺电线");
  21. System.out.println("铺电线");
  22. }
  23. @Override
  24. void builderD() {
  25. product.setBuilderD("粉刷");
  26. System.out.println("粉刷");
  27. }
  28. @Override
  29. Product getProduct() {
  30. return product;
  31. }
  32. }

Director

  1. package com.kuang.builder;
  2. //指挥:核心,负责指挥构建一个工程,工程如何构建,由它决定
  3. public class Director {
  4. //指挥工人按照顺序建房子
  5. public Product build(Builder builder){
  6. builder.builderA();
  7. builder.builderB();
  8. builder.builderC();
  9. builder.builderD();
  10. return builder.getProduct();
  11. }
  12. }

Test

  1. package com.kuang.builder;
  2. public class Test {
  3. public static void main(String[] args) {
  4. //指挥
  5. Director director = new Director();
  6. //指挥具体的工人完成产品
  7. Product build = director.build(new Worker());
  8. System.out.println(build.toString());
  9. }
  10. }

1、建造者模式的意图和适用场景(拓展)

建造者模式唯一区别于工厂模式的是针对复杂对象的创建。也就是说,如果创建简单对象,通常都是使用工厂模式进行创建,而如果创建复杂对象,就可以考虑使用建造者模式。

当需要创建的产品具备复杂创建过程时,可以抽取出共性创建过程,然后交由具体实现类自定义创建流程,使得同样的创建行为可以生产出不同的产品,分离了创建与表示,使创建产品的灵活性大大增加。

建造者模式主要适用于以下应用场景:

  • 相同的方法,不同的执行顺序,产生不同的结果。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
  • 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
  • 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。
    2、建造者的参与者(拓展)
    image.png
    3、举例说明:设计房屋(拓展)
    image.png

3.4.4、建造者无指挥

image.png

Product

  1. package com.kuang.builder.demo02;
  2. //产品:套餐
  3. public class Product {
  4. private String BuildA ="汉堡";
  5. private String BuildB ="可乐";
  6. private String BuildC ="薯条";
  7. private String BuildD ="甜点";
  8. public String getBuildA() {
  9. return BuildA;
  10. }
  11. public void setBuildA(String buildA) {
  12. BuildA = buildA;
  13. }
  14. public String getBuildB() {
  15. return BuildB;
  16. }
  17. public void setBuildB(String buildB) {
  18. BuildB = buildB;
  19. }
  20. public String getBuildC() {
  21. return BuildC;
  22. }
  23. public void setBuildC(String buildC) {
  24. BuildC = buildC;
  25. }
  26. public String getBuildD() {
  27. return BuildD;
  28. }
  29. public void setBuildD(String buildD) {
  30. BuildD = buildD;
  31. }
  32. @Override
  33. public String toString() {
  34. return "Product{" +
  35. "BuildA='" + BuildA + '\'' +
  36. ", BuildB='" + BuildB + '\'' +
  37. ", BuildC='" + BuildC + '\'' +
  38. ", BuildD='" + BuildD + '\'' +
  39. '}';
  40. }
  41. }

Builder

  1. package com.kuang.builder.demo02;
  2. //建造者
  3. public abstract class Builder {
  4. abstract Builder BuildA(String msg);//"汉堡";
  5. abstract Builder BuildB(String msg);//"可乐";
  6. abstract Builder BuildC(String msg);//"薯条";
  7. abstract Builder BuildD(String msg);//"甜点";
  8. abstract Product getProduct();
  9. }

Worker

  1. package com.kuang.builder.demo02;
  2. public class Worker extends Builder {
  3. private Product product;
  4. public Worker() {
  5. product = new Product();
  6. }
  7. @Override
  8. Builder BuildA(String msg) {
  9. product.setBuildA(msg);
  10. return this;
  11. }
  12. @Override
  13. Builder BuildB(String msg) {
  14. product.setBuildB(msg);
  15. return this;
  16. }
  17. @Override
  18. Builder BuildC(String msg) {
  19. product.setBuildC(msg);
  20. return this;
  21. }
  22. @Override
  23. Builder BuildD(String msg) {
  24. product.setBuildD(msg);
  25. return this;
  26. }
  27. @Override
  28. Product getProduct() {
  29. return product;
  30. }
  31. }

Test

  1. package com.kuang.builder.demo02;
  2. public class Test {
  3. public static void main(String[] args) {
  4. //服务员
  5. Worker worker = new Worker();
  6. //可以按默认走,也可以自由组合
  7. Product product = worker.BuildA("全家桶").BuildB("雪碧").getProduct();
  8. System.out.println(product.toString());
  9. }
  10. }
  1. Product{BuildA='全家桶', BuildB='雪碧', BuildC='薯条', BuildD='甜点'}

3.4.5、建造者模式和工厂模式的区别

通过前面的学习,我们已经了解了建造者模式,那么它和工厂模式有什么区别呢?

  • 建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。
  • 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样
  • 关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。
  • 建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。

3.4.6、建造者模式在JDK的应用

建造者模式在JDK中的StringBuilder中使用了建造者模式的方法。
image.png
image.png
Builder的实现如上图所示。
image.png

  • Appendable定义了多个append方法,属于抽象方法,就是抽象建造者相当于上面的Builder类。
  • AbstractStringBuilder实现了Appendable接口方法,这里已经是建造者了不能实例化 相当于worker
  • StringBuilder充当指挥着角色,同时充当具体的建造者,建造方法的实现是有AbstractStringBuilder的完成。

这个可能和标准的建造者模式不太一样,思想大同小异。

3.5、适配器模式

3.5.1、适配器模式概述

在现实生活中,经常出现两个对象因接口不兼容而不能在一起工作的实例,这时需要第三者进行适配。例如,讲中文的人同讲英文的人对话时需要一个翻译,用直流电的笔记本电脑接交流电源时需要一个电源适配器,用计算机访问照相机的 SD 内存卡时需要一个读卡器等。

在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题。

image.png
适配器理解:
image.png

3.5.2、适配器模式优缺点

image.png
适配器模式(Adapter)的定义如下:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

该模式的主要优点如下。

  • 客户端通过适配器可以透明地调用目标接口。
  • 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
  • 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
  • 在很多业务场景中符合开闭原则。


其缺点是:

  • 适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
  • 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

3.5.3、模式的结构

适配器模式(Adapter)包含以下主要角色。

  1. 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  2. 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  3. 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

image.png

接口

3.5.4、类适配器模式

的代码如下。

  1. package adapter;
  2. //目标接口
  3. interface Target
  4. {
  5. public void request();
  6. }
  7. //适配者接口
  8. class Adaptee
  9. {
  10. public void specificRequest()
  11. {
  12. System.out.println("适配者中的业务代码被调用!");
  13. }
  14. }
  15. //类适配器类 // 这里直接继承了适配器
  16. class ClassAdapter extends Adaptee implements Target
  17. {
  18. public void request()
  19. {
  20. specificRequest();
  21. }
  22. }
  23. //客户端代码
  24. public class ClassAdapterTest
  25. {
  26. public static void main(String[] args)
  27. {
  28. System.out.println("类适配器模式测试:");
  29. Target target = new ClassAdapter();
  30. target.request();
  31. }
  32. }

电压转接

  1. // 需要的方法,适配器类
  2. public class Voltage220V {
  3. public int output220V(){
  4. int src = 220;
  5. System.out.println("电压:"+src+"伏");
  6. return src;
  7. }
  8. }
  9. // 目标接口
  10. public interface IVoltage5V {
  11. // 输出5伏
  12. public int output5V();
  13. }
  14. /**
  15. * 适配器就是一个转接口 需要实现多个方法
  16. */
  17. public class VoltageAdapter extends Voltage220V implements IVoltage5V{
  18. @Override
  19. public int output5V() {
  20. // 获取到220v的电压
  21. int src = output220V();
  22. int dstV = src / 44; // 转成5伏
  23. return dstV;
  24. }
  25. }
  26. public class Phone {
  27. // 充电的方法
  28. public void charging(IVoltage5V iVoltage5V){
  29. if (iVoltage5V.output5V() == 5){
  30. System.out.println("电压为5伏,可以充电!!");
  31. }else if (iVoltage5V.output5V() > 5){
  32. System.out.println("电压过高,不能充电!!");
  33. }
  34. }
  35. }
  36. public class Client {
  37. public static void main(String[] args) {
  38. System.out.println("=====类适配器模式=====");
  39. Phone phone = new Phone();
  40. // 只需要一个适配器
  41. phone.charging(new VoltageAdapter());
  42. }
  43. }

3.5.5、对象适配器模式

代码如下。

  1. package adapter;
  2. //对象适配器类
  3. class ObjectAdapter implements Target
  4. {
  5. private Adaptee adaptee;
  6. public ObjectAdapter(Adaptee adaptee)
  7. {
  8. this.adaptee=adaptee;
  9. }
  10. public void request()
  11. {
  12. adaptee.specificRequest();
  13. }
  14. }
  15. //客户端代码
  16. public class ObjectAdapterTest
  17. {
  18. public static void main(String[] args)
  19. {
  20. System.out.println("对象适配器模式测试:");
  21. Adaptee adaptee = new Adaptee();
  22. Target target = new ObjectAdapter(adaptee);
  23. target.request();
  24. }
  25. }
  1. package adapter;
  2. // 待使用的类
  3. //目标:发动机
  4. interface Motor
  5. {
  6. public void drive();
  7. }
  8. // 定义某种发动机对象(相当于Motor的必须类,只有适配器存在这个类才能实现)
  9. //适配者1:电能发动机
  10. class ElectricMotor
  11. {
  12. public void electricDrive()
  13. {
  14. System.out.println("电能发动机驱动汽车!");
  15. }
  16. }
  17. //适配者2:光能发动机
  18. class OpticalMotor
  19. {
  20. public void opticalDrive()
  21. {
  22. System.out.println("光能发动机驱动汽车!");
  23. }
  24. }
  25. // 下面两个相当于继承了发动机的功能但是只需要插入一个特定的发动机对象
  26. // 适配器实现发动机的所有功能但是差一个发动机对象驱动 所以需要组合一个发动机对象
  27. //电能适配器
  28. class ElectricAdapter implements Motor
  29. {
  30. private ElectricMotor emotor;
  31. public ElectricAdapter()
  32. {
  33. emotor=new ElectricMotor();
  34. }
  35. public void drive()
  36. {
  37. emotor.electricDrive();
  38. }
  39. }
  40. //光能适配器
  41. class OpticalAdapter implements Motor
  42. {
  43. private OpticalMotor omotor;
  44. public OpticalAdapter()
  45. {
  46. omotor=new OpticalMotor();
  47. }
  48. public void drive()
  49. {
  50. omotor.opticalDrive();
  51. }
  52. }
  53. //客户端代码
  54. public class MotorAdapterTest
  55. {
  56. public static void main(String[] args)
  57. {
  58. System.out.println("适配器模式测试:");
  59. Motor motor=(Motor)ReadXML.getObject();
  60. motor.drive();
  61. }
  62. }

对象适配器就是不会继承类,解决兼容问题,而是改进进行聚合来实现。
电压变压实例

  1. public class VoltageAdapter implements IVoltage5V {
  2. private Voltage220V voltage220V;
  3. @Override
  4. public int output5V() {
  5. // 获取到220v的电压
  6. int src = voltage220V.output220V();
  7. int dstV = src / 44; // 转成5伏
  8. return dstV;
  9. }
  10. }
  11. // 就将适配器进行修改 不再泛化 而是进行聚合 聚合一个属性来实现

下面可以进行进行构造器来实现依赖

  1. public class VoltageAdapter implements IVoltage5V {
  2. private Voltage220V voltage220V;
  3. @Override
  4. public int output5V() {
  5. // 获取到220v的电压
  6. int dstV = 0;
  7. if (voltage220V != null){
  8. int src = voltage220V.output220V();
  9. dstV = src / 44; // 转成5伏
  10. }
  11. return dstV;
  12. }
  13. public VoltageAdapter(Voltage220V voltage220V) {
  14. this.voltage220V = voltage220V;
  15. }
  16. }

3.5.6、接口适配器模式

3.5.7、适配器在框架SpringMVC使用

1) SpringMvc中的HandlerAdapter, 就使用了适配器模式
2) SpringMVC处理请求的流程回顾
3) 使用HandlerAdapter 的原因分析:
可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样违背了OCP原则。
image.png
Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类
• 适配器代替controller执行相应的方法
• 扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了,
这就是设计模式的力量

3.6、桥接模式

3.6.1、桥接模式的概述

Bridge在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。

当然,这样的例子还有很多,如不同颜色和字体的文字、不同品牌和功率的汽车、不同性别和职业的男女、支持不同平台和不同文件格式的媒体播放器等。如果用桥接模式就能很好地解决这些问题。
image.png
image.png
image.png

3.6.2、桥接模式的优缺点

优点和缺点
桥接(Bridge)模式的定义如下:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

通过上面的讲解,我们能很好的感觉到桥接模式遵循了里氏替换原则和依赖倒置原则,最终实现了开闭原则,对修改关闭,对扩展开放。这里将桥接模式的优缺点总结如下。

桥接(Bridge)模式的优点是:

  • 抽象与实现分离,扩展能力强
  • 符合开闭原则
  • 符合合成复用原则
  • 其实现细节对客户透明


缺点是:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。
image.png

3.6.4、桥接模式的结构

桥接(Bridge)模式包含以下主要角色。

  1. 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
  2. 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  3. 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
  4. 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。

image.png

3.6.5、代码实现

  1. package bridge;
  2. /**
  3. 桥接模式就是对其进行分类
  4. 下面我如果一个电子产品来分析:
  5. 1、实现存在电脑、手表等分为:台式、笔记本、手表;作为一个Abstraction抽象类(电子产品大类)
  6. 而RefinedAbstraction扩展抽象类就是电子产品里的分类电脑、手表等去继承;
  7. 2、而实现类就是对这里电子产品进行品牌分类 一样的道理;一个实现类 Implementor品牌接口
  8. 而Concrete Implementor具体实现化对象就是各种品牌;
  9. 3、扩展抽象类里组合一个品牌实现类;只需要将电子抽象实现类和接口实现类组合就是一个目标对象;
  10. **/
  11. public class BridgeTest {
  12. public static void main(String[] args) {
  13. Implementor imple = new ConcreteImplementorA();
  14. Abstraction abs = new RefinedAbstraction(imple);
  15. abs.Operation();
  16. }
  17. }
  18. //实现化角色
  19. interface Implementor {
  20. public void OperationImpl();
  21. }
  22. //具体实现化角色
  23. class ConcreteImplementorA implements Implementor {
  24. public void OperationImpl() {
  25. System.out.println("具体实现化(Concrete Implementor)角色被访问");
  26. }
  27. }
  28. //抽象化角色
  29. // 相当于电脑对象
  30. abstract class Abstraction {
  31. // 该对象为品牌类
  32. protected Implementor imple;
  33. // 品牌类的实例化
  34. protected Abstraction(Implementor imple) {
  35. this.imple = imple;
  36. }
  37. public abstract void Operation();
  38. }
  39. //扩展抽象化角色 具体电脑实现类(电脑类别)
  40. class RefinedAbstraction extends Abstraction {
  41. protected RefinedAbstraction(Implementor imple) {
  42. super(imple);
  43. }
  44. public void Operation() {
  45. System.out.println("扩展抽象化(Refined Abstraction)角色被访问");
  46. imple.OperationImpl();
  47. }
  48. }

实现桥接模式解决手机问题
image.png

  1. /**
  2. * 接口类
  3. */
  4. public interface Brand {
  5. void open();
  6. void close();
  7. void call();
  8. }
  9. public class Vivo implements Brand {
  10. @Override
  11. public void open() {
  12. System.out.println("Vivo手机开机");
  13. }
  14. @Override
  15. public void close() {
  16. System.out.println("Vivo手机关机");
  17. }
  18. @Override
  19. public void call() {
  20. System.out.println("Vivo手机打电话");
  21. }
  22. }
  23. public class XiaoMi implements Brand {
  24. @Override
  25. public void open() {
  26. System.out.println("小米手机开机");
  27. }
  28. @Override
  29. public void close() {
  30. System.out.println("小米手机关机");
  31. }
  32. @Override
  33. public void call() {
  34. System.out.println("小米手机打电话");
  35. }
  36. }
  37. /**
  38. * 抽象类 组合
  39. */
  40. public abstract class Phone {
  41. // 组合品牌
  42. private Brand brand;
  43. public Phone(Brand brand) {
  44. this.brand = brand;
  45. }
  46. protected void open(){
  47. this.brand.open();
  48. }
  49. protected void close(){
  50. this.brand.close();
  51. }
  52. protected void call(){
  53. this.brand.call();
  54. }
  55. }
  56. /**
  57. * 抽象类继承 具体继承类
  58. */
  59. public class FoldedPhone extends Phone{
  60. public FoldedPhone(Brand brand) {
  61. super(brand);
  62. }
  63. public void open(){
  64. super.open();
  65. System.out.println("折叠样式手机开机");
  66. }
  67. public void close(){
  68. super.close();
  69. System.out.println("折叠样式手机关机");
  70. }
  71. public void call(){
  72. super.call();
  73. System.out.println("折叠样式手机打电话");
  74. }
  75. }

实例
【例1】用桥接(Bridge)模式模拟女士皮包的选购。
分析:女士皮包有很多种,可以按用途分、按皮质分、按品牌分、按颜色分、按大小分等,存在多个维度的变化,所以采用桥接模式来实现女士皮包的选购比较合适。

本实例按用途分可选钱包(Wallet)和挎包(HandBag),按颜色分可选黄色(Yellow)和红色(Red)。可以按两个维度定义为颜色类和包类。

颜色类(Color)是一个维度,定义为实现化角色,它有两个具体实现化角色:黄色和红色,通过 getColor() 方法可以选择颜色;包类(Bag)是另一个维度,定义为抽象化角色,它有两个扩展抽象化角色:挎包和钱包,它包含了颜色类对象,通过 getName() 方法可以选择相关颜色的挎包和钱包。

客户类通过 ReadXML 类从 XML 配置文件中获取包信息,并把选到的产品通过窗体显示出现,图 2 所示是其结构图。
image.png

  1. package bridge;
  2. import org.w3c.dom.NodeList;
  3. import javax.swing.*;
  4. import javax.xml.parsers.DocumentBuilder;
  5. import javax.xml.parsers.DocumentBuilderFactory;
  6. import java.awt.*;
  7. public class BagManage {
  8. public static void main(String[] args) {
  9. Color color;
  10. Bag bag;
  11. color = (Color) ReadXML.getObject("color");
  12. bag = (Bag) ReadXML.getObject("bag");
  13. bag.setColor(color);
  14. String name = bag.getName();
  15. show(name);
  16. }
  17. public static void show(String name) {
  18. JFrame jf = new JFrame("桥接模式测试");
  19. Container contentPane = jf.getContentPane();
  20. JPanel p = new JPanel();
  21. JLabel l = new JLabel(new ImageIcon("src/bridge/" + name + ".jpg"));
  22. p.setLayout(new GridLayout(1, 1));
  23. p.setBorder(BorderFactory.createTitledBorder("女士皮包"));
  24. p.add(l);
  25. contentPane.add(p, BorderLayout.CENTER);
  26. jf.pack();
  27. jf.setVisible(true);
  28. jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  29. }
  30. }
  31. //实现化角色:颜色
  32. interface Color {
  33. String getColor();
  34. }
  35. //具体实现化角色:黄色
  36. class Yellow implements Color {
  37. public String getColor() {
  38. return "yellow";
  39. }
  40. }
  41. //具体实现化角色:红色
  42. class Red implements Color {
  43. public String getColor() {
  44. return "red";
  45. }
  46. }
  47. //抽象化角色:包
  48. abstract class Bag {
  49. protected Color color;
  50. public void setColor(Color color) {
  51. this.color = color;
  52. }
  53. public abstract String getName();
  54. }
  55. //扩展抽象化角色:挎包
  56. class HandBag extends Bag {
  57. public String getName() {
  58. return color.getColor() + "HandBag";
  59. }
  60. }
  61. //扩展抽象化角色:钱包
  62. class Wallet extends Bag {
  63. public String getName() {
  64. return color.getColor() + "Wallet";
  65. }
  66. }
  67. package bridge;
  68. import javax.xml.parsers.*;
  69. import org.w3c.dom.*;
  70. import java.io.*;
  71. class ReadXML {
  72. public static Object getObject(String args) {
  73. try {
  74. DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
  75. DocumentBuilder builder = dFactory.newDocumentBuilder();
  76. Document doc;
  77. doc = builder.parse(new File("src/bridge/config.xml"));
  78. NodeList nl = doc.getElementsByTagName("className");
  79. Node classNode = null;
  80. if (args.equals("color")) {
  81. classNode = nl.item(0).getFirstChild();
  82. } else if (args.equals("bag")) {
  83. classNode = nl.item(1).getFirstChild();
  84. }
  85. String cName = "bridge." + classNode.getNodeValue();
  86. Class<?> c = Class.forName(cName);
  87. Object obj = c.newInstance();
  88. return obj;
  89. } catch (Exception e) {
  90. e.printStackTrace();
  91. return null;
  92. }
  93. }
  94. }

3.6.6、应用场景

当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。

桥接模式通常适用于以下场景。

  1. 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  2. 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
  3. 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。


桥接模式的一个常见使用场景就是替换继承。我们知道,继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。

因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。

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

在软件开发中,有时桥接(Bridge)模式可与适配器模式联合使用。当桥接(Bridge)模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来,其具体结构图如图 5 所示。
image.png

总结:适配器模式和桥接模式的区别
共同点:
桥接和适配器都是让两个东西配合工作
不同点:
出发点不同。
1)适配器:改变已有的两个接口,让他们相容。
2)桥接模式:分离抽象化和实现,使两者的接口可以不同,目的是分离。

  1. 所以说,如果你拿到两个已有模块,想让他们同时工作,那么你使用的适配器。<br /> 如果你还什么都没有,但是想分开实现,那么桥接是一个选择。
  2. 桥接是先有桥,才有两端的东西<br /> 适配是先有两边的东西,才有适配器
  3. 桥接是在桥好了之后,两边的东西还可以变化。
  4. 例如游戏手柄,就象个桥,它把你的任何操作转化成指令。<br /> (虽然,你可以任何操作组合,但是你的操作脱不开山下左右,a,b,选择 ,确定)<br /> JRE本身就是一个就是一个很好的桥,先写好在linux上执行的Jre,再写好可以在windows下执行的JRE,<br /> 这样无论什么样的Java程序,只要配和相应的Jre就能在Linux或者Windows上运行.<br /> 两个Jre并没有限定你写什么样的程序,但要求你必须用Java来写。

适配器是定义了新接口,然后才与旧接口进行适配,即先接口后关系。
桥接模式是定义了一个(即两个接口之间的关系),然后通过每个接口的多个实现的不同组合达到其灵活性的目的,即先关系后组合。

3.6.7、桥接模式在JDBC的源码

桥接模式在JDBC的源码剖析
1) Jdbc 的 Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类
2) 代码分析+Debug源码
image.png

3.6.8、注意问题

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

3.7、装饰者模式

3.7.1、装饰者模式概述

装饰者模式在Java中的典型应用就是IO流

装饰者模式又名包装(Wrapper)模式。装饰者模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
装饰者模式动态地将责任附加到对象身上。若要扩展功能,装饰者提供

3.7.2、装饰者模式的结构

装饰者模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰者模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。
装饰者模式的类图如下:
image.png
在装饰模式中的角色有:

  • 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
  • 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。
  • 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
  • 具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。

抽象构件角色

  1. public interface Component {
  2. public void sampleOperation();
  3. }

具体构件角色

  1. public class ConcreteComponent implements Component {
  2. @Override
  3. public void sampleOperation() {
  4. // 写相关的业务代码
  5. }
  6. }

装饰角色

  1. public class Decorator implements Component{
  2. private Component component;
  3. public Decorator(Component component){
  4. this.component = component;
  5. }
  6. @Override
  7. public void sampleOperation() {
  8. // 委派给构件
  9. component.sampleOperation();
  10. }
  11. }

具体装饰角色

  1. public class ConcreteDecoratorA extends Decorator {
  2. public ConcreteDecoratorA(Component component) {
  3. super(component);
  4. }
  5. @Override
  6. public void sampleOperation() {
  7. // 写相关的业务代码
  8.    super.sampleOperation();
  9. // 写相关的业务代码
  10. }
  11. }

3.7.3、代码实现

齐天大圣的例子
孙悟空有七十二般变化,他的每一种变化都给他带来一种附加的本领。他变成鱼儿时,就可以到水里游泳;他变成鸟儿时,就可以在天上飞行。
本例中,Component的角色便由鼎鼎大名的齐天大圣扮演;ConcreteComponent的角色属于大圣的本尊,就是猢狲本人;Decorator的角色由大圣的七十二变扮演。而ConcreteDecorator的角色便是鱼儿、鸟儿等七十二般变化。
image.png
抽象构件角色“齐天大圣”接口定义了一个move()方法,这是所有的具体构件类和装饰类必须实现的。

  1. //大圣的尊号
  2. public interface TheGreatestSage {
  3. public void move();
  4. }

具体构件角色“大圣本尊”猢狲类

  1. public class Monkey implements TheGreatestSage {
  2. @Override
  3. public void move() {
  4. //代码
  5. System.out.println("Monkey Move");
  6. }
  7. }

抽象装饰角色“七十二变”

  1. public class Change implements TheGreatestSage {
  2. private TheGreatestSage sage;
  3. public Change(TheGreatestSage sage){
  4. this.sage = sage;
  5. }
  6. @Override
  7. public void move() {
  8. // 代码
  9. sage.move();
  10. }
  11. }

具体装饰角色“鱼儿”

  1. public class Fish extends Change {
  2. public Fish(TheGreatestSage sage) {
  3. super(sage);
  4. }
  5. @Override
  6. public void move() {
  7. // 代码
  8. System.out.println("Fish Move");
  9. }
  10. }

客户端调用

  1. public class Client {
  2. public static void main(String[] args) {
  3. TheGreatestSage sage = new Monkey();
  4. // 第一种写法 单层装饰
  5. TheGreatestSage bird = new Bird(sage);
  6. TheGreatestSage fish = new Fish(bird);
  7. // 第二种写法 双层装饰
  8. //TheGreatestSage fish = new Fish(new Bird(sage));
  9. fish.move();
  10. }
  11. }

“大圣本尊”是ConcreteComponent类,而“鸟儿”、“鱼儿”是装饰类。要装饰的是“大圣本尊”,也即“猢狲”实例。
上面的例子中,第二种些方法:系统把大圣从一只猢狲装饰成了一只鸟儿(把鸟儿的功能加到了猢狲身上),然后又把鸟儿装饰成了一条鱼儿(把鱼儿的功能加到了猢狲+鸟儿身上,得到了猢狲+鸟儿+鱼儿)。

下面一个订单问题

  1. // 构建者
  2. public abstract class Drink {
  3. private String des; // 描述
  4. private float price = 0.0f;
  5. public String getDes() {
  6. return des;
  7. }
  8. public void setDes(String des) {
  9. this.des = des;
  10. }
  11. public float getPrice() {
  12. return price;
  13. }
  14. public void setPrice(float price) {
  15. this.price = price;
  16. }
  17. // 计算费用的抽象方法
  18. public abstract float cost();
  19. }
  20. // 具体实现类
  21. public class Coffee extends Drink{
  22. @Override
  23. public float cost() {
  24. return super.getPrice();
  25. }
  26. }
  27. public class Espresso extends Coffee{
  28. public Espresso() {
  29. setDes("意大利咖啡");
  30. setPrice(10.0f);
  31. }
  32. }
  33. public class LongBlack extends Coffee{
  34. public LongBlack() {
  35. setDes("LongBlack");
  36. setPrice(5.0f);
  37. }
  38. }
  39. public class ShortBlack extends Coffee{
  40. public ShortBlack() {
  41. setDes("ShortBlack");
  42. setPrice(6.0f);
  43. }
  44. }
  45. // 定义修饰类
  46. package cn.wen.decorator;
  47. public class Decorator extends Drink{
  48. private Drink obj;
  49. public Decorator(Drink obj) { // 组合
  50. this.obj = obj;
  51. }
  52. @Override
  53. public float cost() {
  54. // getPrice 自己的价格
  55. return super.getPrice() + obj.cost();
  56. }
  57. @Override
  58. public String getDes() {
  59. // 被装饰者的信息
  60. return super.getDes() + " "+super.getPrice() + "&&" +obj.getDes();
  61. }
  62. }
  63. //具体饮品
  64. public class Chocolate extends Decorator{
  65. public Chocolate(Drink obj) {
  66. super(obj);
  67. setDes("巧克力");
  68. setPrice(3.0f); // 调味品的价格
  69. }
  70. }
  71. public class Milk extends Decorator{
  72. public Milk(Drink obj) {
  73. super(obj);
  74. setDes("牛奶");
  75. setPrice(4.0f); // 调味品的价格
  76. }
  77. }
  78. public class Soy extends Decorator{
  79. public Soy(Drink obj) {
  80. super(obj);
  81. setDes("豆浆");
  82. setPrice(1.5f); // 调味品的价格
  83. }
  84. }
  85. public class DecoratorTest {
  86. @Test
  87. public void testAll(){
  88. System.out.println("=======执行下面测试======");
  89. // 单点一杯意大利咖啡
  90. Drink coffee = new Espresso();
  91. System.out.println("当前饮品的描述为:" + coffee.getDesc() + ";价格为:" + coffee.cost());
  92. // 单点一杯意大利咖啡 + 一份牛奶
  93. coffee = new Milk(coffee);
  94. System.out.println("当前饮品的描述为:" + coffee.getDesc() + ";价格为:" + coffee.cost());
  95. // 单点一杯意大利咖啡 + 一份牛奶 + 一份巧克力
  96. coffee = new Chocolate(coffee);
  97. System.out.println("当前饮品的描述为:" + coffee.getDesc() + ";价格为:" + coffee.cost());
  98. // 单点一杯意大利咖啡 + 一份牛奶 + 一份巧克力 + 一份巧克力
  99. coffee = new Chocolate(coffee);
  100. System.out.println("当前饮品的描述为:" + coffee.getDesc() + ";价格为:" + coffee.cost());
  101. }
  102. }

4.7.4、装饰者模式的简化

大多数情况下,装饰者模式的实现都要比上面给出的示意性例子要简单。
如果只有一个ConcreteComponent类,那么可以考虑去掉抽象的Component类(接口),把Decorator作为一个ConcreteComponent子类。如下图所示:
image.png
如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。甚至在只有两个ConcreteDecorator类的情况下,都可以这样做。如下图所示:
image.png
用孙悟空的例子来说,必须永远把孙悟空的所有变化都当成孙悟空来对待,而如果把老孙变成的鱼儿当成鱼儿,而不是老孙,那就被老孙骗了,而这时不应当发生的。下面的做法是对的:

  1. TheGreatestSage sage = new Monkey();
  2. TheGreatestSage bird = new Bird(sage);

4.7.5、优缺点

优点
1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
(2)通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
缺点
由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。

4.7.6、装饰者在Java IO流中的应用

装饰者模式在Java语言中的最著名的应用莫过于Java I/O标准库的设计了。
由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现。而如果采用装饰者模式,那么类的数目就会大大减少,性能的重复也可以减至最少。因此装饰者模式是Java I/O库的基本模式。
Java I/O库的对象结构图如下,由于Java I/O的对象众多,因此只画出InputStream的部分。
image.png
根据上图可以看出:

  • 抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。
  • 具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。
  • 抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。
  • 具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。

image.png

4.8、组合模式

4.8.1、组合模式的概述

组合模式(Composite),将对象组合成树形结构以表示“部分-整体”的层次结构,用户对单个对象和组合对象的使用具有一致性。
所以当我们的案例是树形结构或者是部分-整体的关系时,就可以考虑使用组合模式。
组合模式有两种不同的实现,分别为透明模式和安全模式,下面将详细说明一下两种实现的区别。
先说明一下UML图中各角色的职责。Component是对象声明接口,在适当情况下,实现所有类共有接口的默认行为;Leaf是叶子节点对象,其没有子节点;Composite是树枝节点对象,用来存储部件,组合树枝节点和叶子节点形成一个树形结构。
下面这两种方式我们共用同一套客户端,先将客户端代码放上。

  1. public class Client {
  2. public static void main(String[] args) {
  3. //创建根节点及其子节点
  4. Composite root = new Composite("root");
  5. root.add(new Leaf("Leaf A"));
  6. root.add(new Leaf("Leaf B"));
  7. //创建第二层节点及其子节点
  8. Composite branch = new Composite("Composite X");
  9. branch.add(new Leaf("Leaf XA"));
  10. branch.add(new Leaf("Leaf XB"));
  11. root.add(branch);
  12. //创建第三层节点及其子节点
  13. Composite branch2 = new Composite("Composite XY");
  14. branch2.add(new Leaf("Leaf XYA"));
  15. branch2.add(new Leaf("Leaf XYB"));
  16. branch.add(branch2);
  17. //创建第二层节点
  18. root.add(new Leaf("Leaf C"));
  19. //创建第二层节点并删除
  20. Leaf leaf = new Leaf("Leaf D");
  21. root.add(leaf);
  22. root.remove(leaf);
  23. //打印
  24. root.display(1);
  25. }
  26. }

4.8.2、组合模式透明模式

透明模式是把组合使用的方法放到抽象类中,不管叶子对象还是树枝对象都有相同的结构,这样做的好处就是叶子节点和树枝节点对于外界没有区别,它们具备完全一致的行为接口。但因为Leaf类本身不具备add()、remove()方法的功能,所以实现它是没有意义的。UML结构图如下:
image.png

1、 Component
  1. public abstract class Component {
  2. protected String name;
  3. public Component(String name) {
  4. this.name = name;
  5. }
  6. //增加一个叶子构件或树枝构件
  7. public abstract void add(Component component);
  8. //删除一个叶子构件或树枝构件
  9. public abstract void remove(Component component);
  10. //获取分支下的所有叶子构件和树枝构件
  11. public abstract void display(int depth);
  12. }

2.、Composite
  1. public class Composite extends Component {
  2. public Composite(String name) {
  3. super(name);
  4. }
  5. //构建容器
  6. private ArrayList<Component> componentArrayList = new ArrayList<Component>();
  7. @Override
  8. public void add(Component component) {
  9. this.componentArrayList.add(component);
  10. }
  11. @Override
  12. public void remove(Component component) {
  13. this.componentArrayList.remove(component);
  14. }
  15. @Override
  16. public void display(int depth) {
  17. //输出树形结构
  18. for(int i=0; i<depth; i++) {
  19. System.out.print('-');
  20. }
  21. System.out.println(name);
  22. //下级遍历
  23. for (Component component : componentArrayList) {
  24. component.display(depth + 1);
  25. }
  26. }
  27. }

3、 Leaf
  1. public class Leaf extends Component {
  2. public Leaf(String name) {
  3. super(name);
  4. }
  5. @Override
  6. public void add(Component component) {
  7. //空实现,抛出“不支持请求”异常
  8. throw new UnsupportedOperationException();
  9. }
  10. @Override
  11. public void remove(Component component) {
  12. //空实现,抛出“不支持请求”异常
  13. throw new UnsupportedOperationException();
  14. }
  15. @Override
  16. public void display(int depth) {
  17. //输出树形结构的叶子节点
  18. for(int i=0; i<depth; i++) {
  19. System.out.print('-');
  20. }
  21. System.out.println(name);
  22. }
  23. }

image.png

4.8.3、组合模式之安全模式

安全模式是把树枝节点树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全。但由于不够透明,所以树叶节点和树枝节点将不具有相同的接口,客户端的调用需要做相应的判断,带来了不便。UML结构图如下:
image.png

1. Component

这里相比透明模式就少了add()和romove()抽象方法的声明。

  1. public abstract class Component {
  2. protected String name;
  3. public Component(String name) {
  4. this.name = name;
  5. }
  6. //获取分支下的所有叶子构件和树枝构件
  7. public abstract void display(int depth);
  8. }

2. Composite

这里add()和remove()方法的实现就从继承变为了自己实现

  1. public class Composite extends Component {
  2. public Composite(String name) {
  3. super(name);
  4. }
  5. //构建容器
  6. private ArrayList<Component> componentArrayList = new ArrayList<Component>();
  7. //增加一个叶子构件或树枝构件
  8. public void add(Component component) {
  9. this.componentArrayList.add(component);
  10. }
  11. //删除一个叶子构件或树枝构件
  12. public void remove(Component component) {
  13. this.componentArrayList.remove(component);
  14. }
  15. @Override
  16. public void display(int depth) {
  17. //输出树形结构
  18. for(int i=0; i<depth; i++) {
  19. System.out.print('-');
  20. }
  21. System.out.println(name);
  22. //下级遍历
  23. for (Component component : componentArrayList) {
  24. component.display(depth + 1);
  25. }
  26. }
  27. }

3. Leaf

叶子节点中没有了空实现,比较安全

  1. public class Leaf extends Component {
  2. public Leaf(String name) {
  3. super(name);
  4. }
  5. @Override
  6. public void display(int depth) {
  7. //输出树形结构的叶子节点
  8. for(int i=0; i<depth; i++) {
  9. System.out.print('-');
  10. }
  11. System.out.println(name);
  12. }
  13. }

image.png
由此可看出两个方法是相同的运行结果,区别在于内部实现不同,一种是叶节点与树枝节点具备一致的行为接口但有空实现的透明模式,另一种是树枝节点单独拥有用来组合的方法但调用不便的安全模式。
为什么说它调用不便呢,因为我们如果通过递归遍历树时,这时需要判断当前节点是叶子节点还是树枝节点,客户端就需要相应的判断。

4.8.4、组合模式的应用

  1. 何时使用
  • 想表达“部分-整体”层次结构(树形结构)时
  • 希望用户忽略组合对象与单个对象的不同,用户将统一的使用组合结构中的所有对象
  1. 方法
  • 树枝和叶子实现统一接口,树枝内部组合该接口
  1. 优点
  • 高层模块调用简单。一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,高层模块不必关心自己处理的是单个对象还是整个组合结构。
  • 节点自由增加
  1. 缺点
  • 使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒转原则
  1. 使用场景
  • 维护和展示部分-整体关系的场景(如树形菜单、文件和文件夹管理)
  • 从一个整体中能够独立出部分模块或功能的场景
  1. 应用实例
  • Swing中,Button、Checkbox等组件都是树叶,而Container容器是树枝
  • 文本编辑时,可以单个字编辑,也可以整段编辑,还可以全文编辑
  • 文件复制时,可以一个一个文件复制,也可以整个文件夹复制

4.8.5、组合模式的实现

面我们以公司的层级结构为例,先看一下这个例子中该公司的层级结构(该例选自大话设计模式——程杰著)。
image.png
这种部分与整体的关系,我们就可以考虑使用组合模式,下面采用组合模式的透明模式对其实现,UML图如下:
image.png

1、 具体公司类

此为树枝节点,实现添加、移除、显示和履行职责四种方法。

  1. public class ConcreteCompany extends Company {
  2. private List<Company> companyList = new ArrayList<Company>();
  3. public ConcreteCompany(String name) {
  4. super(name);
  5. }
  6. @Override
  7. public void add(Company company) {
  8. this.companyList.add(company);
  9. }
  10. @Override
  11. public void remove(Company company) {
  12. this.companyList.remove(company);
  13. }
  14. @Override
  15. public void display(int depth) {
  16. //输出树形结构
  17. for(int i=0; i<depth; i++) {
  18. System.out.print('-');
  19. }
  20. System.out.println(name);
  21. //下级遍历
  22. for (Company component : companyList) {
  23. component.display(depth + 1);
  24. }
  25. }
  26. @Override
  27. public void lineOfDuty() {
  28. //职责遍历
  29. for (Company company : companyList) {
  30. company.lineOfDuty();
  31. }
  32. }
  33. }

2.、人力资源部

叶子节点,add和remove方法空实现。

  1. public class HRDepartment extends Company {
  2. public HRDepartment(String name) {
  3. super(name);
  4. }
  5. @Override
  6. public void add(Company company) {
  7. }
  8. @Override
  9. public void remove(Company company) {
  10. }
  11. @Override
  12. public void display(int depth) {
  13. //输出树形结构的子节点
  14. for(int i=0; i<depth; i++) {
  15. System.out.print('-');
  16. }
  17. System.out.println(name);
  18. }
  19. @Override
  20. public void lineOfDuty() {
  21. System.out.println(name + " : 员工招聘培训管理");
  22. }
  23. }

3、财务部

叶子节点,add和remove方法空实现。

  1. public class FinanceDepartment extends Company {
  2. public FinanceDepartment(String name) {
  3. super(name);
  4. }
  5. @Override
  6. public void add(Company company) {
  7. }
  8. @Override
  9. public void remove(Company company) {
  10. }
  11. @Override
  12. public void display(int depth) {
  13. //输出树形结构的子节点
  14. for(int i=0; i<depth; i++) {
  15. System.out.print('-');
  16. }
  17. System.out.println(name);
  18. }
  19. @Override
  20. public void lineOfDuty() {
  21. System.out.println(name + " : 公司财务收支管理");
  22. }
  23. }

4、 Client客户端
  1. public class Client {
  2. public static void main(String[] args) {
  3. //总公司
  4. ConcreteCompany root = new ConcreteCompany("北京总公司");
  5. root.add(new HRDepartment("总公司人力资源部"));
  6. root.add(new FinanceDepartment("总公司财务部"));
  7. //分公司
  8. ConcreteCompany company = new ConcreteCompany("上海华东分公司");
  9. company.add(new HRDepartment("华东分公司人力资源部"));
  10. company.add(new FinanceDepartment("华东分公司财务部"));
  11. root.add(company);
  12. //办事处
  13. ConcreteCompany company1 = new ConcreteCompany("南京办事处");
  14. company1.add(new HRDepartment("南京办事处人力资源部"));
  15. company1.add(new FinanceDepartment("南京办事处财务部"));
  16. company.add(company1);
  17. ConcreteCompany company2 = new ConcreteCompany("杭州办事处");
  18. company2.add(new HRDepartment("杭州办事处人力资源部"));
  19. company2.add(new FinanceDepartment("杭州办事处财务部"));
  20. company.add(company2);
  21. System.out.println("结构图:");
  22. root.display(1);
  23. System.out.println("\n职责:");
  24. root.lineOfDuty();
  25. }
  26. }

image.png

下面的学校的介绍

  1. package cn.wen.composite;
  2. /**
  3. * 组织 校园 系 学院
  4. */
  5. public abstract class OrganizationComponent {
  6. private String name; // 名字
  7. private String des; // 说明
  8. public String getName() {
  9. return name;
  10. }
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. public String getDes() {
  15. return des;
  16. }
  17. public void setDes(String des) {
  18. this.des = des;
  19. }
  20. protected void add(OrganizationComponent organizationComponent){
  21. // 默认实现
  22. throw new UnsupportedOperationException();
  23. }
  24. protected void remove(OrganizationComponent organizationComponent){
  25. // 默认实现
  26. throw new UnsupportedOperationException();
  27. }
  28. public OrganizationComponent(String name, String des) {
  29. this.name = name;
  30. this.des = des;
  31. }
  32. // 方法print 抽象类
  33. protected abstract void print();
  34. }
  35. package cn.wen.composite;
  36. import java.util.*;
  37. /**
  38. * University 就是Composite 组合器 可以管理Collage
  39. */
  40. public class University extends OrganizationComponent{
  41. List<OrganizationComponent> organizationComponents = new ArrayList<>();
  42. public University(String name, String des) {
  43. super(name, des);
  44. }
  45. // 重写add方法
  46. @Override
  47. protected void add(OrganizationComponent organizationComponent) {
  48. organizationComponents.add(organizationComponent);
  49. }
  50. @Override
  51. public String getName() {
  52. return super.getName();
  53. }
  54. @Override
  55. public String getDes() {
  56. return super.getDes();
  57. }
  58. @Override
  59. protected void remove(OrganizationComponent organizationComponent) {
  60. organizationComponents.remove(organizationComponent);
  61. }
  62. @Override
  63. protected void print() {
  64. System.out.println("================"+getName()+"====================");
  65. // 遍历
  66. for (OrganizationComponent organizationComponent : organizationComponents) {
  67. organizationComponent.print();
  68. }
  69. }
  70. }
  71. package cn.wen.composite;
  72. import java.util.ArrayList;
  73. import java.util.List;
  74. /**
  75. * Collage 就是Composite 组合器 可以管理Collage
  76. */
  77. public class Collage extends OrganizationComponent{
  78. // 存放的是Department 可以管理下一级的数据
  79. List<OrganizationComponent> organizationComponents = new ArrayList<>();
  80. public Collage(String name, String des) {
  81. super(name, des);
  82. }
  83. // 重写add方法
  84. @Override
  85. protected void add(OrganizationComponent organizationComponent) {
  86. // 实际业务中不一定完全相同
  87. organizationComponents.add(organizationComponent);
  88. }
  89. @Override
  90. public String getName() {
  91. return super.getName();
  92. }
  93. @Override
  94. public String getDes() {
  95. return super.getDes();
  96. }
  97. @Override
  98. protected void remove(OrganizationComponent organizationComponent) {
  99. organizationComponents.remove(organizationComponent);
  100. }
  101. @Override
  102. protected void print() {
  103. System.out.println("================"+getName()+"====================");
  104. // 遍历
  105. for (OrganizationComponent organizationComponent : organizationComponents) {
  106. organizationComponent.print();
  107. }
  108. }
  109. }
  110. package cn.wen.composite;
  111. import java.util.ArrayList;
  112. import java.util.List;
  113. /**
  114. * Department 就是Composite 组合器 可以管理Collage
  115. */
  116. public class Department extends OrganizationComponent{
  117. public Department(String name, String des) {
  118. super(name, des);
  119. }
  120. // 叶子节点不需要管理其他数据
  121. @Override
  122. public String getName() {
  123. return super.getName();
  124. }
  125. @Override
  126. public String getDes() {
  127. return super.getDes();
  128. }
  129. @Override
  130. protected void print() {
  131. System.out.println(getName());
  132. }
  133. }
  134. package cn.wen.composite;
  135. public class Client {
  136. public static void main(String[] args) {
  137. // 从大道小插件对象
  138. OrganizationComponent university = new University("清华大学", "顶级大学");
  139. // 创建学院
  140. OrganizationComponent computerCollage = new Collage("计算机学院", "计算机学院");
  141. OrganizationComponent info = new Collage("信息工程学院", "信息工程学院");
  142. // 创建系
  143. computerCollage.add(new Department("软件工程","软件工程不错"));
  144. computerCollage.add(new Department("网络工程","网络工程不错"));
  145. computerCollage.add(new Department("计算机科学与技术","计算机科学与技术不错"));
  146. // 创建系
  147. computerCollage.add(new Department("通信工程","通信工程不好学"));
  148. computerCollage.add(new Department("信息工程","信息工程不错"));
  149. // 学院加入到学校
  150. university.add(computerCollage);
  151. university.add(info);
  152. university.print();
  153. }
  154. }

组合模式这样就定义了包含人力资源部和财务部这些基本对象和分公司、办事处等组合对象的类层次结构。
基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户代码中,任何用到基本对象的地方都可以使用组合对象了。
这里用了透明模式,用户不用关心到底是处理一个叶节点还是处理一个组合组件,也就用不着为定义组合而写一些选择判断语句了。简单点说就是组合模式可以让客户一致地使用组合结构和单个对象。

4.9、外观模式

4.9.1、外观模式的概述

外观模式(Facade),在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。
软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
图 1 给出了客户去当地房产局办理房产证过户要遇到的相关部门。
image.png
外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。

外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点。

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


外观(Facade)模式的主要缺点如下。

  1. 不能很好地限制客户使用子系统类,很容易带来未知风险。
  2. 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

4.9.2、外观模式的结构

外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。现在来分析其基本结构和实现方法。

1、模式的结构

外观(Facade)模式包含以下主要角色。

  1. 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
  2. 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
  3. 客户(Client)角色:通过一个外观角色访问各个子系统的功能。


其结构图如图 2 所示。
image.png

2、模式的实现

外观模式的实现代码如下:

  1. package facade;
  2. public class FacadePattern {
  3. public static void main(String[] args) {
  4. Facade f = new Facade();
  5. f.method();
  6. }
  7. }
  8. //外观角色
  9. class Facade {
  10. private SubSystem01 obj1 = new SubSystem01();
  11. private SubSystem02 obj2 = new SubSystem02();
  12. private SubSystem03 obj3 = new SubSystem03();
  13. public void method() {
  14. obj1.method1();
  15. obj2.method2();
  16. obj3.method3();
  17. }
  18. }
  19. //子系统角色
  20. class SubSystem01 {
  21. public void method1() {
  22. System.out.println("子系统01的method1()被调用!");
  23. }
  24. }
  25. //子系统角色
  26. class SubSystem02 {
  27. public void method2() {
  28. System.out.println("子系统02的method2()被调用!");
  29. }
  30. }
  31. //子系统角色
  32. class SubSystem03 {
  33. public void method3() {
  34. System.out.println("子系统03的method3()被调用!");
  35. }
  36. }

程序运行结果如下:

  1. 子系统01method1()被调用!
  2. 子系统02method2()被调用!
  3. 子系统03method3()被调用!

4.9.3、外观模式实例

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

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

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

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

基本介绍

  1. 外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用 。
  2. 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
  1. package cn.wen.facade;
  2. // 外观类
  3. public class HomeTheaterFacade {
  4. // 定义各个子系统的对象
  5. private TheaterLight theaterLight;
  6. private Popcorn popcorn;
  7. private Stereo stereo;
  8. private Projector projector;
  9. private Screen screen;
  10. private DVDPlayer dvdPlayer;
  11. public HomeTheaterFacade() {
  12. this.theaterLight = TheaterLight.getInstance();
  13. this.popcorn = Popcorn.getInstance();
  14. this.stereo = Stereo.getInstance();
  15. this.projector = Projector.getInstance();
  16. this.screen = Screen.getInstance();
  17. this.dvdPlayer = DVDPlayer.getInstance();
  18. }
  19. // 操作分成4步
  20. public void read(){
  21. popcorn.on();
  22. popcorn.pop();
  23. screen.down();
  24. projector.on();
  25. stereo.on();
  26. dvdPlayer.on();
  27. theaterLight.dim();
  28. }
  29. // 播放
  30. public void play(){
  31. dvdPlayer.play();
  32. }
  33. // 暂停
  34. public void pause(){
  35. dvdPlayer.pause();
  36. }
  37. // 关闭
  38. public void end(){
  39. popcorn.off();
  40. theaterLight.bright();
  41. screen.up();
  42. projector.off();
  43. stereo.off();
  44. dvdPlayer.off();
  45. }
  46. public static void main(String[] args) {
  47. }
  48. }
  49. // 子类 实现自己的功能 通过饿汉式创建实例
  50. package cn.wen.facade;
  51. public class DVDPlayer {
  52. // 使用单例模式, 饿汉式
  53. private static DVDPlayer instance = new DVDPlayer();
  54. public static DVDPlayer getInstance(){
  55. return instance;
  56. }
  57. public void on(){
  58. System.out.println(" DVD on ");
  59. }
  60. public void off(){
  61. System.out.println(" DVD off ");
  62. }
  63. public void play(){
  64. System.out.println(" DVD is playing");
  65. }
  66. // 暂停
  67. public void pause(){
  68. System.out.println(" DVD pause");
  69. }
  70. }
  71. package cn.wen.facade;
  72. public class Popcorn {
  73. // 使用单例模式, 饿汉式
  74. private static Popcorn instance = new Popcorn();
  75. public static Popcorn getInstance(){
  76. return instance;
  77. }
  78. public void on(){
  79. System.out.println(" Popcorn on ");
  80. }
  81. public void off(){
  82. System.out.println(" Popcorn off ");
  83. }
  84. public void pop(){
  85. System.out.println(" Popcorn is poping");
  86. }
  87. }
  88. package cn.wen.facade;
  89. public class Projector {
  90. // 使用单例模式, 饿汉式
  91. private static Projector instance = new Projector();
  92. public static Projector getInstance(){
  93. return instance;
  94. }
  95. public void on(){
  96. System.out.println(" Projector on ");
  97. }
  98. public void off(){
  99. System.out.println(" Projector off ");
  100. }
  101. public void focus(){
  102. System.out.println(" Projector is focusing");
  103. }
  104. }
  105. package cn.wen.facade;
  106. public class Screen {
  107. // 使用单例模式, 饿汉式
  108. private static Screen instance = new Screen();
  109. public static Screen getInstance(){
  110. return instance;
  111. }
  112. public void up(){
  113. System.out.println(" Screen on ");
  114. }
  115. public void down(){
  116. System.out.println(" Screen down ");
  117. }
  118. }
  119. package cn.wen.facade;
  120. public class Stereo {
  121. // 使用单例模式, 饿汉式
  122. private static Stereo instance = new Stereo();
  123. public static Stereo getInstance(){
  124. return instance;
  125. }
  126. public void on(){
  127. System.out.println(" Stereo on ");
  128. }
  129. public void off(){
  130. System.out.println(" Stereo off ");
  131. }
  132. public void up(){
  133. System.out.println(" Stereo is up");
  134. }
  135. }
  136. package cn.wen.facade;
  137. public class TheaterLight {
  138. // 使用单例模式, 饿汉式
  139. private static TheaterLight instance = new TheaterLight();
  140. public static TheaterLight getInstance(){
  141. return instance;
  142. }
  143. public void on(){
  144. System.out.println(" TheaterLight on ");
  145. }
  146. public void off(){
  147. System.out.println(" TheaterLight off ");
  148. }
  149. public void bright(){
  150. System.out.println(" TheaterLight is bright");
  151. }
  152. public void dim(){
  153. System.out.println("TheaterLight is dim");
  154. }
  155. }
  156. package cn.wen.facade;
  157. public class Client {
  158. public static void main(String[] args) {
  159. HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
  160. homeTheaterFacade.read();
  161. homeTheaterFacade.play();
  162. homeTheaterFacade.pause();
  163. homeTheaterFacade.end();
  164. }
  165. }

4.9.4、外观模式应用场景

通常在以下情况下可以考虑使用外观模式。

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

4.9.5、外观模式的应用

外观模式在MyBatis框架应用的源码分析
1) MyBatis 中的Configuration 去创建MetaObject 对象使用到外观模式
2) 代码分析+Debug源码+示意图
image.png

4.9.6、外观模式的注意

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

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

4.10、享元模式

4.10.1、享元模式的概念

享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

其主要缺点是:

  1. 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
  2. 读取享元模式的外部状态会使得运行时间稍微变长。

4.10.2、享元模式的结构

享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态

  • 内部状态指对象共享出来的信息,存储在享元信息内部,并且不回随环境的改变而改变;
  • 外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。


比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。

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

1、模式结构

享元模式的主要角色有如下。

  1. 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  2. 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
  3. 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
  4. 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。


图 1 是享元模式的结构图,其中:

  • UnsharedConcreteFlyweight 是非享元角色,里面包含了非共享的外部状态信息 info;
  • Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部状态以参数的形式通过该方法传入;
  • ConcreteFlyweight 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;
  • FlyweightFactory 是享元工厂角色,它是关键字 key 来管理具体享元;
  • 客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。

image.png
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一 点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态
1) 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态外部状态
2) 内部状态对象共享出来的信息存储在享元对象内部且不会随环境的改变而改变
3) 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态
4) 举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题

2、模式的实现

享元模式的实现代码如下:

  1. public class FlyweightPattern {
  2. public static void main(String[] args) {
  3. FlyweightFactory factory = new FlyweightFactory();
  4. Flyweight f01 = factory.getFlyweight("a");
  5. Flyweight f02 = factory.getFlyweight("a");
  6. Flyweight f03 = factory.getFlyweight("a");
  7. Flyweight f11 = factory.getFlyweight("b");
  8. Flyweight f12 = factory.getFlyweight("b");
  9. f01.operation(new UnsharedConcreteFlyweight("第1次调用a。"));
  10. f02.operation(new UnsharedConcreteFlyweight("第2次调用a。"));
  11. f03.operation(new UnsharedConcreteFlyweight("第3次调用a。"));
  12. f11.operation(new UnsharedConcreteFlyweight("第1次调用b。"));
  13. f12.operation(new UnsharedConcreteFlyweight("第2次调用b。"));
  14. }
  15. }
  16. //非享元角色
  17. class UnsharedConcreteFlyweight {
  18. private String info;
  19. UnsharedConcreteFlyweight(String info) {
  20. this.info = info;
  21. }
  22. public String getInfo() {
  23. return info;
  24. }
  25. public void setInfo(String info) {
  26. this.info = info;
  27. }
  28. }
  29. //抽象享元角色
  30. interface Flyweight {
  31. public void operation(UnsharedConcreteFlyweight state);
  32. }
  33. //具体享元角色
  34. class ConcreteFlyweight implements Flyweight {
  35. private String key;
  36. ConcreteFlyweight(String key) {
  37. this.key = key;
  38. System.out.println("具体享元" + key + "被创建!");
  39. }
  40. public void operation(UnsharedConcreteFlyweight outState) {
  41. System.out.print("具体享元" + key + "被调用,");
  42. System.out.println("非享元信息是:" + outState.getInfo());
  43. }
  44. }
  45. //享元工厂角色
  46. class FlyweightFactory {
  47. private HashMap<String, Flyweight> flyweights = new HashMap<String, Flyweight>();
  48. public Flyweight getFlyweight(String key) {
  49. Flyweight flyweight = (Flyweight) flyweights.get(key);
  50. if (flyweight != null) {
  51. System.out.println("具体享元" + key + "已经存在,被成功获取!");
  52. } else {
  53. flyweight = new ConcreteFlyweight(key);
  54. flyweights.put(key, flyweight);
  55. }
  56. return flyweight;
  57. }
  58. }

程序运行结果如下:

  1. 具体享元a被创建!
  2. 具体享元a已经存在,被成功获取!
  3. 具体享元a已经存在,被成功获取!
  4. 具体享元b被创建!
  5. 具体享元b已经存在,被成功获取!
  6. 具体享元a被调用,非享元信息是:第1次调用a
  7. 具体享元a被调用,非享元信息是:第2次调用a
  8. 具体享元a被调用,非享元信息是:第3次调用a
  9. 具体享元b被调用,非享元信息是:第1次调用b
  10. 具体享元b被调用,非享元信息是:第2次调用b

4.10.3、享元模式的实例

小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:
1) 有客户要求以新闻的形式发布
2) 有客户人要求以博客的形式发布
3) 有客户希望以微信公众号的形式发布

  1. // 抽象方法
  2. package cn.wen.flyweight;
  3. public abstract class WebSite {
  4. public abstract void use(User user); // 抽象方法
  5. }
  6. // 具体实现什么类型网站的实现类
  7. package cn.wen.flyweight;
  8. // 具体的网站
  9. public class ConcreteWebSite extends WebSite{
  10. private String type = "";// 网站发布的形式类型
  11. public ConcreteWebSite(String type) {
  12. this.type = type;
  13. }
  14. @Override
  15. public void use(User user) {
  16. System.out.println("网站的发布形式:"+type+" 在使用中.... 使用者为:"+user.getName());
  17. }
  18. }
  19. // 登录用户
  20. package cn.wen.flyweight;
  21. public class User {
  22. public User(String name) {
  23. this.name = name;
  24. }
  25. private String name;
  26. public String getName() {
  27. return name;
  28. }
  29. public void setName(String name) {
  30. this.name = name;
  31. }
  32. }
  33. // 网站工厂类
  34. package cn.wen.flyweight;
  35. import java.util.HashMap;
  36. // 网站工厂类 根据需要返回一个网站
  37. public class WebSiteFactory {
  38. // 集合,充当池的作用
  39. private HashMap<String, ConcreteWebSite> pool = new HashMap<>();
  40. // 根据网站的类型,返回一个网站 如果没有就创建一个网站 存放到网站池中 返回
  41. public WebSite getWebSiteCategory(String type){
  42. if (!pool.containsKey(type)){
  43. // 创建一个网站 放到池中
  44. pool.put(type, new ConcreteWebSite(type));
  45. }
  46. return pool.get(type);
  47. }
  48. // 获取文章分类的总数(多少个类型的网站类型)
  49. public int getWebSiteCount(){
  50. return pool.size();
  51. }
  52. }
  53. // 用户
  54. package cn.wen.flyweight;
  55. public class Client {
  56. public static void main(String[] args) {
  57. // 创建一个工厂
  58. WebSiteFactory factory = new WebSiteFactory();
  59. // 客户需要一个新闻形式的网站
  60. WebSite webSite1 = factory.getWebSiteCategory("新闻");
  61. webSite1.use(new User("丫丫"));
  62. // // 客户需要一个博客形式的网站
  63. // WebSite webSite2 = factory.getWebSiteCategory("博客");
  64. //
  65. // webSite2.use();
  66. //
  67. // // 客户需要一个博客形式的网站
  68. // WebSite webSite3 = factory.getWebSiteCategory("博客");
  69. //
  70. // webSite3.use();
  71. //
  72. // // 客户需要一个博客形式的网站
  73. // WebSite webSite4 = factory.getWebSiteCategory("博客");
  74. //
  75. // webSite4.use();
  76. //
  77. // System.out.println();
  78. }
  79. }

4.10.4、享元模式的应用

  1. package cn.wen.flyweight;
  2. public class FlyWeight {
  3. public static void main(String[] args) {
  4. /**
  5. * 1、如果Integer.valueOf(x) x 在 -128~127之间,就是使用享元模式返回(缓存,相当于缓存池中取),
  6. * 如果不在范围内就是new一个Integer对象。
  7. * 2、在valueOf方法中,先判断值是否在IntegerCache中 如果不在就创建新的Integer(new) ,
  8. * 否则就直接在缓存池中返回一个对象
  9. * 3、valueOf方法使用到了享元模式
  10. * 4、如果使用valueOf方法就会得到一个Integer实例范围在-128~127,执行速度会比new一个对象更快
  11. *
  12. */
  13. Integer x = Integer.valueOf(127); // 得到x实例,类型Integer
  14. Integer y = new Integer(127); // 得到y实例,类型Integer
  15. Integer z = Integer.valueOf(127);
  16. Integer w = new Integer(127);
  17. System.out.println(x.equals(y)); // 大小 true
  18. System.out.println(x == y); // false
  19. System.out.println(x == z); // true
  20. System.out.println(w == z); // false
  21. System.out.println(w == y); // false
  22. Integer x1 = Integer.valueOf(200);
  23. Integer x2 = Integer.valueOf(200);
  24. System.out.println(x1 == x2); // false
  25. }
  26. }

image.png

4.10.5、享元模式的应用场景

当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。

享元模式其实是工厂方法模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能。

前面分析了享元模式的结构与特点,下面分析它适用的应用场景。享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。

  1. 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
  2. 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
  3. 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。

    4.10.6、享元模式的细节

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

4.11、代理模式

4.11.1、代理模式的概念

在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。

在软件设计中,使用代理模式的例子也很多,例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
代理模式的主要优点有:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性


其主要缺点是:

  • 代理模式会造成系统设计中类的数量增加
  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  • 增加了系统的复杂度;

代理模式的结构:

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。


image.png
为什么要学习代理模式?
因为这是SpringAOP的底层!!!!!
代理模式的分类:

  • 静态代理
  • 动态代理

image.png

9.1、静态代理

角色分析:

  • 抽象角色:一般使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真角色,代理真实角色后,会做一些附属操作
  • 客户:访问代理的人

代理模式的优点:

  • 可以使角色的操作更加纯粹!不用去关注!
  • 公共也就交给代理角色!实现业务分工!
  • 公共业务发生扩展的时候,更加集中管理!

缺点:

  • 一个真实角色需要产生一个代理角色;代码量会翻倍~开发效率低下。

静态代理的实例:

  1. package proxy;
  2. public class ProxyTest {
  3. public static void main(String[] args) {
  4. Proxy proxy = new Proxy();
  5. proxy.Request();
  6. }
  7. }
  8. //抽象主题
  9. interface Subject {
  10. void Request();
  11. }
  12. //真实主题
  13. class RealSubject implements Subject {
  14. public void Request() {
  15. System.out.println("访问真实主题方法...");
  16. }
  17. }
  18. //代理
  19. class Proxy implements Subject {
  20. private RealSubject realSubject;
  21. public void Request() {
  22. if (realSubject == null) {
  23. realSubject = new RealSubject();
  24. }
  25. preRequest();
  26. realSubject.Request();
  27. postRequest();
  28. }
  29. public void preRequest() {
  30. System.out.println("访问真实主题之前的预处理。");
  31. }
  32. public void postRequest() {
  33. System.out.println("访问真实主题之后的后续处理。");
  34. }
  35. }

运行结果:
访问真实主题之前的预处理。
访问真实主题方法…
访问真实主题之后的后续处理。

1、典型的实现用户业务CURD

1)、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查

  1. //抽象角色:增删改查业务
  2. public interface UserService {
  3. void add();
  4. void delete();
  5. void update();
  6. void query();
  7. }

2)、我们需要一个真实对象来完成这些增删改查操作

  1. //真实对象,完成增删改查操作的人
  2. public class UserServiceImpl implements UserService {
  3. public void add() {
  4. System.out.println("增加了一个用户");
  5. }
  6. public void delete() {
  7. System.out.println("删除了一个用户");
  8. }
  9. public void update() {
  10. System.out.println("更新了一个用户");
  11. }
  12. public void query() {
  13. System.out.println("查询了一个用户");
  14. }
  15. }

3)、需求来了,现在我们需要增加一个日志功能,怎么实现!

  • 思路1 :在实现类上增加代码 【麻烦!】
  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!

4)、设置一个代理类来处理日志!代理角色

  1. //代理角色,在这里面增加日志的实现
  2. public class UserServiceProxy implements UserService {
  3. private UserServiceImpl userService;
  4. public void setUserService(UserServiceImpl userService) {
  5. this.userService = userService;
  6. }
  7. public void add() {
  8. log("add");
  9. userService.add();
  10. }
  11. public void delete() {
  12. log("delete");
  13. userService.delete();
  14. }
  15. public void update() {
  16. log("update");
  17. userService.update();
  18. }
  19. public void query() {
  20. log("query");
  21. userService.query();
  22. }
  23. public void log(String msg){
  24. System.out.println("执行了"+msg+"方法");
  25. }
  26. }

测试类

  1. public class Client {
  2. public static void main(String[] args) {
  3. //真实业务
  4. UserServiceImpl userService = new UserServiceImpl();
  5. //代理类
  6. UserServiceProxy proxy = new UserServiceProxy();
  7. //使用代理类实现日志功能!
  8. proxy.setUserService(userService);
  9. proxy.add();
  10. }
  11. }

但是静态代理会产生代码冗余,添加一个对象就要实现一个类的实现。

9.2、动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的!
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口——JDK代理
    • 基于类:cglib
    • Java字节码实现:javasist

需要了解两个类:Proxy代理类,InvocationHandler接口调用处理程序(里面会使用invoke方法)
image.png

动态代理也叫 JDK 代理或接口代理,有以下特点:

  • 代理对象不需要实现接口
  • 代理对象的生成是利用 JDK 的 API 动态的在内存中构建代理对象
  • 能在代码运行时动态地改变某个对象的代理,并且能为代理对象动态地增加方法、增加行为


一般情况下,动态代理的底层不用我们亲自去实现,可以使用线程提供的 API 。例如,在 Java 生态中,目前普遍使用的是 JDK 自带的代理和 GGLib 提供的类库。
注意该方法在 Proxy 类中是静态方法,且接收的三个参数说明依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的
  • Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,把当前执行目标对象的方法作为参数传入
  1. Object invoke(Object proxy, 方法 method, Object[] args);
  2. //参数
  3. //proxy - 调用该方法的代理实例
  4. //method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
  5. //args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。

动态代理的实现

image.png
  1. //生成代理类
  2. public Object getProxy(){
  3. return Proxy.newProxyInstance(this.getClass().getClassLoader(),
  4. rent.getClass().getInterfaces(),this);
  5. }

1、典型出租房屋动态代理

Rent . java 即抽象角色

  1. //抽象角色:租房
  2. public interface Rent {
  3. public void rent();
  4. }

Host . java 即真实角色 只有真实角色才需要实现接口

  1. //真实角色: 房东,房东要出租房子
  2. public class Host implements Rent{
  3. public void rent() {
  4. System.out.println("房屋出租");
  5. }
  6. }

ProxyInvocationHandler. java 即代理角色

  1. public class ProxyInvocationHandler implements InvocationHandler {
  2. private Rent rent;
  3. public void setRent(Rent rent) {
  4. this.rent = rent;
  5. }
  6. //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
  7. public Object getProxy(){
  8. return Proxy.newProxyInstance(this.getClass().getClassLoader(),
  9. rent.getClass().getInterfaces(),this);
  10. }
  11. // proxy : 代理类 method : 代理类的调用处理程序的方法对象.
  12. // 处理代理实例上的方法调用并返回结果
  13. @Override
  14. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  15. seeHouse();
  16. //核心:本质利用反射实现!
  17. Object result = method.invoke(rent, args);
  18. fare();
  19. return result;
  20. }
  21. //看房
  22. public void seeHouse(){
  23. System.out.println("带房客看房");
  24. }
  25. //收中介费
  26. public void fare(){
  27. System.out.println("收中介费");
  28. }
  29. }

Client . java

  1. //租客
  2. public class Client {
  3. public static void main(String[] args) {
  4. //真实角色
  5. Host host = new Host();
  6. //代理实例的调用处理程序
  7. ProxyInvocationHandler pih = new ProxyInvocationHandler();
  8. pih.setRent(host); //将真实角色放置进去!
  9. Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
  10. proxy.rent();
  11. }
  12. }

2、我们还可以实现一下CURD操作

我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

代理类

  1. public class ProxyInvocationHandler implements InvocationHandler {
  2. private Object target;
  3. public void setTarget(Object target) {
  4. this.target = target;
  5. }
  6. //生成代理类
  7. public Object getProxy(){
  8. return Proxy.newProxyInstance(this.getClass().getClassLoader(),
  9. target.getClass().getInterfaces(),this);
  10. }
  11. // proxy : 代理类
  12. // method : 代理类的调用处理程序的方法对象.
  13. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  14. log(method.getName());
  15. Object result = method.invoke(target, args);
  16. return result;
  17. }
  18. public void log(String methodName){
  19. System.out.println("执行了"+methodName+"方法");
  20. }
  21. }

测试类

  1. public class Test {
  2. public static void main(String[] args) {
  3. //真实对象
  4. UserServiceImpl userService = new UserServiceImpl();
  5. //代理对象的调用处理程序
  6. ProxyInvocationHandler pih = new ProxyInvocationHandler();
  7. pih.setTarget(userService); //设置要代理的对象
  8. UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
  9. proxy.delete();
  10. }
  11. }

动态代理应用于AOP切面编程,为了避免对大量的源代码更改,所以增加一个切面编程,可以横切插入数据就上面的invoke方法中在Object result = method.invoke(target, args); 上下行可以进行添加操作,实现对原先的代码进行添加等等,广泛用于log的打印。

3、采用动态代理基本上只要是人(IPerson)就可以提供找老师服务。

顶层接口

  1. public interface IPerson {
  2. void findTeacher(); //找老师
  3. }

创建辅导班代理类(辅导班相当于一个中介 将老师和学生连接起来)

  1. public class JdkFuDao implements InvocationHandler {
  2. private IPerson target;
  3. public IPerson getInstance(IPerson target) {
  4. this.target = target;
  5. Class<?> clazz = target.getClass();
  6. return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
  7. }
  8. @Override
  9. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  10. before();
  11. Object result = method.invoke(this.target, args);
  12. after();
  13. return result;
  14. }
  15. private void after() {
  16. System.out.println("双方同意,开始辅导");
  17. }
  18. private void before() {
  19. System.out.println("这里是C语言中文网辅导班,已经收集到您的需求,开始挑选老师");
  20. }
  21. }

创建一个人的类 目标对象一定要实现接口

  1. public class ZhaoLiu implements IPerson {
  2. @Override
  3. public void findTeacher() {
  4. System.out.println("符合赵六的要求");
  5. }
  6. public void buyInsure() {
  7. }
  8. }

测试类

  1. public class Test {
  2. public static void main(String[] args) {
  3. JdkFuDao jdkFuDao = new JdkFuDao();
  4. IPerson zhaoliu = jdkFuDao.getInstance(new ZhaoLiu());
  5. zhaoliu.findTeacher();
  6. }
  7. }

9.3、静态代理和动态代理区别

静态代理和动态代理主要有以下几点区别:

  • 静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,则代理类需要同步增加,违背开闭原则。
  • 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
  • 若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。

10、模板方法模式

在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等。

10.1、模式的定义和特点

模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。

该模式的主要优点如下。

  1. 封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  2. 它在父类中提取了公共的部分代码,便于代码复用。
  3. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。


该模式的主要缺点如下。

  1. 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
  2. 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  3. 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

10.2、模式的结构和实现

模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。现在来介绍它们的基本结构。

1.、模式的结构

模板方法模式包含以下主要角色。

1)抽象类/抽象模板(Abstract Class)

抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。

① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
② 基本方法:是整个算法中的一个步骤,包含以下几种类型。

  • 抽象方法:在抽象类中声明,由具体子类实现。
  • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
  • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
    2)具体子类/具体实现(Concrete Class)
    具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
    模板方法模式的结构图如图 1 所示。
    image.png

    2、模式的实现

    ```java public class TemplateMethodPattern { public static void main(String[] args) {
    1. AbstractClass tm = new ConcreteClass();
    2. tm.TemplateMethod();
    } }

//抽象类 abstract class AbstractClass { //模板方法 public void TemplateMethod() { SpecificMethod(); abstractMethod1(); abstractMethod2(); }

  1. //具体方法
  2. public void SpecificMethod() {
  3. System.out.println("抽象类中的具体方法被调用...");
  4. }
  5. //抽象方法1
  6. public abstract void abstractMethod1();
  7. //抽象方法2
  8. public abstract void abstractMethod2();

}

//具体子类 class ConcreteClass extends AbstractClass { public void abstractMethod1() { System.out.println(“抽象方法1的实现被调用…”); }

  1. public void abstractMethod2() {
  2. System.out.println("抽象方法2的实现被调用...");
  3. }

}

  1. <a name="AEDmy"></a>
  2. ### 10.3、模式的应用
  3. 【例1】用模板方法模式实现出国留学手续设计程序。
  4. 分析:出国留学手续一般经过以下流程:索取学校资料,提出入学申请,办理因私出国护照、出境卡和公证,申请签证,体检、订机票、准备行装,抵达目标学校等,其中有些业务对各个学校是一样的,但有些业务因学校不同而不同,所以比较适合用模板方法模式来实现。
  5. 在本实例中,我们先定义一个出国留学的抽象类 StudyAbroad,里面包含了一个模板方法 TemplateMethod(),该方法中包含了办理出国留学手续流程中的各个基本方法,其中有些方法的处理由于各国都一样,所以在抽象类中就可以实现,但有些方法的处理各国是不同的,必须在其具体子类(如美国留学类 StudyInAmerica)中实现。如果再增加一个国家,只要增加一个子类就可以了,图 2 所示是其结构图。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/25484710/1652340774841-ce7e2bfe-bb52-4f06-9369-076fea92b549.png#clientId=u3f72cac4-febb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=391&id=u20351703&margin=%5Bobject%20Object%5D&name=image.png&originHeight=587&originWidth=995&originalType=binary&ratio=1&rotation=0&showTitle=false&size=170822&status=done&style=none&taskId=u0b5da3c6-d2be-441c-a338-e6f18e20054&title=&width=663.3333333333334)
  6. ```java
  7. public class StudyAbroadProcess {
  8. public static void main(String[] args) {
  9. StudyAbroad tm = new StudyInAmerica();
  10. tm.TemplateMethod();
  11. }
  12. }
  13. //抽象类: 出国留学
  14. abstract class StudyAbroad {
  15. public void TemplateMethod() //模板方法
  16. {
  17. LookingForSchool(); //索取学校资料
  18. ApplyForEnrol(); //入学申请
  19. ApplyForPassport(); //办理因私出国护照、出境卡和公证
  20. ApplyForVisa(); //申请签证
  21. ReadyGoAbroad(); //体检、订机票、准备行装
  22. Arriving(); //抵达
  23. }
  24. public void ApplyForPassport() {
  25. System.out.println("三.办理因私出国护照、出境卡和公证:");
  26. System.out.println(" 1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。");
  27. System.out.println(" 2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。");
  28. }
  29. public void ApplyForVisa() {
  30. System.out.println("四.申请签证:");
  31. System.out.println(" 1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;");
  32. System.out.println(" 2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。");
  33. }
  34. public void ReadyGoAbroad() {
  35. System.out.println("五.体检、订机票、准备行装:");
  36. System.out.println(" 1)进行身体检查、免疫检查和接种传染病疫苗;");
  37. System.out.println(" 2)确定机票时间、航班和转机地点。");
  38. }
  39. public abstract void LookingForSchool();//索取学校资料
  40. public abstract void ApplyForEnrol(); //入学申请
  41. public abstract void Arriving(); //抵达
  42. }
  43. //具体子类: 美国留学
  44. class StudyInAmerica extends StudyAbroad {
  45. @Override
  46. public void LookingForSchool() {
  47. System.out.println("一.索取学校以下资料:");
  48. System.out.println(" 1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;");
  49. System.out.println(" 2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;");
  50. System.out.println(" 3)了解该学校的住宿、交通、医疗保险情况如何;");
  51. System.out.println(" 4)该学校在中国是否有授权代理招生的留学中介公司?");
  52. System.out.println(" 5)掌握留学签证情况;");
  53. System.out.println(" 6)该国政府是否允许留学生合法打工?");
  54. System.out.println(" 8)毕业之后可否移民?");
  55. System.out.println(" 9)文凭是否受到我国认可?");
  56. }
  57. @Override
  58. public void ApplyForEnrol() {
  59. System.out.println("二.入学申请:");
  60. System.out.println(" 1)填写报名表;");
  61. System.out.println(" 2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;");
  62. System.out.println(" 3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。");
  63. }
  64. @Override
  65. public void Arriving() {
  66. System.out.println("六.抵达目标学校:");
  67. System.out.println(" 1)安排住宿;");
  68. System.out.println(" 2)了解校园及周边环境。");
  69. }
  70. }
  1. 一.索取学校以下资料:
  2. 1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;
  3. 2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;
  4. 3)了解该学校的住宿、交通、医疗保险情况如何;
  5. 4)该学校在中国是否有授权代理招生的留学中介公司?
  6. 5)掌握留学签证情况;
  7. 6)该国政府是否允许留学生合法打工?
  8. 8)毕业之后可否移民?
  9. 9)文凭是否受到我国认可?
  10. 二.入学申请:
  11. 1)填写报名表;
  12. 2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;
  13. 3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。
  14. 三.办理因私出国护照、出境卡和公证:
  15. 1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。
  16. 2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。
  17. 四.申请签证:
  18. 1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;
  19. 2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。
  20. 五.体检、订机票、准备行装:
  21. 1)进行身体检查、免疫检查和接种传染病疫苗;
  22. 2)确定机票时间、航班和转机地点。
  23. 六.抵达目标学校:
  24. 1)安排住宿;
  25. 2)了解校园及周边环境。

应用2:
编写制作豆浆的程序,说明如下:
1) 制作豆浆的流程 选材—->添加配料—->浸泡—->放到豆浆机打碎
2) 通过添加不同的配料,可以制作出不同口味的豆浆
3) 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
4) 请使用 模板方法模式 完成 (说明:因为模板方法模式,比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式 )

编写制作豆浆的程序,说明如下:

  • 制作豆浆的流程 选材—->添加配料—->浸泡—->放到豆浆机打碎
  • 通过添加不同的配料,可以制作出不同口味的豆浆
  • 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的(红豆、花生豆浆。。。)

模板方法模式的钩子方法
1) 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
2) 还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造

  1. package cn.wen.template;
  2. // 抽象类 表示豆浆
  3. public abstract class SoyaMilk {
  4. //模板方法,make 方法可以做成final
  5. final void make() {
  6. select();
  7. if (customerWantCondiments()){
  8. addCondiments();
  9. }
  10. soak();
  11. beat();
  12. }
  13. // 选材料
  14. public void select(){
  15. System.out.println("第一步:选择好新鲜的黄豆");
  16. }
  17. // 添加不同的配料 子类具体实现
  18. public abstract void addCondiments();
  19. // 黄豆和配料就跑
  20. public void soak(){
  21. System.out.println("第三步:黄豆和配料开始浸泡,需要3小时");
  22. }
  23. // 打碎
  24. public void beat(){
  25. System.out.println("第四步:黄豆和配料放到豆浆机打碎");
  26. }
  27. // 钩子方法,决定是否需要添加配料
  28. public boolean customerWantCondiments(){
  29. return true;
  30. }
  31. }
  32. package cn.wen.template;
  33. public class PeanutSoyaMilk extends SoyaMilk{
  34. @Override
  35. public void addCondiments() {
  36. System.out.println("加入上好的花生");
  37. }
  38. }
  39. package cn.wen.template;
  40. public class PureSoyaMilk extends SoyaMilk{
  41. @Override
  42. public void addCondiments() {
  43. // 空实现
  44. }
  45. // 不需要添加配料
  46. @Override
  47. public boolean customerWantCondiments() {
  48. return false;
  49. }
  50. }
  51. package cn.wen.template;
  52. public class RedBeanSoyaMilk extends SoyaMilk{
  53. @Override
  54. public void addCondiments() {
  55. System.out.println("加入上好的红豆");
  56. }
  57. }
  58. package cn.wen.template;
  59. public class Client {
  60. public static void main(String[] args) {
  61. // 制作红豆豆浆
  62. System.out.println("----制作红豆豆浆-----");
  63. SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
  64. redBeanSoyaMilk.make();
  65. System.out.println("----制作红豆豆浆-----");
  66. SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
  67. redBeanSoyaMilk.make();
  68. }
  69. }

10.4、模式的扩展

在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。如下面例子中,可以通过在具体子类中重写钩子方法 HookMethod1() 和 HookMethod2() 来改变抽象父类中的运行结果,其结构图如图 3 所示。
image.png

  1. public class HookTemplateMethod {
  2. public static void main(String[] args) {
  3. HookAbstractClass tm = new HookConcreteClass();
  4. tm.TemplateMethod();
  5. }
  6. }
  7. //含钩子方法的抽象类
  8. abstract class HookAbstractClass {
  9. //模板方法
  10. public void TemplateMethod() {
  11. abstractMethod1();
  12. HookMethod1();
  13. if (HookMethod2()) {
  14. SpecificMethod();
  15. }
  16. abstractMethod2();
  17. }
  18. //具体方法
  19. public void SpecificMethod() {
  20. System.out.println("抽象类中的具体方法被调用...");
  21. }
  22. //钩子方法1
  23. public void HookMethod1() {
  24. }
  25. //钩子方法2
  26. public boolean HookMethod2() {
  27. return true;
  28. }
  29. //抽象方法1
  30. public abstract void abstractMethod1();
  31. //抽象方法2
  32. public abstract void abstractMethod2();
  33. }
  34. //含钩子方法的具体子类
  35. class HookConcreteClass extends HookAbstractClass {
  36. public void abstractMethod1() {
  37. System.out.println("抽象方法1的实现被调用...");
  38. }
  39. public void abstractMethod2() {
  40. System.out.println("抽象方法2的实现被调用...");
  41. }
  42. public void HookMethod1() {
  43. System.out.println("钩子方法1被重写...");
  44. }
  45. public boolean HookMethod2() {
  46. return false;
  47. }
  48. }

10.5、IOC源码实现

1、图片接口

image.png

2、具体办法源码

ConfigurableApplicationContext接口:

  1. public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
  2. //该接口中定义的模板方法
  3. void refresh() throws BeansException, IllegalStateException;
  4. }

AbstractApplicationContext :

  1. public abstract class AbstractApplicationContext extends DefaultResourceLoader
  2. implements ConfigurableApplicationContext, DisposableBean {
  3. //实现ConfigurableApplicationContext接口中的模板方法
  4. @Override
  5. public void refresh() throws BeansException, IllegalStateException {
  6. synchronized (this.startupShutdownMonitor) {
  7. // Prepare this context for refreshing.
  8. prepareRefresh();
  9. // Tell the subclass to refresh the internal bean factory.
  10. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  11. // Prepare the bean factory for use in this context.
  12. prepareBeanFactory(beanFactory);
  13. try {
  14. // Allows post-processing of the bean factory in context subclasses.
  15. postProcessBeanFactory(beanFactory);
  16. // Invoke factory processors registered as beans in the context.
  17. invokeBeanFactoryPostProcessors(beanFactory);
  18. // Register bean processors that intercept bean creation.
  19. registerBeanPostProcessors(beanFactory);
  20. // Initialize message source for this context.
  21. initMessageSource();
  22. // Initialize event multicaster for this context.
  23. initApplicationEventMulticaster();
  24. // Initialize other special beans in specific context subclasses.
  25. onRefresh();
  26. // Check for listener beans and register them.
  27. registerListeners();
  28. // Instantiate all remaining (non-lazy-init) singletons.
  29. finishBeanFactoryInitialization(beanFactory);
  30. // Last step: publish corresponding event.
  31. finishRefresh();
  32. }
  33. catch (BeansException ex) {
  34. if (logger.isWarnEnabled()) {
  35. logger.warn("Exception encountered during context initialization - " +
  36. "cancelling refresh attempt: " + ex);
  37. }
  38. // Destroy already created singletons to avoid dangling resources.
  39. destroyBeans();
  40. // Reset 'active' flag.
  41. cancelRefresh(ex);
  42. // Propagate exception to caller.
  43. throw ex;
  44. }
  45. finally {
  46. // Reset common introspection caches in Spring's core, since we
  47. // might not ever need metadata for singleton beans anymore...
  48. resetCommonCaches();
  49. }
  50. }
  51. }
  52. //模板方法中包含的基本方法
  53. protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
  54. refreshBeanFactory();
  55. ConfigurableListableBeanFactory beanFactory = getBeanFactory();
  56. if (logger.isDebugEnabled()) {
  57. logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
  58. }
  59. return beanFactory;
  60. }
  61. protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
  62. @Override
  63. public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
  64. //钩子方法
  65. protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  66. }
  67. /**
  68. * 钩子方法
  69. * Template method which can be overridden to add context-specific refresh work.
  70. * Called on initialization of special beans, before instantiation of singletons.
  71. * <p>This implementation is empty.
  72. * @throws BeansException in case of errors
  73. * @see #refresh()
  74. */
  75. protected void onRefresh() throws BeansException {
  76. // For subclasses: do nothing by default.
  77. }
  78. }

GenericApplicationContext:

  1. public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
  2. //父类中基本方法的实现
  3. @Override
  4. public final ConfigurableListableBeanFactory getBeanFactory() {
  5. return this.beanFactory;
  6. }
  7. /**
  8. * Do nothing: We hold a single internal BeanFactory and rely on callers
  9. * to register beans through our public methods (or the BeanFactory's).
  10. * @see #registerBeanDefinition
  11. */
  12. @Override
  13. protected final void refreshBeanFactory() throws IllegalStateException {
  14. if (!this.refreshed.compareAndSet(false, true)) {
  15. throw new IllegalStateException(
  16. "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
  17. }
  18. this.beanFactory.setSerializationId(getId());
  19. }
  20. }

AbstractRefreshableApplicationContext:

  1. public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
  2. //实现父类模板方法中的基本方法
  3. @Override
  4. public final ConfigurableListableBeanFactory getBeanFactory() {
  5. synchronized (this.beanFactoryMonitor) {
  6. if (this.beanFactory == null) {
  7. throw new IllegalStateException("BeanFactory not initialized or already closed - " +
  8. "call 'refresh' before accessing beans via the ApplicationContext");
  9. }
  10. return this.beanFactory;
  11. }
  12. }
  13. /**
  14. * This implementation performs an actual refresh of this context's underlying
  15. * bean factory, shutting down the previous bean factory (if any) and
  16. * initializing a fresh bean factory for the next phase of the context's lifecycle.
  17. */
  18. @Override
  19. protected final void refreshBeanFactory() throws BeansException {
  20. if (hasBeanFactory()) {
  21. destroyBeans();
  22. closeBeanFactory();
  23. }
  24. try {
  25. DefaultListableBeanFactory beanFactory = createBeanFactory();
  26. beanFactory.setSerializationId(getId());
  27. customizeBeanFactory(beanFactory);
  28. loadBeanDefinitions(beanFactory);
  29. synchronized (this.beanFactoryMonitor) {
  30. this.beanFactory = beanFactory;
  31. }
  32. }
  33. catch (IOException ex) {
  34. throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
  35. }
  36. }
  37. }

10.6、模板方法注意

模板方法模式的注意事项和细节
1) 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
2) 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
3) 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
4) 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
5) 一般模板方法都加上final关键字, 防止子类重写模板方法.
6) 模板方法模式使用场景:当要完成在某个过程该过程要执行一系列步骤 这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,通常考虑用模板方法模式来处理

11、命令模式

11.1、命令模式的概念

在现实生活中,命令模式的例子也很多。比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。
再比如,我们去餐厅吃饭,菜单不是等到客人来了之后才定制的,而是已经预先配置好的。这样,客人来了就只需要点菜,而不是任由客人临时定制。餐厅提供的菜单就相当于把请求和处理进行了解耦,这就是命令模式的体现。

命令(Command)模式的定义如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
1) 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
2) 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
3) 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
4) 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。
Invoker是调用者(将军),Receiver是被调用者(士兵),
MyCommand是命令,实现了Command接口,持有接收对象

11.2、命令模式的优缺点

命令模式的主要优点如下。

  1. 通过引入中间件(抽象接口)降低系统的耦合度。
  2. 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
  3. 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  4. 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
  5. 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。


其缺点是:

  1. 可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
  2. 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。

11.3、命令模式的结构和实现

可以将系统中的相关操作抽象成命令,使调用者与实现者相关分离,其结构如下。

1、模式的结构

命令模式包含以下主要角色。

  1. 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  2. 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  3. 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  4. 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。


其结构图如图 1 所示。
image.png

2、模式的实现

  1. package command;
  2. public class CommandPattern {
  3. public static void main(String[] args) {
  4. Command cmd = new ConcreteCommand();
  5. Invoker ir = new Invoker(cmd);
  6. System.out.println("客户访问调用者的call()方法...");
  7. ir.call();
  8. }
  9. }
  10. //调用者
  11. class Invoker {
  12. private Command command;
  13. public Invoker(Command command) {
  14. this.command = command;
  15. }
  16. public void setCommand(Command command) {
  17. this.command = command;
  18. }
  19. public void call() {
  20. System.out.println("调用者执行命令command...");
  21. command.execute();
  22. }
  23. }
  24. //抽象命令
  25. interface Command {
  26. public abstract void execute();
  27. }
  28. //具体命令
  29. class ConcreteCommand implements Command {
  30. private Receiver receiver;
  31. ConcreteCommand() {
  32. receiver = new Receiver();
  33. }
  34. public void execute() {
  35. receiver.action();
  36. }
  37. }
  38. //接收者
  39. class Receiver {
  40. public void action() {
  41. System.out.println("接收者的action()方法被调用...");
  42. }
  43. }

1) 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。
2) 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。
3) 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用,这时 就可以考虑使用命令模式。
4) 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来.
5) 在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品
image.png

  1. package cn.wen.command;
  2. // 创建命令接口
  3. public interface Command {
  4. // 执行动作(操作)
  5. public void execute();
  6. // 撤销动作(操作)
  7. public void undo();
  8. }
  9. package cn.wen.command;
  10. /**
  11. * 没有任何命令 空操作 初始化每个按钮 当调用空命令是 对象什么都不做
  12. * 其实 这样是一种设计模式 可以省掉空判断
  13. */
  14. public class NoCommand implements Command {
  15. @Override
  16. public void execute() {
  17. }
  18. @Override
  19. public void undo() {
  20. }
  21. }
  22. package cn.wen.command;
  23. public class LightReceiver {
  24. public void on(){
  25. System.out.println("电灯打开了.....");
  26. }
  27. public void off(){
  28. System.out.println("电灯关闭了");
  29. }
  30. }
  31. package cn.wen.command;
  32. public class LightOnCommand implements Command{
  33. // 聚合LightReceiver
  34. private LightReceiver light;
  35. public LightOnCommand(LightReceiver light) {
  36. this.light = light;
  37. }
  38. @Override
  39. public void execute() {
  40. // 调用接收者的方法
  41. light.on();
  42. }
  43. @Override
  44. public void undo() {
  45. // 调用接受者的方法
  46. light.off();
  47. }
  48. }
  49. package cn.wen.command;
  50. public class LightOffCommand implements Command{
  51. // 聚合LightReceiver
  52. private LightReceiver light;
  53. public LightOffCommand(LightReceiver light) {
  54. this.light = light;
  55. }
  56. @Override
  57. public void execute() {
  58. // 调用接收者的方法
  59. light.off();
  60. }
  61. @Override
  62. public void undo() {
  63. // 调用接受者的方法
  64. light.on();
  65. }
  66. }
  67. package cn.wen.command;
  68. public class RemoteController {
  69. // 开 按钮的命令
  70. Command[] onCommands;
  71. Command[] offCommands;
  72. // 执行撤销的命令
  73. Command undoCommand;
  74. // 构造器 完成按钮的初始化
  75. public RemoteController() {
  76. onCommands = new Command[5];
  77. offCommands = new Command[5];
  78. for (int i = 0; i < 5; i++) {
  79. onCommands[i] = new NoCommand();
  80. offCommands[i] = new NoCommand();
  81. }
  82. }
  83. // 给按钮设置需要的命令
  84. public void setCommand(int no, Command onCommand, Command offCommand){
  85. onCommands[no] = onCommand;
  86. offCommands[no] = offCommand;
  87. }
  88. // 按下开按钮
  89. public void onButtonWasPushed(int no){
  90. // 找到你按下的开的按钮
  91. onCommands[no].execute();
  92. // 记录这次的操作,用于撤销
  93. undoCommand = onCommands[no];
  94. }
  95. // 按下开按钮
  96. public void offButtonWasPushed(int no){
  97. // 找到你按下的开的按钮
  98. offCommands[no].execute();
  99. // 记录这次的操作,用于撤销
  100. undoCommand = offCommands[no];
  101. }
  102. // 按下开按钮
  103. public void undoButtonWasPushed(int no){
  104. undoCommand.undo();
  105. }
  106. }
  107. package cn.wen.command;
  108. public class Client {
  109. public static void main(String[] args) {
  110. // 使用设计模式 完成通过遥控对电灯控制
  111. // 创建电灯的对象(接收者)
  112. LightReceiver lightReceiver = new LightReceiver();
  113. // 创建电灯相关的开关命令
  114. LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
  115. LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
  116. // 需要一个遥控器
  117. RemoteController remoteController = new RemoteController();
  118. // 遥控设置命令
  119. remoteController.setCommand(0, lightOnCommand, lightOffCommand);
  120. System.out.println("=========按下灯的开按钮========");
  121. remoteController.onButtonWasPushed(0);
  122. }
  123. }

11.4、命令模式应用场景

当系统的某项操作具备命令语义,且命令实现不稳定(变化)时,可以通过命令模式解耦请求与实现。使用抽象命令接口使请求方的代码架构稳定,封装接收方具体命令的实现细节。接收方与抽象命令呈现弱耦合(内部方法无需一致),具备良好的扩展性。

命令模式通常适用于以下场景。

  1. 请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。
  2. 系统随机请求命令或经常增加、删除命令时,命令模式可以方便地实现这些功能。
  3. 当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
  4. 当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。

    11.5、命令模式的注意事项

  5. 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。

  6. 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
  7. 容易实现对请求的撤销和重做
  8. 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
  9. 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
  10. 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制

12、访问者模式

在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。

这样的例子还有很多,例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。

这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。

12.1、模式的定义和特点

访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。

  1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。


访问者(Visitor)模式的主要缺点如下。

  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”
  2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

12.2、模式的结构和实现

问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类,其基本结构与实现方法如下。

1、 模式的结构

访问者模式包含以下主要角色。

  1. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  2. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  3. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  4. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  5. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。


其结构图如图 1 所示。
image.png

2、模式的实现

访问者模式的实现代码如下:

  1. package net.biancheng.c.visitor;
  2. import java.util.*;
  3. public class VisitorPattern {
  4. public static void main(String[] args) {
  5. ObjectStructure os = new ObjectStructure();
  6. os.add(new ConcreteElementA());
  7. os.add(new ConcreteElementB());
  8. Visitor visitor = new ConcreteVisitorA();
  9. os.accept(visitor);
  10. System.out.println("------------------------");
  11. visitor = new ConcreteVisitorB();
  12. os.accept(visitor);
  13. }
  14. }
  15. //抽象访问者
  16. interface Visitor {
  17. void visit(ConcreteElementA element);
  18. void visit(ConcreteElementB element);
  19. }
  20. //具体访问者A类
  21. class ConcreteVisitorA implements Visitor {
  22. public void visit(ConcreteElementA element) {
  23. System.out.println("具体访问者A访问-->" + element.operationA());
  24. }
  25. public void visit(ConcreteElementB element) {
  26. System.out.println("具体访问者A访问-->" + element.operationB());
  27. }
  28. }
  29. //具体访问者B类
  30. class ConcreteVisitorB implements Visitor {
  31. public void visit(ConcreteElementA element) {
  32. System.out.println("具体访问者B访问-->" + element.operationA());
  33. }
  34. public void visit(ConcreteElementB element) {
  35. System.out.println("具体访问者B访问-->" + element.operationB());
  36. }
  37. }
  38. //抽象元素类
  39. interface Element {
  40. void accept(Visitor visitor);
  41. }
  42. //具体元素A类
  43. class ConcreteElementA implements Element {
  44. public void accept(Visitor visitor) {
  45. visitor.visit(this);
  46. }
  47. public String operationA() {
  48. return "具体元素A的操作。";
  49. }
  50. }
  51. //具体元素B类
  52. class ConcreteElementB implements Element {
  53. public void accept(Visitor visitor) {
  54. visitor.visit(this);
  55. }
  56. public String operationB() {
  57. return "具体元素B的操作。";
  58. }
  59. }
  60. //对象结构角色
  61. class ObjectStructure {
  62. private List<Element> list = new ArrayList<Element>();
  63. public void accept(Visitor visitor) {
  64. Iterator<Element> i = list.iterator();
  65. while (i.hasNext()) {
  66. ((Element) i.next()).accept(visitor);
  67. }
  68. }
  69. public void add(Element element) {
  70. list.add(element);
  71. }
  72. public void remove(Element element) {
  73. list.remove(element);
  74. }
  75. }
  1. 具体访问者A访问-->具体元素A的操作。
  2. 具体访问者A访问-->具体元素B的操作。
  3. ------------------------
  4. 具体访问者B访问-->具体元素A的操作。
  5. 具体访问者B访问-->具体元素B的操作。

12.3、应用实例

1、利用“访问者(Visitor)模式”模拟艺术公司与造币公司的功能。

分析:艺术公司利用“铜”可以设计出铜像,利用“纸”可以画出图画;造币公司利用“铜”可以印出铜币,利用“纸”可以印出纸币。对“铜”和“纸”这两种元素,两个公司的处理方法不同,所以该实例用访问者模式来实现比较适合。

首先,定义一个公司(Company)接口,它是抽象访问者,提供了两个根据纸(Paper)或铜(Cuprum)这两种元素创建作品的方法;再定义艺术公司(ArtCompany)类和造币公司(Mint)类,它们是具体访问者,实现了父接口的方法。

然后,定义一个材料(Material)接口,它是抽象元素,提供了 accept(Company visitor)方法来接受访问者(Company)对象访问;再定义纸(Paper)类和铜(Cuprum)类,它们是具体元素类,实现了父接口中的方法。

最后,定义一个材料集(SetMaterial)类,它是对象结构角色,拥有保存所有元素的容器 List,并提供让访问者对象遍历容器中的所有元素的 accept(Company visitor)方法;客户类设计成窗体程序,它提供材料集(SetMaterial)对象供访问者(Company)对象访问,实现了 ItemListener 接口,处理用户的事件请求。图 2 所示是其结构图。
image.png

  1. package net.biancheng.c.visitor;
  2. import javax.swing.*;
  3. import java.awt.event.ItemEvent;
  4. import java.awt.event.ItemListener;
  5. import java.util.ArrayList;
  6. import java.util.Iterator;
  7. import java.util.List;
  8. public class VisitorProducer {
  9. public static void main(String[] args) {
  10. new MaterialWin();
  11. }
  12. }
  13. //窗体类
  14. class MaterialWin extends JFrame implements ItemListener {
  15. private static final long serialVersionUID = 1L;
  16. JPanel CenterJP;
  17. SetMaterial os; //材料集对象
  18. Company visitor1, visitor2; //访问者对象
  19. String[] select;
  20. MaterialWin() {
  21. super("利用访问者模式设计艺术公司和造币公司");
  22. JRadioButton Art;
  23. JRadioButton mint;
  24. os = new SetMaterial();
  25. os.add(new Cuprum());
  26. os.add(new Paper());
  27. visitor1 = new ArtCompany();//艺术公司
  28. visitor2 = new Mint(); //造币公司
  29. this.setBounds(10, 10, 750, 350);
  30. this.setResizable(false);
  31. CenterJP = new JPanel();
  32. this.add("Center", CenterJP);
  33. JPanel SouthJP = new JPanel();
  34. JLabel yl = new JLabel("原材料有:铜和纸,请选择生产公司:");
  35. Art = new JRadioButton("艺术公司", true);
  36. mint = new JRadioButton("造币公司");
  37. Art.addItemListener(this);
  38. mint.addItemListener(this);
  39. ButtonGroup group = new ButtonGroup();
  40. group.add(Art);
  41. group.add(mint);
  42. SouthJP.add(yl);
  43. SouthJP.add(Art);
  44. SouthJP.add(mint);
  45. this.add("South", SouthJP);
  46. select = (os.accept(visitor1)).split(" "); //获取产品名
  47. showPicture(select[0], select[1]); //显示产品
  48. }
  49. //显示图片
  50. void showPicture(String Cuprum, String paper) {
  51. CenterJP.removeAll(); //清除面板内容
  52. CenterJP.repaint(); //刷新屏幕
  53. String FileName1 = "src/visitor/Picture/" + Cuprum + ".jpg";
  54. String FileName2 = "src/visitor/Picture/" + paper + ".jpg";
  55. JLabel lb = new JLabel(new ImageIcon(FileName1), JLabel.CENTER);
  56. JLabel rb = new JLabel(new ImageIcon(FileName2), JLabel.CENTER);
  57. CenterJP.add(lb);
  58. CenterJP.add(rb);
  59. this.setVisible(true);
  60. this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  61. }
  62. @Override
  63. public void itemStateChanged(ItemEvent arg0) {
  64. JRadioButton jc = (JRadioButton) arg0.getSource();
  65. if (jc.isSelected()) {
  66. if (jc.getText() == "造币公司") {
  67. select = (os.accept(visitor2)).split(" ");
  68. } else {
  69. select = (os.accept(visitor1)).split(" ");
  70. }
  71. showPicture(select[0], select[1]); //显示选择的产品
  72. }
  73. }
  74. }
  75. //抽象访问者:公司
  76. interface Company {
  77. String create(Paper element);
  78. String create(Cuprum element);
  79. }
  80. //具体访问者:艺术公司
  81. class ArtCompany implements Company {
  82. public String create(Paper element) {
  83. return "讲学图";
  84. }
  85. public String create(Cuprum element) {
  86. return "朱熹铜像";
  87. }
  88. }
  89. //具体访问者:造币公司
  90. class Mint implements Company {
  91. public String create(Paper element) {
  92. return "纸币";
  93. }
  94. public String create(Cuprum element) {
  95. return "铜币";
  96. }
  97. }
  98. //抽象元素:材料
  99. interface Material {
  100. String accept(Company visitor);
  101. }
  102. //具体元素:纸
  103. class Paper implements Material {
  104. public String accept(Company visitor) {
  105. return (visitor.create(this));
  106. }
  107. }
  108. //具体元素:铜
  109. class Cuprum implements Material {
  110. public String accept(Company visitor) {
  111. return (visitor.create(this));
  112. }
  113. }
  114. //对象结构角色:材料集
  115. class SetMaterial {
  116. private List<Material> list = new ArrayList<Material>();
  117. public String accept(Company visitor) {
  118. Iterator<Material> i = list.iterator();
  119. String tmp = "";
  120. while (i.hasNext()) {
  121. tmp += ((Material) i.next()).accept(visitor) + " ";
  122. }
  123. return tmp; //返回某公司的作品集
  124. }
  125. public void add(Material element) {
  126. list.add(element);
  127. }
  128. public void remove(Material element) {
  129. list.remove(element);
  130. }
  131. }

12.4、模式的应用场景

当系统中存在类型数量稳定(固定)的一类数据结构时,可以使用访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会对数据产生任何副作用(脏数据)。

简而言之,就是当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式。

通常在以下情况可以考虑使用访问者(Visitor)模式。

  1. 对象结构相对稳定,但其操作算法经常变化的程序。
  2. 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
  3. 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

12.5、模式在源码的应用

1、在JDK中应用


Java 7 版本后,Files 类提供了 walkFileTree() 方法,该方法可以很容易的对目录下的所有文件进行遍历,需要 Path、FileVisitor 两个参数。其中,Path 是要遍历文件的路径,FileVisitor 则可以看成一个文件访问器。源码如下。

  1. package java.nio.file;
  2. public final class Files {
  3. ...
  4. public static Path walkFileTree(Path start, FileVisitor<? super Path> visitor)
  5. throws IOException
  6. {
  7. return walkFileTree(start,
  8. EnumSet.noneOf(FileVisitOption.class),
  9. Integer.MAX_VALUE,
  10. visitor);
  11. }
  12. ...
  13. }

通过访问者去遍历文件树会比较方便,比如查找文件夹内符合某个条件的文件或者某一天内所创建的文件,这个类中都提供了相对应的方法。它的实现也非常简单,代码如下。

  1. public class SimpleFileVisitor<T> implements FileVisitor<T> {
  2. protected SimpleFileVisitor() {
  3. }
  4. @Override
  5. public FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
  6. throws IOException
  7. {
  8. Objects.requireNonNull(dir);
  9. Objects.requireNonNull(attrs);
  10. return FileVisitResult.CONTINUE;
  11. }
  12. @Override
  13. public FileVisitResult visitFile(T file, BasicFileAttributes attrs)
  14. throws IOException
  15. {
  16. Objects.requireNonNull(file);
  17. Objects.requireNonNull(attrs);
  18. return FileVisitResult.CONTINUE;
  19. }
  20. @Override
  21. public FileVisitResult visitFileFailed(T file, IOException exc)
  22. throws IOException
  23. {
  24. Objects.requireNonNull(file);
  25. throw exc;
  26. }
  27. @Override
  28. public FileVisitResult postVisitDirectory(T dir, IOException exc)
  29. throws IOException
  30. {
  31. Objects.requireNonNull(dir);
  32. if (exc != null)
  33. throw exc;
  34. return FileVisitResult.CONTINUE;
  35. }
  36. }

2、模式在spring中的使用

在 Spring IoC 中,BeanDefinition 用来存储 Spring Bean 的定义信息,比如属性值、构造方法参数或者更具体的实现。Spring 解析完配置后,会生成 BeanDefinition 并且记录下来。下次 getBean 获取 Bean 时,会通过 BeanDefinition 来实例化具体的 Bean 对象。

BeanDefinition 是一个接口,即一个抽象的定义,实际使用的是其实现类,如 ChildBeanDefinition、RootBeanDefinition、GenericBeanDefinition 等。

Spring 中的 BeanDefinitionVisitor 类主要用于访问 BeanDefinition,解析属性或者构造方法里面的占位符,并把解析结果更新到 BeanDefinition 中。这里应用的就是访问者模式。抽象元素为 BeanDefinition,具体元素有 RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition 等。

因为没有对访问者进行扩展,所以这里只有一个具体访问者 BeanDefinitionVisitor,没有再抽出一层抽象访问者。

BeanDefinitionVisitor 类的源码如下。

  1. public class BeanDefinitionVisitor {
  2. @Nullable
  3. private StringValueResolver valueResolver;
  4. public BeanDefinitionVisitor(StringValueResolver valueResolver) {
  5. Assert.notNull(valueResolver, "StringValueResolver must not be null");
  6. this.valueResolver = valueResolver;
  7. }
  8. protected BeanDefinitionVisitor() {
  9. }
  10. public void visitBeanDefinition(BeanDefinition beanDefinition) {
  11. visitParentName(beanDefinition);
  12. visitBeanClassName(beanDefinition);
  13. visitFactoryBeanName(beanDefinition);
  14. visitFactoryMethodName(beanDefinition);
  15. visitScope(beanDefinition);
  16. if (beanDefinition.hasPropertyValues()) {
  17. visitPropertyValues(beanDefinition.getPropertyValues());
  18. }
  19. if (beanDefinition.hasConstructorArgumentValues()) {
  20. ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
  21. visitIndexedArgumentValues(cas.getIndexedArgumentValues());
  22. visitGenericArgumentValues(cas.getGenericArgumentValues());
  23. }
  24. }
  25. protected void visitParentName(BeanDefinition beanDefinition) {
  26. String parentName = beanDefinition.getParentName();
  27. if (parentName != null) {
  28. String resolvedName = resolveStringValue(parentName);
  29. if (!parentName.equals(resolvedName)) {
  30. beanDefinition.setParentName(resolvedName);
  31. }
  32. }
  33. }
  34. ...
  35. }

以上没有使用双重分派模式,直接调用 visit 进行元素的访问。visitBeanDefinition() 方法分别实现了不同的 visit 来对相同的数据进行不同的处理。我们看到,在 visitBeanDefinition() 方法中,访问了其它数据,比如父类的名字、自己的类名、在 IoC 容器中的名称等各种信息。

13、迭代器模式

13.1、迭代器模式概念

既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:

  1. 暴露了聚合类的内部表示,使其数据不安全;
  2. 增加了客户的负担。


“迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java 中的 Collection、List、Set、Map 等都包含了迭代器。

迭代器模式在生活中应用的比较广泛,比如:物流系统中的传送带,不管传送的是什么物品,都会被打包成一个个箱子,并且有一个统一的二维码。这样我们不需要关心箱子里是什么,在分发时只需要一个个检查发送的目的地即可。再比如,我们平时乘坐交通工具,都是统一刷卡或者刷脸进站,而不需要关心是男性还是女性、是残疾人还是正常人等信息。
迭代器(Iterator)模式的定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式,其主要优点如下。

  1. 访问一个聚合对象的内容而无须暴露它的内部表示。
  2. 遍历任务交由迭代器完成,这简化了聚合类。
  3. 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
  4. 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
  5. 封装性良好,为遍历不同的聚合结构提供一个统一的接口。


其主要缺点是:增加了类的个数,这在一定程度上增加了系统的复杂性。

在日常开发中,我们几乎不会自己写迭代器。除非需要定制一个自己实现的数据结构对应的迭代器,否则,开源框架提供的 API 完全够用。

13.2、迭代器的结构

迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。现在我们来分析其基本结构与实现方法。

1、模式的结构

迭代器模式主要包含以下角色。

  1. 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  2. 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  3. 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  4. 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。


其结构图如图 1 所示。
image.png

2、模式的实现

迭代器模式的实现代码如下:

  1. package net.biancheng.c.iterator;
  2. import java.util.*;
  3. public class IteratorPattern {
  4. public static void main(String[] args) {
  5. Aggregate ag = new ConcreteAggregate();
  6. ag.add("中山大学");
  7. ag.add("华南理工");
  8. ag.add("韶关学院");
  9. System.out.print("聚合的内容有:");
  10. Iterator it = ag.getIterator();
  11. while (it.hasNext()) {
  12. Object ob = it.next();
  13. System.out.print(ob.toString() + "\t");
  14. }
  15. Object ob = it.first();
  16. System.out.println("\nFirst:" + ob.toString());
  17. }
  18. }
  19. //抽象聚合
  20. interface Aggregate {
  21. public void add(Object obj);
  22. public void remove(Object obj);
  23. public Iterator getIterator();
  24. }
  25. //具体聚合
  26. class ConcreteAggregate implements Aggregate {
  27. private List<Object> list = new ArrayList<Object>();
  28. public void add(Object obj) {
  29. list.add(obj);
  30. }
  31. public void remove(Object obj) {
  32. list.remove(obj);
  33. }
  34. public Iterator getIterator() {
  35. return (new ConcreteIterator(list));
  36. }
  37. }
  38. //抽象迭代器
  39. interface Iterator {
  40. Object first();
  41. Object next();
  42. boolean hasNext();
  43. }
  44. //具体迭代器
  45. class ConcreteIterator implements Iterator {
  46. private List<Object> list = null;
  47. private int index = -1;
  48. public ConcreteIterator(List<Object> list) {
  49. this.list = list;
  50. }
  51. public boolean hasNext() {
  52. if (index < list.size() - 1) {
  53. return true;
  54. } else {
  55. return false;
  56. }
  57. }
  58. public Object first() {
  59. index = 0;
  60. Object obj = list.get(index);
  61. ;
  62. return obj;
  63. }
  64. public Object next() {
  65. Object obj = null;
  66. if (this.hasNext()) {
  67. obj = list.get(++index);
  68. }
  69. return obj;
  70. }
  71. }

程序运行结果如下:

  1. 聚合的内容有:中山大学 华南理工 韶关学院
  2. First:中山大学

13.3、应用实例

  1. package com.atguigu.iterator;
  2. //系
  3. public class Department {
  4. private String name;
  5. private String desc;
  6. public Department(String name, String desc) {
  7. super();
  8. this.name = name;
  9. this.desc = desc;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public String getDesc() {
  18. return desc;
  19. }
  20. public void setDesc(String desc) {
  21. this.desc = desc;
  22. }
  23. }

这里也可以实现通过使用自定义的Iterator迭代器的接口 实现几种简单的方法。

  1. package com.atguigu.iterator;
  2. import java.util.Iterator;
  3. public class ComputerCollegeIterator implements Iterator {
  4. //这里我们需要 Department 是以怎样的方式存放=>数组
  5. Department[] departments;
  6. int position = 0; //遍历的位置
  7. public ComputerCollegeIterator(Department[] departments) {
  8. this.departments = departments;
  9. }
  10. //判断是否还有下一个元素
  11. @Override
  12. public boolean hasNext() {
  13. // TODO Auto-generated method stub
  14. if(position >= departments.length || departments[position] == null) {
  15. return false;
  16. }else {
  17. return true;
  18. }
  19. }
  20. @Override
  21. public Object next() {
  22. // TODO Auto-generated method stub
  23. Department department = departments[position];
  24. position += 1;
  25. return department;
  26. }
  27. //删除的方法,默认空实现
  28. public void remove() {
  29. }
  30. }
  1. package com.atguigu.iterator;
  2. import java.util.Iterator;
  3. import java.util.List;
  4. public class InfoColleageIterator implements Iterator {
  5. List<Department> departmentList; // 信息工程学院是以 List 方式存放系
  6. int index = -1;//索引
  7. public InfoColleageIterator(List<Department> departmentList) {
  8. this.departmentList = departmentList;
  9. }
  10. //判断 list 中还有没有下一个元素
  11. @Override
  12. public boolean hasNext() {
  13. // TODO Auto-generated method stub
  14. if(index >= departmentList.size() - 1) {
  15. //满足条件,说明没有下一个
  16. return false;
  17. } else {
  18. index += 1;
  19. return true;
  20. }
  21. }
  22. @Override
  23. public Object next() {
  24. //获取下一个
  25. // TODO Auto-generated method stub
  26. return departmentList.get(index);
  27. }
  28. //空实现 remove
  29. public void remove() {
  30. }
  31. }
  1. package com.atguigu.iterator;
  2. import java.util.Iterator;
  3. public interface College {
  4. public String getName();
  5. //增加系的方法
  6. public void addDepartment(String name, String desc);
  7. //返回一个迭代器,遍历
  8. public Iterator createIterator();
  9. }
  1. package com.atguigu.iterator;
  2. import java.util.Iterator;
  3. public class ComputerCollege implements College {
  4. Department[] departments;
  5. int numOfDepartment = 0 ;// 保存当前数组的对象个数
  6. public ComputerCollege() {
  7. departments = new Department[5];
  8. addDepartment("Java 专业", " Java 专业 ");
  9. addDepartment("PHP 专业", " PHP 专业 ");
  10. addDepartment("大数据专业", " 大数据专业 ");
  11. }
  12. @Override
  13. public String getName() {
  14. // TODO Auto-generated method stub
  15. return "计算机学院";
  16. }
  17. @Override
  18. public void addDepartment(String name, String desc) {
  19. // TODO Auto-generated method stub
  20. Department department = new Department(name, desc);
  21. departments[numOfDepartment] = department;
  22. numOfDepartment += 1;
  23. }
  24. @Override
  25. public Iterator createIterator() {
  26. // TODO Auto-generated method stub
  27. return new ComputerCollegeIterator(departments);
  28. }
  29. }
  1. package com.atguigu.iterator;
  2. import java.util.ArrayList;
  3. import java.util.Iterator;
  4. import java.util.List;
  5. public class InfoCollege implements College {
  6. List<Department> departmentList;
  7. public InfoCollege() {
  8. departmentList = new ArrayList<Department>();
  9. addDepartment("信息安全专业", " 信息安全专业 ");
  10. addDepartment("网络安全专业", " 网络安全专业 ");
  11. addDepartment("服务器安全专业", " 服务器安全专业 ");
  12. }
  13. @Override
  14. public String getName() {
  15. // TODO Auto-generated method stub
  16. return "信息工程学院";
  17. }
  18. @Override
  19. public void addDepartment(String name, String desc) {
  20. // TODO Auto-generated method stub
  21. Department department = new Department(name, desc);
  22. departmentList.add(department);
  23. }
  24. @Override
  25. public Iterator createIterator() {
  26. // TODO Auto-generated method stub
  27. return new InfoColleageIterator(departmentList);
  28. }
  29. }
  1. package com.atguigu.iterator;
  2. import java.util.Iterator;
  3. import java.util.List;
  4. public class OutPutImpl {
  5. //学院集合
  6. List<College> collegeList;
  7. public OutPutImpl(List<College> collegeList) {
  8. this.collegeList = collegeList;
  9. }
  10. //遍历所有学院,然后调用 printDepartment 输出各个学院的系
  11. public void printCollege() {
  12. //从 collegeList 取出所有学院, Java 中的 List 已经实现 Iterator
  13. Iterator<College> iterator = collegeList.iterator();
  14. while(iterator.hasNext()) {
  15. //取出一个学院
  16. College college = iterator.next();
  17. System.out.println("=== "+college.getName() +"=====" );
  18. printDepartment(college.createIterator()); //得到对应迭代器
  19. }
  20. }
  21. //输出 学院输出 系
  22. public void printDepartment(Iterator iterator) {
  23. while(iterator.hasNext()) {
  24. Department d = (Department)iterator.next();
  25. System.out.println(d.getName());
  26. }
  27. }
  28. }
  1. package com.atguigu.iterator;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class Client {
  5. public static void main(String[] args) {
  6. // TODO Auto-generated method stub
  7. //创建学院
  8. List<College> collegeList = new ArrayList<College>();
  9. ComputerCollege computerCollege = new ComputerCollege();
  10. InfoCollege infoCollege = new InfoCollege();
  11. collegeList.add(computerCollege);
  12. collegeList.add(infoCollege);
  13. OutPutImpl outPutImpl = new OutPutImpl(collegeList);
  14. outPutImpl.printCollege();
  15. }
  16. }

13.4、应用场景

迭代器模式是与集合共生共死的,一般来说,我们只要实现一个集合,就需要同时提供这个集合的迭代器,就像java中的Collection,List、Set、Map等,这些集合都有自己的迭代器。假如我们要实现一个这样的新的容器,当然也需要引入迭代器模式,给我们的容器实现一个迭代器。
但是,由于容器与迭代器的关系太密切了,所以大多数语言在实现容器的时候都给提供了迭代器,并且这些语言提供的容器和迭代器在绝大多数情况下就可以满足我们的需要,所以现在需要我们自己去实践迭代器模式的场景还是比较少见的,我们只需要使用语言中已有的容器和迭代器就可以了。

13.5、迭代器模式的优缺点

优点
①简化了遍历方式,对于对象集合的遍历,还是比较麻烦的,对于数组或者有序列表,我们尚可以通过游标来取得,但用户需要在对集合了解很清楚的前提下,自行遍历对象,但是对于hash表来说,用户遍历起来就比较麻烦了。而引入了迭代器方法后,用户用起来就简单的多了。
②可以提供多种遍历方式,比如说对有序列表,我们可以根据需要提供正序遍历,倒序遍历两种迭代器,用户用起来只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。
③封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。
缺点
对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐,大家可能都有感觉,像ArrayList,我们宁可愿意使用for循环和get方法来遍历集合。

13.6、迭代器在应用场景

1、迭代器模式在JDK中的Iterator迭代器使用到了。
2、迭代器模式在Mybatis的使用。

14、观察者模式

14.1、观察者模式的概念

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式是一种对象行为型模式,其主要优点如下。

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
  2. 目标与观察者之间建立了一套触发机制。


它的主要缺点如下。

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

在许多设计中,经常涉及多个对象都对一个特殊对象中的数据变化感兴趣,而且这多个对象都希望跟踪那个特殊对象中的数据变化,也就是说当对象间存在一对多关系时,在这样的情况下就可以使用观察者模式。当一个对象被修改时,则会自动通知它的依赖对象。
观察者模式是关于多个对象想知道一个对象中数据变化情况的一种成熟的模式。观察者模式中有一个称作“主题”的对象和若干个称作“观察者”的对象,“主题”和“观察者”间是一种一对多的依赖关系,当“主题”的状态发生变化时,所有“观察者”都得到通知。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

14.2、观察者模式的结构

image.png
观察者模式的结构中包含四种角色:
(1)主题(Subject):主题是一个接口,该接口规定了具体主题需要实现的方法,比如,添加、删除观察者以及通知观察者更新数据的方法。
(2)观察者(Observer):观察者是一个接口,该接口规定了具体观察者用来更新数据的方法。
(3)具体主题(ConcreteSubject):具体主题是实现主题接口类的一个实例,该实例包含有可以经常发生变化的数据。具体主题需使用一个集合,比如ArrayList,存放观察者的引用,以便数据变化时通知具体观察者。
(4)具体观察者(ConcreteObserver):具体观察者是实现观察者接口类的一个实例。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,使自己成为它的观察者,或让这个具体主题将自己从具体主题的集合中删除,使自己不再是它的观察者。

14.3、观察者模式的实现

观察者模式的实现代码如下:

  1. package net.biancheng.c.observer;
  2. import java.util.*;
  3. public class ObserverPattern {
  4. public static void main(String[] args) {
  5. Subject subject = new ConcreteSubject();
  6. Observer obs1 = new ConcreteObserver1();
  7. Observer obs2 = new ConcreteObserver2();
  8. subject.add(obs1);
  9. subject.add(obs2);
  10. subject.notifyObserver();
  11. }
  12. }
  13. //抽象目标
  14. abstract class Subject {
  15. protected List<Observer> observers = new ArrayList<Observer>();
  16. //增加观察者方法
  17. public void add(Observer observer) {
  18. observers.add(observer);
  19. }
  20. //删除观察者方法
  21. public void remove(Observer observer) {
  22. observers.remove(observer);
  23. }
  24. public abstract void notifyObserver(); //通知观察者方法
  25. }
  26. //具体目标
  27. class ConcreteSubject extends Subject {
  28. public void notifyObserver() {
  29. System.out.println("具体目标发生改变...");
  30. System.out.println("--------------");
  31. for (Object obs : observers) {
  32. ((Observer) obs).response();
  33. }
  34. }
  35. }
  36. //抽象观察者
  37. interface Observer {
  38. void response(); //反应
  39. }
  40. //具体观察者1
  41. class ConcreteObserver1 implements Observer {
  42. public void response() {
  43. System.out.println("具体观察者1作出反应!");
  44. }
  45. }
  46. //具体观察者1
  47. class ConcreteObserver2 implements Observer {
  48. public void response() {
  49. System.out.println("具体观察者2作出反应!");
  50. }
  51. }
  1. 具体目标发生改变...
  2. --------------
  3. 具体观察者1作出反应!
  4. 具体观察者2作出反应!

【例1】利用观察者模式设计一个程序,分析“人民币汇率”的升值或贬值对进口公司进口产品成本或出口公司的出口产品收入以及公司利润率的影响。

分析:当“人民币汇率”升值时,进口公司的进口产品成本降低且利润率提升,出口公司的出口产品收入降低且利润率降低;当“人民币汇率”贬值时,进口公司的进口产品成本提升且利润率降低,出口公司的出口产品收入提升且利润率提升。

这里的汇率(Rate)类是抽象目标类,它包含了保存观察者(Company)的 List 和增加/删除观察者的方法,以及有关汇率改变的抽象方法 change(int number);而人民币汇率(RMBrate)类是具体目标, 它实现了父类的 change(int number) 方法,即当人民币汇率发生改变时通过相关公司;公司(Company)类是抽象观察者,它定义了一个有关汇率反应的抽象方法 response(int number);进口公司(ImportCompany)类和出口公司(ExportCompany)类是具体观察者类,它们实现了父类的 response(int number) 方法,即当它们接收到汇率发生改变的通知时作为相应的反应。图 2 所示是其结构图。
image.png
程序代码如下:

  1. package net.biancheng.c.observer;
  2. import java.util.*;
  3. public class RMBrateTest {
  4. public static void main(String[] args) {
  5. Rate rate = new RMBrate();
  6. Company watcher1 = new ImportCompany();
  7. Company watcher2 = new ExportCompany();
  8. rate.add(watcher1);
  9. rate.add(watcher2);
  10. rate.change(10);
  11. rate.change(-9);
  12. }
  13. }
  14. //抽象目标:汇率
  15. abstract class Rate {
  16. protected List<Company> companys = new ArrayList<Company>();
  17. //增加观察者方法
  18. public void add(Company company) {
  19. companys.add(company);
  20. }
  21. //删除观察者方法
  22. public void remove(Company company) {
  23. companys.remove(company);
  24. }
  25. public abstract void change(int number);
  26. }
  27. //具体目标:人民币汇率
  28. class RMBrate extends Rate {
  29. public void change(int number) {
  30. for (Company obs : companys) {
  31. ((Company) obs).response(number);
  32. }
  33. }
  34. }
  35. //抽象观察者:公司
  36. interface Company {
  37. void response(int number);
  38. }
  39. //具体观察者1:进口公司
  40. class ImportCompany implements Company {
  41. public void response(int number) {
  42. if (number > 0) {
  43. System.out.println("人民币汇率升值" + number + "个基点,降低了进口产品成本,提升了进口公司利润率。");
  44. } else if (number < 0) {
  45. System.out.println("人民币汇率贬值" + (-number) + "个基点,提升了进口产品成本,降低了进口公司利润率。");
  46. }
  47. }
  48. }
  49. //具体观察者2:出口公司
  50. class ExportCompany implements Company {
  51. public void response(int number) {
  52. if (number > 0) {
  53. System.out.println("人民币汇率升值" + number + "个基点,降低了出口产品收入,降低了出口公司的销售利润率。");
  54. } else if (number < 0) {
  55. System.out.println("人民币汇率贬值" + (-number) + "个基点,提升了出口产品收入,提升了出口公司的销售利润率。");
  56. }
  57. }
  58. }
  1. 人民币汇率升值10个基点,降低了进口产品成本,提升了进口公司利润率。
  2. 人民币汇率升值10个基点,降低了出口产品收入,降低了出口公司的销售利润率。
  3. 人民币汇率贬值9个基点,提升了进口产品成本,降低了进口公司利润率。
  4. 人民币汇率贬值9个基点,提升了出口产品收入,提升了出口公司的销售利润率。


观察者模式在软件幵发中用得最多的是窗体程序设计中的事件处理,窗体中的所有组件都是“事件源”,也就是目标对象,而事件处理程序类的对象是具体观察者对象。下面以一个学校铃声的事件处理程序为例,介绍 Windows 中的“事件处理模型”的工作原理。
例2】利用观察者模式设计一个学校铃声的事件处理程序。

分析:在本实例中,学校的“铃”是事件源和目标,“老师”和“学生”是事件监听器和具体观察者,“铃声”是事件类。学生和老师来到学校的教学区,都会注意学校的铃,这叫事件绑定;当上课时间或下课时间到,会触发铃发声,这时会生成“铃声”事件;学生和老师听到铃声会开始上课或下课,这叫事件处理。这个实例非常适合用观察者模式实现,图 3 给出了学校铃声的事件模型。
image.png

现在用“观察者模式”来实现该事件处理模型。

首先,定义一个铃声事件(RingEvent)类,它记录了铃声的类型(上课铃声/下课铃声)。

再定义一个学校的铃(BellEventSource)类,它是事件源,是观察者目标类,该类里面包含了监听器容器 listener,可以绑定监听者(学生或老师),并且有产生铃声事件和通知所有监听者的方法。

然后,定义铃声事件监听者(BellEventListener)类,它是抽象观察者,它包含了铃声事件处理方法 heardBell(RingEvent e)。

最后,定义老师类(TeachEventListener)和学生类(StuEventListener),它们是事件监听器,是具体观察者,听到铃声会去上课或下课。图 4 给出了学校铃声事件处理程序的结构。
image.png

  1. package net.biancheng.c.observer;
  2. import java.util.*;
  3. public class BellEventTest {
  4. public static void main(String[] args) {
  5. BellEventSource bell = new BellEventSource(); //铃(事件源)
  6. bell.addPersonListener(new TeachEventListener()); //注册监听器(老师)
  7. bell.addPersonListener(new StuEventListener()); //注册监听器(学生)
  8. bell.ring(true); //打上课铃声
  9. System.out.println("------------");
  10. bell.ring(false); //打下课铃声
  11. }
  12. }
  13. //铃声事件类:用于封装事件源及一些与事件相关的参数
  14. class RingEvent extends EventObject {
  15. private static final long serialVersionUID = 1L;
  16. private boolean sound; //true表示上课铃声,false表示下课铃声
  17. public RingEvent(Object source, boolean sound) {
  18. super(source);
  19. this.sound = sound;
  20. }
  21. public void setSound(boolean sound) {
  22. this.sound = sound;
  23. }
  24. public boolean getSound() {
  25. return this.sound;
  26. }
  27. }
  28. //目标类:事件源,铃
  29. class BellEventSource {
  30. private List<BellEventListener> listener; //监听器容器
  31. public BellEventSource() {
  32. listener = new ArrayList<BellEventListener>();
  33. }
  34. //给事件源绑定监听器
  35. public void addPersonListener(BellEventListener ren) {
  36. listener.add(ren);
  37. }
  38. //事件触发器:敲钟,当铃声sound的值发生变化时,触发事件。
  39. public void ring(boolean sound) {
  40. String type = sound ? "上课铃" : "下课铃";
  41. System.out.println(type + "响!");
  42. RingEvent event = new RingEvent(this, sound);
  43. notifies(event); //通知注册在该事件源上的所有监听器
  44. }
  45. //当事件发生时,通知绑定在该事件源上的所有监听器做出反应(调用事件处理方法)
  46. protected void notifies(RingEvent e) {
  47. BellEventListener ren = null;
  48. Iterator<BellEventListener> iterator = listener.iterator();
  49. while (iterator.hasNext()) {
  50. ren = iterator.next();
  51. ren.heardBell(e);
  52. }
  53. }
  54. }
  55. //抽象观察者类:铃声事件监听器
  56. interface BellEventListener extends EventListener {
  57. //事件处理方法,听到铃声
  58. public void heardBell(RingEvent e);
  59. }
  60. //具体观察者类:老师事件监听器
  61. class TeachEventListener implements BellEventListener {
  62. public void heardBell(RingEvent e) {
  63. if (e.getSound()) {
  64. System.out.println("老师上课了...");
  65. } else {
  66. System.out.println("老师下课了...");
  67. }
  68. }
  69. }
  70. //具体观察者类:学生事件监听器
  71. class StuEventListener implements BellEventListener {
  72. public void heardBell(RingEvent e) {
  73. if (e.getSound()) {
  74. System.out.println("同学们,上课了...");
  75. } else {
  76. System.out.println("同学们,下课了...");
  77. }
  78. }
  79. }
  1. 上课铃响!
  2. 老师上课了...
  3. 同学们,上课了...
  4. ------------
  5. 下课铃响!
  6. 老师下课了...
  7. 同学们,下课了...

14.4、应用场景

在软件系统中,当系统一方行为依赖另一方行为的变动时,可使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。

通过前面的分析与应用实例可知观察者模式适合以下几种情形。

  1. 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  2. 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  3. 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
  4. 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。

14.5、模式优缺点

观察者模式的效果有以下的优点:

  1. 观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。如果被观察者和观察者都被扔到一起,那么这个对象必然跨越抽象化和具体化层次。
  2. 观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知,

观察者模式有下面的缺点:

  1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  2. 如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。
  3. 如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
  4. 虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。

    14.6、观察者模式在Spring中的应用

    观察者模式在Spring中的主要体现在事件监听,事件机制的实现需要三个部分,事件源,事件,事件监听器;
    1 ApplicationEvent抽象类作为事件的父类,通过source获取事件源。 ```java public abstract class ApplicationEvent extends EventObject { private static final long serialVersionUID = 7099057708183571937L; private final long timestamp = System.currentTimeMillis();

    public ApplicationEvent(Object source) {

    1. super(source);

    }

    public final long getTimestamp() {

    1. return this.timestamp;

    } }

  1. 2 ApplicationListener接口作为事件监听器,继承自EventListener
  2. ```java
  3. @FunctionalInterface
  4. public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
  5. void onApplicationEvent(E event);
  6. }

3 ApplicationContext接口作为事件源,在这里可以将监听器注册进应用上下文中,也可以触发指定的事件。在ApplicationContext的父类ApplicationEventPublisher中有这样一个方法publishEvent,用以发布事件,具体的实现类在AbstractApplicationContext中。

  1. default void publishEvent(ApplicationEvent event) {
  2. this.publishEvent((Object)event);
  3. }
  4. void publishEvent(Object var1);

15、中介者模式

15.1、中介者模式的概述

说道这你肯定想起了Spring, Spring的一个主要功能就是IOC,各层间、层内不同对象也是不需要事先注入绑定,而是将创建bean的事情全交给Spring处理,Spring负责各方的联系。在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者;

中介者模式是迪米特原则的经典体现:迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),它要求一个对象应该对其他对象保持最少的了解。如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。 大大的降低了他们之间的耦合性。

image.png
中介者(Mediator)模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

中介者模式是一种对象行为型模式,其主要优点如下。

  1. 类之间各司其职,符合迪米特法则
  2. 降低了对象之间的耦合性,使得对象易于独立地被复用。
  3. 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。


其主要缺点是:中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。

15.2、中介者模式的结构

中介者模式实现的关键是找出“中介者”,下面对它的结构和实现进行分析。
中介者模式包含以下主要角色。

  1. 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  2. 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  3. 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  4. 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。


中介者模式的结构图如图 1 所示。
image.png

  1. package net.biancheng.c.mediator;
  2. import java.util.*;
  3. public class MediatorPattern {
  4. public static void main(String[] args) {
  5. Mediator md = new ConcreteMediator();
  6. Colleague c1, c2;
  7. c1 = new ConcreteColleague1();
  8. c2 = new ConcreteColleague2();
  9. md.register(c1);
  10. md.register(c2);
  11. c1.send();
  12. System.out.println("-------------");
  13. c2.send();
  14. }
  15. }
  16. //抽象中介者
  17. abstract class Mediator {
  18. public abstract void register(Colleague colleague);
  19. public abstract void relay(Colleague cl); //转发
  20. }
  21. //具体中介者
  22. class ConcreteMediator extends Mediator {
  23. private List<Colleague> colleagues = new ArrayList<Colleague>();
  24. public void register(Colleague colleague) {
  25. if (!colleagues.contains(colleague)) {
  26. colleagues.add(colleague);
  27. colleague.setMedium(this);
  28. }
  29. }
  30. public void relay(Colleague cl) {
  31. for (Colleague ob : colleagues) {
  32. if (!ob.equals(cl)) {
  33. ((Colleague) ob).receive();
  34. }
  35. }
  36. }
  37. }
  38. //抽象同事类
  39. abstract class Colleague {
  40. protected Mediator mediator;
  41. public void setMedium(Mediator mediator) {
  42. this.mediator = mediator;
  43. }
  44. public abstract void receive();
  45. public abstract void send();
  46. }
  47. //具体同事类
  48. class ConcreteColleague1 extends Colleague {
  49. public void receive() {
  50. System.out.println("具体同事类1收到请求。");
  51. }
  52. public void send() {
  53. System.out.println("具体同事类1发出请求。");
  54. mediator.relay(this); //请中介者转发
  55. }
  56. }
  57. //具体同事类
  58. class ConcreteColleague2 extends Colleague {
  59. public void receive() {
  60. System.out.println("具体同事类2收到请求。");
  61. }
  62. public void send() {
  63. System.out.println("具体同事类2发出请求。");
  64. mediator.relay(this); //请中介者转发
  65. }
  66. }
  1. 具体同事类1发出请求。
  2. 具体同事类2收到请求。
  3. -------------
  4. 具体同事类2发出请求。
  5. 具体同事类1收到请求。

15.3、应用实例

  1. package com.atguigu.mediator.smarthouse;
  2. //同事抽象类
  3. public abstract class Colleague {
  4. private Mediator mediator;
  5. public String name;
  6. public Colleague(Mediator mediator, String name) {
  7. this.mediator = mediator;
  8. this.name = name;
  9. }
  10. public Mediator GetMediator() {
  11. return this.mediator;
  12. }
  13. public abstract void SendMessage(int stateChange);
  14. }
  1. package com.atguigu.mediator.smarthouse;
  2. //具体的同事类
  3. public class Alarm extends Colleague {
  4. //构造器
  5. public Alarm(Mediator mediator, String name) {
  6. super(mediator, name);
  7. // TODO Auto-generated constructor stub
  8. //在创建 Alarm 同事对象时,将自己放入到 ConcreteMediator 对象中[集合]
  9. mediator.Register(name, this);
  10. }
  11. public void SendAlarm(int stateChange) {
  12. SendMessage(stateChange);
  13. }
  14. @Override
  15. public void SendMessage(int stateChange) {
  16. // TODO Auto-generated method stub
  17. //调用的中介者对象的 getMessage
  18. this.GetMediator().GetMessage(stateChange, this.name);
  19. }
  20. }
  1. package com.atguigu.mediator.smarthouse;
  2. public class CoffeeMachine extends Colleague {
  3. public CoffeeMachine(Mediator mediator, String name) {
  4. super(mediator, name);
  5. // TODO Auto-generated constructor stub
  6. mediator.Register(name, this);
  7. }
  8. @Override
  9. public void SendMessage(int stateChange) {
  10. // TODO Auto-generated method stub
  11. this.GetMediator().GetMessage(stateChange, this.name);
  12. }
  13. public void StartCoffee() {
  14. System.out.println("It's time to startcoffee!");
  15. }
  16. public void FinishCoffee() {
  17. System.out.println("After 5 minutes!");
  18. System.out.println("Coffee is ok!");
  19. SendMessage(0);
  20. }
  21. }
  1. package com.atguigu.mediator.smarthouse;
  2. public class Curtains extends Colleague {
  3. public Curtains(Mediator mediator, String name) {
  4. super(mediator, name);
  5. // TODO Auto-generated constructor stub
  6. mediator.Register(name, this);
  7. }
  8. @Override
  9. public void SendMessage(int stateChange) {
  10. // TODO Auto-generated method stub
  11. this.GetMediator().GetMessage(stateChange, this.name);
  12. }
  13. public void UpCurtains() {
  14. System.out.println("I am holding Up Curtains!");
  15. }
  16. }
  1. package com.atguigu.mediator.smarthouse;
  2. public class TV extends Colleague {
  3. public TV(Mediator mediator, String name) {
  4. super(mediator, name);
  5. // TODO Auto-generated constructor stub
  6. mediator.Register(name, this);
  7. }
  8. @Override
  9. public void SendMessage(int stateChange) {
  10. // TODO Auto-generated method stub
  11. this.GetMediator().GetMessage(stateChange, this.name);
  12. }
  13. public void StartTv() {
  14. // TODO Auto-generated method stub
  15. System.out.println("It's time to StartTv!");
  16. }
  17. public void StopTv() {
  18. // TODO Auto-generated method stub
  19. System.out.println("StopTv!");
  20. }
  21. }
  1. package com.atguigu.mediator.smarthouse;
  2. public abstract class Mediator {
  3. //将给中介者对象,加入到集合中
  4. public abstract void Register(String colleagueName, Colleague colleague);
  5. //接收消息, 具体的同事对象发出
  6. public abstract void GetMessage(int stateChange, String colleagueName);
  7. public abstract void SendMessage();
  8. }
  1. package com.atguigu.mediator.smarthouse;
  2. import java.util.HashMap;
  3. //具体的中介者类
  4. public class ConcreteMediator extends Mediator {
  5. //集合,放入所有的同事对象
  6. private HashMap<String, Colleague> colleagueMap;
  7. private HashMap<String, String> interMap;
  8. public ConcreteMediator() {
  9. colleagueMap = new HashMap<String, Colleague>();
  10. interMap = new HashMap<String, String>();
  11. }
  12. @Override
  13. public void Register(String colleagueName, Colleague colleague) {
  14. // TODO Auto-generated method stub
  15. colleagueMap.put(colleagueName, colleague);
  16. // TODO Auto-generated method stub
  17. if (colleague instanceof Alarm) {
  18. interMap.put("Alarm", colleagueName);
  19. } else if (colleague instanceof CoffeeMachine) {
  20. interMap.put("CoffeeMachine", colleagueName);
  21. } else if (colleague instanceof TV) {
  22. interMap.put("TV", colleagueName);
  23. } else if (colleague instanceof Curtains) {
  24. interMap.put("Curtains", colleagueName);
  25. }
  26. }
  27. //具体中介者的核心方法
  28. //1. 根据得到消息,完成对应任务
  29. //2. 中介者在这个方法,协调各个具体的同事对象,完成任务
  30. @Override
  31. public void GetMessage(int stateChange, String colleagueName) {
  32. // TODO Auto-generated method stub
  33. //处理闹钟发出的消息
  34. if (colleagueMap.get(colleagueName) instanceof Alarm) {
  35. if (stateChange == 0) {
  36. //
  37. ((CoffeeMachine) (colleagueMap.get(interMap.get("CoffeeMachine")))).StartCoffee();
  38. ((TV) (colleagueMap.get(interMap.get("TV")))).StartTv();
  39. } else if (stateChange == 1) {
  40. ((TV) (colleagueMap.get(interMap.get("TV")))).StopTv();
  41. }
  42. } else if (colleagueMap.get(colleagueName) instanceof CoffeeMachine) {
  43. ((Curtains) (colleagueMap.get(interMap.get("Curtains")))).UpCurtains();
  44. } else if (colleagueMap.get(colleagueName) instanceof TV) {
  45. //如果 TV 发现消息
  46. } else if (colleagueMap.get(colleagueName) instanceof Curtains) {
  47. //如果是以窗帘发出的消息,这里处理...
  48. }
  49. }
  50. @Override
  51. public void SendMessage() {
  52. // TODO Auto-generated method stub
  53. }
  54. }
  1. package com.atguigu.mediator.smarthouse;
  2. public class ClientTest {
  3. public static void main(String[] args) {
  4. //创建一个中介者对象
  5. Mediator mediator = new ConcreteMediator();
  6. //创建 Alarm 并且加入到 ConcreteMediator 对象的 HashMap
  7. Alarm alarm = new Alarm(mediator, "alarm");
  8. //创建了 CoffeeMachine 对象,并 且加入到 ConcreteMediator 对象的 HashMap
  9. CoffeeMachine coffeeMachine = new CoffeeMachine(mediator, "coffeeMachine");
  10. //创建 Curtains , 并 且加入到 ConcreteMediator 对象的 HashMap
  11. Curtains curtains = new Curtains(mediator, "curtains");
  12. TV tV = new TV(mediator, "TV");
  13. //让闹钟发出消息
  14. alarm.SendAlarm(0);
  15. coffeeMachine.FinishCoffee();
  16. alarm.SendAlarm(1);
  17. }
  18. }

15.4、中介者模式的使用场景

中介者模式很容易实现呢,但是也容易误用,不要着急使用,先要思考你的设计是否合理。
当对象之间的交互变多时,为了防止一个类会涉及修改其他类的行为,可以使用中介者模式,将系统从网状结构变为以中介者为中心的星型结构。

16、备忘录模式

16.1、备忘录模式的概述

每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。

其实很多应用软件都提供了这项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。

备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。
备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。

备忘录模式是一种对象行为型模式,其主要优点如下。

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
  • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。


其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

16.2、模式的结构和实现

备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类,现在我们来学习其结构与实现。

1、 模式的结构

备忘录模式的主要角色如下。

  1. 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  2. 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  3. 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。


备忘录模式的结构图如图 1 所示。
image.png

2、模式的实现

备忘录模式的实现代码如下:

  1. package net.biancheng.c.memento;
  2. public class MementoPattern {
  3. public static void main(String[] args) {
  4. Originator or = new Originator();
  5. Caretaker cr = new Caretaker();
  6. or.setState("S0");
  7. System.out.println("初始状态:" + or.getState());
  8. cr.setMemento(or.createMemento()); //保存状态
  9. or.setState("S1");
  10. System.out.println("新的状态:" + or.getState());
  11. or.restoreMemento(cr.getMemento()); //恢复状态
  12. System.out.println("恢复状态:" + or.getState());
  13. }
  14. }
  15. //备忘录
  16. class Memento {
  17. private String state;
  18. public Memento(String state) {
  19. this.state = state;
  20. }
  21. public void setState(String state) {
  22. this.state = state;
  23. }
  24. public String getState() {
  25. return state;
  26. }
  27. }
  28. //发起人
  29. class Originator {
  30. private String state;
  31. public void setState(String state) {
  32. this.state = state;
  33. }
  34. public String getState() {
  35. return state;
  36. }
  37. public Memento createMemento() {
  38. return new Memento(state);
  39. }
  40. public void restoreMemento(Memento m) {
  41. this.setState(m.getState());
  42. }
  43. }
  44. //管理者
  45. class Caretaker {
  46. private Memento memento;
  47. public void setMemento(Memento m) {
  48. memento = m;
  49. }
  50. public Memento getMemento() {
  51. return memento;
  52. }
  53. }
  1. 初始状态:S0
  2. 新的状态:S1
  3. 恢复状态:S0

16.3、应用实例

1、游戏进度存档

  1. public class GameRole {
  2. private int vit;//生命力
  3. private int atk;//攻击力
  4. private int def;//防御力
  5. //设置初始状态
  6. public void setInitState() {
  7. this.vit=100;
  8. this.atk=100;
  9. this.def=100;
  10. }
  11. //设置战后状态
  12. public void fight() {
  13. this.vit=0;
  14. this.atk=0;
  15. this.def=0;
  16. }
  17. //保存状态
  18. public RoleStateMemento saveState() {
  19. return new RoleStateMemento(vit,atk,def);
  20. }
  21. //恢复状态
  22. public void RecoverState(RoleStateMemento memento) {
  23. this.vit=memento.getVit();
  24. this.atk=memento.getAtk();
  25. this.def=memento.getDef();
  26. }
  27. //显示状态
  28. public void display() {
  29. System.out.println("vit=" + vit + ", atk=" + atk + ", def=" + def );
  30. }
  31. }
  1. //角色状态存储箱
  2. public class RoleStateMemento {
  3. private int vit;//生命力
  4. private int atk;//攻击力
  5. private int def;//防御力
  6. public RoleStateMemento(int vit, int atk, int def) {
  7. super();
  8. this.vit = vit;
  9. this.atk = atk;
  10. this.def = def;
  11. }
  12. public int getVit() {
  13. return vit;
  14. }
  15. public void setVit(int vit) {
  16. this.vit = vit;
  17. }
  18. public int getAtk() {
  19. return atk;
  20. }
  21. public void setAtk(int atk) {
  22. this.atk = atk;
  23. }
  24. public int getDef() {
  25. return def;
  26. }
  27. public void setDef(int def) {
  28. this.def = def;
  29. }
  30. }
  1. //角色状态管理者
  2. public class RoleStateCareTaker {
  3. private RoleStateMemento memento;
  4. public RoleStateMemento getMemento() {
  5. return memento;
  6. }
  7. public void setMemento(RoleStateMemento memento) {
  8. this.memento = memento;
  9. }
  10. }
  1. public class Main {
  2. // 这种只能保存上一个存档的记录
  3. public static void main(String[] args) {
  4. //大战前
  5. GameRole lxy=new GameRole();
  6. lxy.setInitState();
  7. lxy.display();
  8. //保存进度
  9. RoleStateCareTaker state1=new RoleStateCareTaker();
  10. state1.setMemento(lxy.saveState());
  11. //大战后
  12. lxy.fight();
  13. lxy.display();
  14. //恢复原状态
  15. lxy.RecoverState(state1.getMemento());
  16. lxy.display();
  17. }
  18. }

16.4、备忘录模式的优缺点

优点

  1. 当一个类里面的对象有几种状态,如果当前的状态是无效的,那么可以用暂存起来的备忘录将状态复原。
  2. 实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点

  1. 如果要将一个对象的所有的状态记录,那么这个在备忘录上面对象的占用的资源会很昂贵。

16.4、备忘录模式应用场景

当你需要创建对象状态快照来恢复其之前的状态时 , 可以使用备忘录模式 。 备忘录模式允许你复制对象中的全部状态 ( 包括私有成员变量 ) , 并将其独立于对象进行保存 。
当系统必须要保存一个对象在某一个时刻的状态,以方便需要的时候能恢复到先前的状态时,就可以使用备忘录模式。
领导找小巩建立一个会议纪要系统,需要保存相关信息,需要的时候可以查阅。

17、解释器模式

在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。
虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的,本文将介绍其工作原理与使用方法。

17.1、解释器模式的概述

解释器(Interpreter)模式的定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文
这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。
解释器模式是一种类行为型模式,其主要优点如下。

  1. 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
  2. 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。


解释器模式的主要缺点如下。

  1. 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
  2. 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
  3. 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。

17.2、模式的结构与实现

解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念。

  • 文法

文法是用于描述语言的语法结构的形式规则。没有规矩不成方圆,例如,有些人认为完美爱情的准则是“相互吸引、感情专一、任何一方都没有恋爱经历”,虽然最后一条准则较苛刻,但任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。例如,中文中的“句子”的文法如下。

  1. 〈句子〉::=〈主语〉〈谓语〉〈宾语〉
  2. 〈主语〉::=〈代词〉|〈名词〉
  3. 〈谓语〉::=〈动词〉
  4. 〈宾语〉::=〈代词〉|〈名词〉
  5. 〈代词〉你|我|他
  6. 〈名词〉7大学生I筱霞I英语
  7. 〈动词〉::=是|学习

注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。

  • 句子

句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。例如,上述文法可以推出“我是大学生”,所以它是句子。

  • 语法树

语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。图 1 所示是“我是大学生”的语法树。

image.png

有了以上基础知识,现在来介绍解释器模式的结构就简单了。解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。

1. 模式的结构

解释器模式包含以下主要角色。

  1. 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
  2. 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
  3. 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
  4. 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
  5. 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。


解释器模式的结构图如图 2 所示。
image.png

2. 模式的实现

解释器模式实现的关键是定义文法规则、设计终结符类与非终结符类、画出结构图,必要时构建语法树,其代码结构如下:

  1. package net.biancheng.c.interpreter;
  2. //抽象表达式类
  3. interface AbstractExpression {
  4. public void interpret(String info); //解释方法
  5. }
  6. //终结符表达式类
  7. class TerminalExpression implements AbstractExpression {
  8. public void interpret(String info) {
  9. //对终结符表达式的处理
  10. }
  11. }
  12. //非终结符表达式类
  13. class NonterminalExpression implements AbstractExpression {
  14. private AbstractExpression exp1;
  15. private AbstractExpression exp2;
  16. public void interpret(String info) {
  17. //非对终结符表达式的处理
  18. }
  19. }
  20. //环境类
  21. class Context {
  22. private AbstractExpression exp;
  23. public Context() {
  24. //数据初始化
  25. }
  26. public void operation(String info) {
  27. //调用相关表达式类的解释方法
  28. }
  29. }

17.3、应用实例

【例1】用解释器模式设计一个“韶粵通”公交车卡的读卡器程序。

说明:假如“韶粵通”公交车读卡器可以判断乘客的身份,如果是“韶关”或者“广州”的“老人” “妇女”“儿童”就可以免费乘车,其他人员乘车一次扣 2 元。

分析:本实例用“解释器模式”设计比较适合,首先设计其文法规则如下。

  1. <expression> ::= <city>的<person>
  2. <city> ::= 韶关|广州
  3. <person> ::= 老人|妇女|儿童

然后,根据文法规则按以下步骤设计公交车卡的读卡器程序的类图。

  • 定义一个抽象表达式(Expression)接口,它包含了解释方法 interpret(String info)。
  • 定义一个终结符表达式(Terminal Expression)类,它用集合(Set)类来保存满足条件的城市或人,并实现抽象表达式接口中的解释方法 interpret(Stringinfo),用来判断被分析的字符串是否是集合中的终结符。
  • 定义一个非终结符表达式(AndExpressicm)类,它也是抽象表达式的子类,它包含满足条件的城市的终结符表达式对象和满足条件的人员的终结符表达式对象,并实现 interpret(String info) 方法,用来判断被分析的字符串是否是满足条件的城市中的满足条件的人员。
  • 最后,定义一个环境(Context)类,它包含解释器需要的数据,完成对终结符表达式的初始化,并定义一个方法 freeRide(String info) 调用表达式对象的解释方法来对被分析的字符串进行解释。其结构图如图 3 所示。

image.png

  1. package net.biancheng.c.interpreter;
  2. import java.util.*;
  3. /*文法规则
  4. <expression> ::= <city>的<person>
  5. <city> ::= 韶关|广州
  6. <person> ::= 老人|妇女|儿童
  7. */
  8. public class InterpreterPatternDemo {
  9. public static void main(String[] args) {
  10. Context bus = new Context();
  11. bus.freeRide("韶关的老人");
  12. bus.freeRide("韶关的年轻人");
  13. bus.freeRide("广州的妇女");
  14. bus.freeRide("广州的儿童");
  15. bus.freeRide("山东的儿童");
  16. }
  17. }
  18. //抽象表达式类
  19. interface Expression {
  20. public boolean interpret(String info);
  21. }
  22. //终结符表达式类
  23. /**
  24. 通常,终结符表达式比较简单,主要处理场景元素和数据的转换。
  25. **/
  26. class TerminalExpression implements Expression {
  27. private Set<String> set = new HashSet<String>();
  28. public TerminalExpression(String[] data) {
  29. for (int i = 0; i < data.length; i++) set.add(data[i]);
  30. }
  31. public boolean interpret(String info) {
  32. if (set.contains(info)) {
  33. return true;
  34. }
  35. return false;
  36. }
  37. }
  38. //非终结符表达式类
  39. /**
  40.  每个非终结符表达式都代表了一个文法规则,并且每个文法规则都只关心自己周边的文法规则的结果,
  41. 因此这就产生了每个非终结符表达式调用自己周边的非终结符表达式,
  42. 然后最终、最小的文法规则就是终结符表达式。
  43. **/
  44. class AndExpression implements Expression {
  45. private Expression city = null;
  46. private Expression person = null;
  47. public AndExpression(Expression city, Expression person) {
  48. this.city = city;
  49. this.person = person;
  50. }
  51. public boolean interpret(String info) {
  52. String s[] = info.split("的");
  53. return city.interpret(s[0]) && person.interpret(s[1]);
  54. }
  55. }
  56. //环境类
  57. class Context {
  58. private String[] citys = {"韶关", "广州"};
  59. private String[] persons = {"老人", "妇女", "儿童"};
  60. private Expression cityPerson;
  61. public Context() {
  62. Expression city = new TerminalExpression(citys);
  63. Expression person = new TerminalExpression(persons);
  64. cityPerson = new AndExpression(city, person);
  65. }
  66. public void freeRide(String info) {
  67. boolean ok = cityPerson.interpret(info);
  68. if (ok) System.out.println("您是" + info + ",您本次乘车免费!");
  69. else System.out.println(info + ",您不是免费人员,本次乘车扣费2元!");
  70. }
  71. }
  1. 您是韶关的老人,您本次乘车免费!
  2. 韶关的年轻人,您不是免费人员,本次乘车扣费2元!
  3. 您是广州的妇女,您本次乘车免费!
  4. 您是广州的儿童,您本次乘车免费!
  5. 山东的儿童,您不是免费人员,本次乘车扣费2元!

17.4、应用场景

  • 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
  • 一些重复出现的问题可以用一种简单的语言来表达
  • 一个简单语法需要解释的场景

    18、状态模式

    在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。如人都有高兴和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。

对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。

以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。

18.1、状态模式的概述

状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

状态模式是一种对象行为型模式,其主要优点如下。

  1. 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
  2. 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
  3. 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。


状态模式的主要缺点如下。

  1. 状态模式的使用必然会增加系统的类与对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
  3. 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。

    18.2、状态模式的结构和实现

    状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法。

    1. 模式的结构

    状态模式包含以下主要角色。

  4. 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。

  5. 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  6. 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。


image.png

2. 模式的实现

状态模式的实现代码如下:

  1. public class StatePatternClient {
  2. public static void main(String[] args) {
  3. Context context = new Context(); //创建环境
  4. context.Handle(); //处理请求
  5. context.Handle();
  6. context.Handle();
  7. context.Handle();
  8. }
  9. }
  10. //环境类
  11. class Context {
  12. private State state;
  13. //定义环境类的初始状态
  14. public Context() {
  15. this.state = new ConcreteStateA();
  16. }
  17. //设置新状态
  18. public void setState(State state) {
  19. this.state = state;
  20. }
  21. //读取状态
  22. public State getState() {
  23. return (state);
  24. }
  25. //对请求做处理
  26. public void Handle() {
  27. state.Handle(this);
  28. }
  29. }
  30. //抽象状态类
  31. abstract class State {
  32. public abstract void Handle(Context context);
  33. }
  34. //具体状态A类
  35. class ConcreteStateA extends State {
  36. public void Handle(Context context) {
  37. System.out.println("当前状态是 A.");
  38. context.setState(new ConcreteStateB());
  39. }
  40. }
  41. //具体状态B类
  42. class ConcreteStateB extends State {
  43. public void Handle(Context context) {
  44. System.out.println("当前状态是 B.");
  45. context.setState(new ConcreteStateA());
  46. }
  47. }
  1. 当前状态是 A.
  2. 当前状态是 B.
  3. 当前状态是 A.
  4. 当前状态是 B.

18.3、状态模式的应用实例

例1】用“状态模式”设计一个学生成绩的状态转换程序。
分析:本实例包含了“不及格”“中等”和“优秀” 3 种状态,当学生的分数小于 60 分时为“不及格”状态,当分数大于等于 60 分且小于 90 分时为“中等”状态,当分数大于等于 90 分时为“优秀”状态,我们用状态模式来实现这个程序。
首先,定义一个抽象状态类(AbstractState),其中包含了环境属性、状态名属性和当前分数属性,以及加减分方法 addScore(intx) 和检查当前状态的抽象方法 checkState()。
然后,定义“不及格”状态类 LowState、“中等”状态类 MiddleState 和“优秀”状态类 HighState,它们是具体状态类,实现 checkState() 方法,负责检査自己的状态,并根据情况转换。
最后,定义环境类(ScoreContext),其中包含了当前状态对象和加减分的方法 add(int score),客户类通过该方法来改变成绩状态。图 2 所示是其结构图。
image.png

  1. public class ScoreStateTest {
  2. public static void main(String[] args) {
  3. ScoreContext account = new ScoreContext();
  4. System.out.println("学生成绩状态测试:");
  5. account.add(30);
  6. account.add(40);
  7. account.add(25);
  8. account.add(-15);
  9. account.add(-25);
  10. }
  11. }
  12. //环境类
  13. class ScoreContext {
  14. private AbstractState state;
  15. ScoreContext() {
  16. state = new LowState(this);
  17. }
  18. public void setState(AbstractState state) {
  19. this.state = state;
  20. }
  21. public AbstractState getState() {
  22. return state;
  23. }
  24. public void add(int score) {
  25. state.addScore(score);
  26. }
  27. }
  28. //抽象状态类
  29. abstract class AbstractState {
  30. protected ScoreContext hj; //环境
  31. protected String stateName; //状态名
  32. protected int score; //分数
  33. public abstract void checkState(); //检查当前状态
  34. public void addScore(int x) {
  35. score += x;
  36. System.out.print("加上:" + x + "分,\t当前分数:" + score);
  37. checkState();
  38. System.out.println("分,\t当前状态:" + hj.getState().stateName);
  39. }
  40. }
  41. //具体状态类:不及格
  42. class LowState extends AbstractState {
  43. public LowState(ScoreContext h) {
  44. hj = h;
  45. stateName = "不及格";
  46. score = 0;
  47. }
  48. public LowState(AbstractState state) {
  49. hj = state.hj;
  50. stateName = "不及格";
  51. score = state.score;
  52. }
  53. public void checkState() {
  54. if (score >= 90) {
  55. hj.setState(new HighState(this));
  56. } else if (score >= 60) {
  57. hj.setState(new MiddleState(this));
  58. }
  59. }
  60. }
  61. //具体状态类:中等
  62. class MiddleState extends AbstractState {
  63. public MiddleState(AbstractState state) {
  64. hj = state.hj;
  65. stateName = "中等";
  66. score = state.score;
  67. }
  68. public void checkState() {
  69. if (score < 60) {
  70. hj.setState(new LowState(this));
  71. } else if (score >= 90) {
  72. hj.setState(new HighState(this));
  73. }
  74. }
  75. }
  76. //具体状态类:优秀
  77. class HighState extends AbstractState {
  78. public HighState(AbstractState state) {
  79. hj = state.hj;
  80. stateName = "优秀";
  81. score = state.score;
  82. }
  83. public void checkState() {
  84. if (score < 60) {
  85. hj.setState(new LowState(this));
  86. } else if (score < 90) {
  87. hj.setState(new MiddleState(this));
  88. }
  89. }
  90. }
  1. 学生成绩状态测试:
  2. 加上:30分, 当前分数:30分, 当前状态:不及格
  3. 加上:40分, 当前分数:70分, 当前状态:中等
  4. 加上:25分, 当前分数:95分, 当前状态:优秀
  5. 加上:-15分, 当前分数:80分, 当前状态:中等
  6. 加上:-25分, 当前分数:55分, 当前状态:不及格

【例2】用“状态模式”设计一个多线程的状态转换程序。

分析:多线程存在 5 种状态,分别为新建状态、就绪状态、运行状态、阻塞状态和死亡状态,各个状态当遇到相关方法调用或事件触发时会转换到其他状态,其状态转换规律如图 3 所示。
image.png
现在先定义一个抽象状态类(TheadState),然后为图 3 所示的每个状态设计一个具体状态类,它们是新建状态(New)、就绪状态(Runnable )、运行状态(Running)、阻塞状态(Blocked)和死亡状态(Dead),每个状态中有触发它们转变状态的方法,环境类(ThreadContext)中先生成一个初始状态(New),并提供相关触发方法,图 4 所示是线程状态转换程序的结构图。
image.png

  1. public class ScoreStateTest {
  2. public static void main(String[] args) {
  3. ThreadContext context = new ThreadContext();
  4. context.start();
  5. context.getCPU();
  6. context.suspend();
  7. context.resume();
  8. context.getCPU();
  9. context.stop();
  10. }
  11. }
  12. //环境类
  13. class ThreadContext {
  14. private ThreadState state;
  15. ThreadContext() {
  16. state = new New();
  17. }
  18. public void setState(ThreadState state) {
  19. this.state = state;
  20. }
  21. public ThreadState getState() {
  22. return state;
  23. }
  24. public void start() {
  25. ((New) state).start(this);
  26. }
  27. public void getCPU() {
  28. ((Runnable) state).getCPU(this);
  29. }
  30. public void suspend() {
  31. ((Running) state).suspend(this);
  32. }
  33. public void stop() {
  34. ((Running) state).stop(this);
  35. }
  36. public void resume() {
  37. ((Blocked) state).resume(this);
  38. }
  39. }
  40. //抽象状态类:线程状态
  41. abstract class ThreadState {
  42. protected String stateName; //状态名
  43. }
  44. //具体状态类:新建状态
  45. class New extends ThreadState {
  46. public New() {
  47. stateName = "新建状态";
  48. System.out.println("当前线程处于:新建状态.");
  49. }
  50. public void start(ThreadContext hj) {
  51. System.out.print("调用start()方法-->");
  52. if (stateName.equals("新建状态")) {
  53. hj.setState(new Runnable());
  54. } else {
  55. System.out.println("当前线程不是新建状态,不能调用start()方法.");
  56. }
  57. }
  58. }
  59. //具体状态类:就绪状态
  60. class Runnable extends ThreadState {
  61. public Runnable() {
  62. stateName = "就绪状态";
  63. System.out.println("当前线程处于:就绪状态.");
  64. }
  65. public void getCPU(ThreadContext hj) {
  66. System.out.print("获得CPU时间-->");
  67. if (stateName.equals("就绪状态")) {
  68. hj.setState(new Running());
  69. } else {
  70. System.out.println("当前线程不是就绪状态,不能获取CPU.");
  71. }
  72. }
  73. }
  74. //具体状态类:运行状态
  75. class Running extends ThreadState {
  76. public Running() {
  77. stateName = "运行状态";
  78. System.out.println("当前线程处于:运行状态.");
  79. }
  80. public void suspend(ThreadContext hj) {
  81. System.out.print("调用suspend()方法-->");
  82. if (stateName.equals("运行状态")) {
  83. hj.setState(new Blocked());
  84. } else {
  85. System.out.println("当前线程不是运行状态,不能调用suspend()方法.");
  86. }
  87. }
  88. public void stop(ThreadContext hj) {
  89. System.out.print("调用stop()方法-->");
  90. if (stateName.equals("运行状态")) {
  91. hj.setState(new Dead());
  92. } else {
  93. System.out.println("当前线程不是运行状态,不能调用stop()方法.");
  94. }
  95. }
  96. }
  97. //具体状态类:阻塞状态
  98. class Blocked extends ThreadState {
  99. public Blocked() {
  100. stateName = "阻塞状态";
  101. System.out.println("当前线程处于:阻塞状态.");
  102. }
  103. public void resume(ThreadContext hj) {
  104. System.out.print("调用resume()方法-->");
  105. if (stateName.equals("阻塞状态")) {
  106. hj.setState(new Runnable());
  107. } else {
  108. System.out.println("当前线程不是阻塞状态,不能调用resume()方法.");
  109. }
  110. }
  111. }
  112. //具体状态类:死亡状态
  113. class Dead extends ThreadState {
  114. public Dead() {
  115. stateName = "死亡状态";
  116. System.out.println("当前线程处于:死亡状态.");
  117. }
  118. }
  1. 当前线程处于:新建状态.
  2. 调用start()方法-->当前线程处于:就绪状态.
  3. 获得CPU时间-->当前线程处于:运行状态.
  4. 调用suspend()方法-->当前线程处于:阻塞状态.
  5. 调用resume()方法-->当前线程处于:就绪状态.
  6. 获得CPU时间-->当前线程处于:运行状态.
  7. 调用stop()方法-->当前线程处于:死亡状态.

18.4、应用场景

通常在以下情况下可以考虑使用状态模式。

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。

    18.5、优缺点

    优点
  1. 将不同状态下的操作归于当前状态中,各个状态之间不会相互影响。
  2. 如果添加状态或者减少状态,只需要新增或者减少对应状态下面的处理逻辑代码即可,容易扩展。

缺点

  1. 增加了系统的类和对象的个数,使系统变得庞大。

19、策略模式

在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法。

在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。

如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题。

19.1、策略的概述

策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

策略模式的主要优点如下。

  1. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句。
  2. 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  3. 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  4. 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  5. 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。


其主要缺点如下。

  1. 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
  2. 策略模式造成很多的策略类,增加维护难度。

    19.2、策略模式的结构和实现

    策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。

1. 模式的结构

策略模式的主要角色如下。

  1. 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  2. 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  3. 环境(Context)类:持有一个策略类的引用,最终给客户端调用。


其结构图如图 1 所示。
image.png

2. 模式的实现

策略模式的实现代码如下:

  1. public class StrategyPattern {
  2. public static void main(String[] args) {
  3. Context c = new Context();
  4. Strategy s = new ConcreteStrategyA();
  5. c.setStrategy(s);
  6. c.strategyMethod();
  7. System.out.println("-----------------");
  8. s = new ConcreteStrategyB();
  9. c.setStrategy(s);
  10. c.strategyMethod();
  11. }
  12. }
  13. //抽象策略类
  14. interface Strategy {
  15. public void strategyMethod(); //策略方法
  16. }
  17. //具体策略类A
  18. class ConcreteStrategyA implements Strategy {
  19. public void strategyMethod() {
  20. System.out.println("具体策略A的策略方法被访问!");
  21. }
  22. }
  23. //具体策略类B
  24. class ConcreteStrategyB implements Strategy {
  25. public void strategyMethod() {
  26. System.out.println("具体策略B的策略方法被访问!");
  27. }
  28. }
  29. //环境类
  30. class Context {
  31. private Strategy strategy;
  32. public Strategy getStrategy() {
  33. return strategy;
  34. }
  35. public void setStrategy(Strategy strategy) {
  36. this.strategy = strategy;
  37. }
  38. public void strategyMethod() {
  39. strategy.strategyMethod();
  40. }
  41. }

程序运行结果如下:

  1. 具体策略A的策略方法被访问!
  2. -----------------
  3. 具体策略B的策略方法被访问!

19.3、策略模式的应用实例

【例1】策略模式在“大闸蟹”做菜中的应用。

分析:关于大闸蟹的做法有很多种,我们以清蒸大闸蟹和红烧大闸蟹两种方法为例,介绍策略模式的应用。

首先,定义一个大闸蟹加工的抽象策略类(CrabCooking),里面包含了一个做菜的抽象方法 CookingMethod();然后,定义清蒸大闸蟹(SteamedCrabs)和红烧大闸蟹(BraisedCrabs)的具体策略类,它们实现了抽象策略类中的抽象方法;,所以将具体策略类定义成 JLabel 的子类;最后,定义一个厨房(Kitchen)环境类,它具有设置和选择做菜策略的方法;客户类通过厨房类获取做菜策略,并把做菜结果图在窗体中显示出来,图 2 所示是其结构图。
image.png

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import javax.swing.*;
  4. public class CrabCookingStrategy implements ItemListener {
  5. private JFrame f;
  6. private JRadioButton qz, hs;
  7. private JPanel CenterJP, SouthJP;
  8. private Kitchen cf; //厨房
  9. private CrabCooking qzx, hsx; //大闸蟹加工者
  10. CrabCookingStrategy() {
  11. f = new JFrame("策略模式在大闸蟹做菜中的应用");
  12. f.setBounds(100, 100, 500, 400);
  13. f.setVisible(true);
  14. f.setResizable(false);
  15. f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  16. SouthJP = new JPanel();
  17. CenterJP = new JPanel();
  18. f.add("South", SouthJP);
  19. f.add("Center", CenterJP);
  20. qz = new JRadioButton("清蒸大闸蟹");
  21. hs = new JRadioButton("红烧大闸蟹");
  22. qz.addItemListener(this);
  23. hs.addItemListener(this);
  24. ButtonGroup group = new ButtonGroup();
  25. group.add(qz);
  26. group.add(hs);
  27. SouthJP.add(qz);
  28. SouthJP.add(hs);
  29. //---------------------------------
  30. cf = new Kitchen(); //厨房
  31. qzx = new SteamedCrabs(); //清蒸大闸蟹类
  32. hsx = new BraisedCrabs(); //红烧大闸蟹类
  33. }
  34. public void itemStateChanged(ItemEvent e) {
  35. JRadioButton jc = (JRadioButton) e.getSource();
  36. if (jc == qz) {
  37. cf.setStrategy(qzx);
  38. cf.CookingMethod(); //清蒸
  39. } else if (jc == hs) {
  40. cf.setStrategy(hsx);
  41. cf.CookingMethod(); //红烧
  42. }
  43. CenterJP.removeAll();
  44. CenterJP.repaint();
  45. CenterJP.add((Component) cf.getStrategy());
  46. f.setVisible(true);
  47. }
  48. public static void main(String[] args) {
  49. new CrabCookingStrategy();
  50. }
  51. }
  52. //抽象策略类:大闸蟹加工类
  53. interface CrabCooking {
  54. public void CookingMethod(); //做菜方法
  55. }
  56. //具体策略类:清蒸大闸蟹
  57. class SteamedCrabs extends JLabel implements CrabCooking {
  58. private static final long serialVersionUID = 1L;
  59. public void CookingMethod() {
  60. this.setIcon(new ImageIcon("src/strategy/SteamedCrabs.jpg"));
  61. this.setHorizontalAlignment(CENTER);
  62. }
  63. }
  64. //具体策略类:红烧大闸蟹
  65. class BraisedCrabs extends JLabel implements CrabCooking {
  66. private static final long serialVersionUID = 1L;
  67. public void CookingMethod() {
  68. this.setIcon(new ImageIcon("src/strategy/BraisedCrabs.jpg"));
  69. this.setHorizontalAlignment(CENTER);
  70. }
  71. }
  72. //环境类:厨房
  73. class Kitchen {
  74. private CrabCooking strategy; //抽象策略
  75. public void setStrategy(CrabCooking strategy) {
  76. this.strategy = strategy;
  77. }
  78. public CrabCooking getStrategy() {
  79. return strategy;
  80. }
  81. public void CookingMethod() {
  82. strategy.CookingMethod(); //做菜
  83. }
  84. }

19.4、策略模式的应用场景

策略模式在很多地方用到,如 Java SE 中的容器布局管理就是一个典型的实例,Java SE 中的每个容器都存在多种布局供用户选择。在程序设计中,通常在以下几种情况中使用策略模式较多。

  1. 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  2. 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  3. 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  4. 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
  5. 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

19.5、源码实现

1、JDK中使用策略模式

在JDK中,我们调用数组工具类Arrays的一个排序方法sort时,可以使用默认的排序规则(升序),也可以自定义指定排序的规则,也就是可以自定义实现升序排序还是降序排序,方法的源码如下:

  1. public static <T> void sort(T[] a, Comparator<? super T> c) {
  2. if (c == null) {
  3. sort(a);//若没有传入Comparator接口的实现类对象,也就是调用默认的排序方法
  4. } else {
  5. if (LegacyMergeSort.userRequested)
  6. legacyMergeSort(a, c);
  7. else
  8. TimSort.sort(a, 0, a.length, c, null, 0, 0);
  9. }
  10. }

也就是说我们传递两个参数,一个是待排序的数组,另一个是Comparator接口的实现类对象,其中Comparator接口是一个函数式接口,接口里面定义了一个用于定义排序规则的抽象方法int compare(T o1, T o2);,由此可见,Comparator接口就是策略模式中的策略接口,它定义了一个排序算法,而具体的策略或者说具体的排序算法实现将由用户自定义实现。
1、不指定排序规则

  1. //Arrays的sort方法默认是进行升序排序
  2. public static void main(String[] args) {
  3. int[] arr={10,11,9,-7,6,18,2};
  4. Arrays.sort(arr);
  5. System.out.println(Arrays.toString(arr));//[-7, 2, 6, 9, 10, 11, 18]
  6. }

2、自定义

  1. public static void main(String[] args) {
  2. //使用匿名类的写法
  3. Comparator<Integer> c=new Comparator<Integer>() {
  4. @Override
  5. public int compare(Integer o1, Integer o2) {
  6. if(o1>o2){
  7. return -1;
  8. }else if(o1<o2){
  9. return 1;
  10. }else{
  11. return 0;
  12. }
  13. }
  14. };
  15. Integer[] arr={10,11,9,-7,6,18,2};
  16. Arrays.sort(arr, c);
  17. System.out.println(Arrays.toString(arr));//[18, 11, 10, 9, 6, 2, -7]
  18. }

2、Spring源码使用策略模式

DispatcherServlet在进行转发前需要进行传说中的九大件的初始化,其中去初始化时除了 initMultipartResolver(上传文件)没有获取 Properties defaultStrategies;默认策略,其他的八大件都会使用到策略模式。先看一下 defaultStrategies为 java.util.Properties类型,定义如下:

  1. public class Properties extends Hashtable<Object, Object> {
  2. // ***
  3. }

流程梳理:
1、当Web容器启动时,ServletWebServerApplicationContext 初始化会调用其refresh()方法,则会调用 DispatcherServlet的onRefresh方法(Spring Ioc的流程可以参见:Spring Ioc和Mvc时序图)
2、onRefresh方法 - > initStrategies方法 -> 初始化九大件
3、初始化时则会调用getDefaultStrategy方法。如下:
image.png

  1. protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
  2. List<T> strategies = this.getDefaultStrategies(context, strategyInterface);
  3. if (strategies.size() != 1) {
  4. throw new BeanInitializationException("DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
  5. } else {
  6. return strategies.get(0);
  7. }
  8. }
  1. protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
  2. String key = strategyInterface.getName();
  3. String value = defaultStrategies.getProperty(key);
  4. if (value == null) {
  5. return new LinkedList();
  6. } else {
  7. String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
  8. List<T> strategies = new ArrayList(classNames.length);
  9. String[] var7 = classNames;
  10. int var8 = classNames.length;
  11. for(int var9 = 0; var9 < var8; ++var9) {
  12. String className = var7[var9];
  13. try {
  14. Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
  15. Object strategy = this.createDefaultStrategy(context, clazz);
  16. strategies.add(strategy);
  17. } catch (ClassNotFoundException var13) {
  18. throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", var13);
  19. } catch (LinkageError var14) {
  20. throw new BeanInitializationException("Unresolvable class definition for DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", var14);
  21. }
  22. }
  23. return strategies;
  24. }
  25. }

5、那么Properties的值在哪里添加进去的呢,DispatcherServlet 的static静态代码块中会看见,是用Spring的Resource将配置文件中的配置加载,设置到这个Map容器中的,如下:

  1. static {
  2. try {
  3. ClassPathResource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
  4. defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
  5. } catch (IOException var1) {
  6. throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + var1.getMessage());
  7. }
  8. }

6、在看看 DispatcherServlet.properties 配置就明白了,就是九大件没传时的默认值,其实也可以考虑用SPI机制实现(记得之前好像是不是有版本就是用SPI实现的)。

  1. # Default implementation classes for DispatcherServlet's strategy interfaces.
  2. # Used as fallback when no matching beans are found in the DispatcherServlet context.
  3. # Not meant to be customized by application developers.
  4. org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
  5. org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
  6. org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
  7. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
  8. org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
  9. org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
  10. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
  11. org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
  12. org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
  13. org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
  14. org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
  15. org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
  16. org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

小结: web容器启动,ServletWebServerApplicationContext 的refresh方法 间接调用到 DispatcherServlet的初始九大件方法, 其中八大件在没有自定义实现的情况下,调用默认的 配置。 而默认配置则是在 DispatcherServlet的静态代码块中,由Spring的ClassPathResource将配置文件DispatcherServlet.properties中的配置加载进一个 Map容器中。只待初始化九大件时,根据不同的九大件类型作为key,调用相应的实现。

20、责任链模式

在现实生活中,一个事件需要经过多个对象处理是很常见的场景。例如,采购审批流程、请假流程等。公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据需要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这无疑增加了难度。

在计算机软硬件中也有相关例子,如总线网中数据报传送,每台计算机根据目标地址是否同自己的地址相同来决定是否接收;还有异常处理中,处理程序根据异常的类型决定自己是否处理该异常;还有 Struts2 的拦截器、JSP 和 Servlet 的 Filter 等,所有这些,都可以考虑使用责任链模式来实现。

20.1、模式的定义和特点

责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

注意:责任链模式也叫职责链模式。

在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。

责任链模式是一种对象行为型模式,其主要优点如下。

  1. 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  2. 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
  3. 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  4. 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  5. 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。


其主要缺点如下。

  1. 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  2. 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  3. 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

    20.2、责任链的定义和实现

    通常情况下,可以通过数据链表来实现职责链模式的数据结构。

    1、 模式的结构

    职责链模式主要包含以下角色。

  4. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。

  5. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  6. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。


责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。

其结构图如图 1 所示。客户端可按图 2 所示设置责任链。
image.png
image.png

2、模式的实现

职责链模式的实现代码如下:

  1. package chainOfResponsibility;
  2. public class ChainOfResponsibilityPattern {
  3. public static void main(String[] args) {
  4. //组装责任链
  5. Handler handler1 = new ConcreteHandler1();
  6. Handler handler2 = new ConcreteHandler2();
  7. handler1.setNext(handler2);
  8. //提交请求
  9. handler1.handleRequest("two");
  10. }
  11. }
  12. //抽象处理者角色
  13. abstract class Handler {
  14. private Handler next;
  15. public void setNext(Handler next) {
  16. this.next = next;
  17. }
  18. public Handler getNext() {
  19. return next;
  20. }
  21. //处理请求的方法
  22. public abstract void handleRequest(String request);
  23. }
  24. //具体处理者角色1
  25. class ConcreteHandler1 extends Handler {
  26. public void handleRequest(String request) {
  27. if (request.equals("one")) {
  28. System.out.println("具体处理者1负责处理该请求!");
  29. } else {
  30. if (getNext() != null) {
  31. getNext().handleRequest(request);
  32. } else {
  33. System.out.println("没有人处理该请求!");
  34. }
  35. }
  36. }
  37. }
  38. //具体处理者角色2
  39. class ConcreteHandler2 extends Handler {
  40. public void handleRequest(String request) {
  41. if (request.equals("two")) {
  42. System.out.println("具体处理者2负责处理该请求!");
  43. } else {
  44. if (getNext() != null) {
  45. getNext().handleRequest(request);
  46. } else {
  47. System.out.println("没有人处理该请求!");
  48. }
  49. }
  50. }
  51. }
  1. 具体处理者2负责处理该请求!

在上面代码中,我们把消息硬编码为 String 类型,而在真实业务中,消息是具备多样性的,可以是 int、String 或者自定义类型。因此,在上面代码的基础上,可以对消息类型进行抽象 Request,增强了消息的兼容性。

20.2、模式的应用实例

【例1】用责任链模式设计一个请假条审批模块。

分析:假如规定学生请假小于或等于 2 天,班主任可以批准;小于或等于 7 天,系主任可以批准;小于或等于 10 天,院长可以批准;其他情况不予批准;这个实例适合使用职责链模式实现。

首先,定义一个领导类(Leader),它是抽象处理者,包含了一个指向下一位领导的指针 next 和一个处理假条的抽象处理方法 handleRequest(int LeaveDays);然后,定义班主任类(ClassAdviser)、系主任类(DepartmentHead)和院长类(Dean),它们是抽象处理者的子类,是具体处理者,必须根据自己的权力去实现父类的 handleRequest(int LeaveDays) 方法,如果无权处理就将假条交给下一位具体处理者,直到最后;客户类负责创建处理链,并将假条交给链头的具体处理者(班主任)。图 3 所示是其结构图。
image.png

  1. package chainOfResponsibility;
  2. public class LeaveApprovalTest {
  3. public static void main(String[] args) {
  4. //组装责任链
  5. Leader teacher1 = new ClassAdviser();
  6. Leader teacher2 = new DepartmentHead();
  7. Leader teacher3 = new Dean();
  8. //Leader teacher4=new DeanOfStudies();
  9. teacher1.setNext(teacher2);
  10. teacher2.setNext(teacher3);
  11. //teacher3.setNext(teacher4);
  12. //提交请求
  13. teacher1.handleRequest(8);
  14. }
  15. }
  16. //抽象处理者:领导类
  17. abstract class Leader {
  18. private Leader next;
  19. public void setNext(Leader next) {
  20. this.next = next;
  21. }
  22. public Leader getNext() {
  23. return next;
  24. }
  25. //处理请求的方法
  26. public abstract void handleRequest(int LeaveDays);
  27. }
  28. //具体处理者1:班主任类
  29. class ClassAdviser extends Leader {
  30. public void handleRequest(int LeaveDays) {
  31. if (LeaveDays <= 2) {
  32. System.out.println("班主任批准您请假" + LeaveDays + "天。");
  33. } else {
  34. if (getNext() != null) {
  35. getNext().handleRequest(LeaveDays);
  36. } else {
  37. System.out.println("请假天数太多,没有人批准该假条!");
  38. }
  39. }
  40. }
  41. }
  42. //具体处理者2:系主任类
  43. class DepartmentHead extends Leader {
  44. public void handleRequest(int LeaveDays) {
  45. if (LeaveDays <= 7) {
  46. System.out.println("系主任批准您请假" + LeaveDays + "天。");
  47. } else {
  48. if (getNext() != null) {
  49. getNext().handleRequest(LeaveDays);
  50. } else {
  51. System.out.println("请假天数太多,没有人批准该假条!");
  52. }
  53. }
  54. }
  55. }
  56. //具体处理者3:院长类
  57. class Dean extends Leader {
  58. public void handleRequest(int LeaveDays) {
  59. if (LeaveDays <= 10) {
  60. System.out.println("院长批准您请假" + LeaveDays + "天。");
  61. } else {
  62. if (getNext() != null) {
  63. getNext().handleRequest(LeaveDays);
  64. } else {
  65. System.out.println("请假天数太多,没有人批准该假条!");
  66. }
  67. }
  68. }
  69. }
  70. //具体处理者4:教务处长类
  71. class DeanOfStudies extends Leader {
  72. public void handleRequest(int LeaveDays) {
  73. if (LeaveDays <= 20) {
  74. System.out.println("教务处长批准您请假" + LeaveDays + "天。");
  75. } else {
  76. if (getNext() != null) {
  77. getNext().handleRequest(LeaveDays);
  78. } else {
  79. System.out.println("请假天数太多,没有人批准该假条!");
  80. }
  81. }
  82. }
  83. }

程序运行结果如下:

  1. 院长批准您请假8天。

假如增加一个教务处长类,可以批准学生请假 20 天,也非常简单,代码如下:

  1. //具体处理者4:教务处长类
  2. class DeanOfStudies extends Leader {
  3. public void handleRequest(int LeaveDays) {
  4. if (LeaveDays <= 20) {
  5. System.out.println("教务处长批准您请假" + LeaveDays + "天。");
  6. } else {
  7. if (getNext() != null) {
  8. getNext().handleRequest(LeaveDays);
  9. } else {
  10. System.out.println("请假天数太多,没有人批准该假条!");
  11. }
  12. }
  13. }
  14. }

20.3、应用场景

前边已经讲述了关于责任链模式的结构与特点,下面介绍其应用场景,责任链模式通常在以下几种情况使用。

  1. 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。
  2. 可动态指定一组对象处理请求,或添加新的处理者。
  3. 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。

    20.4、源码的应用

    HandlerExecutionChain
    其中我们可以看到,在springMVC中,DispatcherServlet这个核心类中使用到了HandlerExecutionChain这个类,他就是责任链模式实行的具体类。在DispatcherServlet的doDispatch这个方法中,我们可以看到它贯穿了整个请求dispatch的流程:

    1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    2. HttpServletRequest processedRequest = request;
    3. HandlerExecutionChain mappedHandler = null;
    4. boolean multipartRequestParsed = false;
    5. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    6. try {
    7. ModelAndView mv = null;
    8. Exception dispatchException = null;
    9. try {
    10. processedRequest = checkMultipart(request);
    11. multipartRequestParsed = (processedRequest != request);
    12. // 获取该请求的handler,每个handler实为HandlerExecutionChain,它为一个处理链,负责处理整个请求
    13. mappedHandler = getHandler(processedRequest);
    14. if (mappedHandler == null || mappedHandler.getHandler() == null) {
    15. noHandlerFound(processedRequest, response);
    16. return;
    17. }
    18. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    19. String method = request.getMethod();
    20. boolean isGet = "GET".equals(method);
    21. if (isGet || "HEAD".equals(method)) {
    22. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    23. if (logger.isDebugEnabled()) {
    24. logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
    25. }
    26. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    27. return;
    28. }
    29. }
    30. // 责任链执行预处理方法,实则是将请求交给注册的请求拦截器执行
    31. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    32. return;
    33. }
    34. // 实际的执行逻辑的部分,也就是你加了@RequestMapping注解的方法
    35. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    36. if (asyncManager.isConcurrentHandlingStarted()) {
    37. return;
    38. }
    39. applyDefaultViewName(processedRequest, mv);
    40. // 责任链执行后处理方法,实则是将请求交给注册的请求拦截器执行
    41. mappedHandler.applyPostHandle(processedRequest, response, mv);
    42. }
    43. catch (Exception ex) {
    44. dispatchException = ex;
    45. }
    46. catch (Throwable err) {
    47. dispatchException = new NestedServletException("Handler dispatch failed", err);
    48. }
    49. // 处理返回的结果,触发责任链上注册的拦截器的AfterCompletion方法,其中也用到了HandlerExecutionChain注册的handler来处理错误结果
    50. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    51. }
    52. catch (Exception ex) {
    53. // 触发责任链上注册的拦截器的AfterCompletion方法
    54. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    55. }
    56. catch (Throwable err) {
    57. triggerAfterCompletion(processedRequest, response, mappedHandler,
    58. new NestedServletException("Handler processing failed", err));
    59. }
    60. finally {
    61. if (asyncManager.isConcurrentHandlingStarted()) {
    62. if (mappedHandler != null) {
    63. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    64. }
    65. }
    66. else {
    67. if (multipartRequestParsed) {
    68. cleanupMultipart(processedRequest);
    69. }
    70. }
    71. }
    72. }