# 设计模式代码 **Repository Path**: jiazdemo/DesignPattern ## Basic Information - **Project Name**: 设计模式代码 - **Description**: 存放康小庄学习设计模式时写的代码 - **Primary Language**: Java - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 5 - **Created**: 2022-08-16 - **Last Updated**: 2022-08-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README **学习地址** **https://www.bilibili.com/video/BV1G4411c7N4** **https://www.bilibili.com/video/BV1Np4y1z7BU** **参考文章** **http://c.biancheng.net/view/1317.html** # 1,设计模式概述 ## 1.1 软件设计模式的产生背景 "设计模式"最初并不是出现在软件设计中,而是被用于建筑领域的设计中。 1977年美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任`克里斯托夫·亚历山大(Christopher Alexander)`在他的著作《建筑模式语言:城镇、建筑、构造》中描述了一些常见的建筑设计问题,并提出了 253 种关于对城镇、邻里、住宅、花园和房间等进行设计的基本模式。 1990年软件工程界开始研讨设计模式的话题,后来召开了多次关于设计模式的研讨会。直到1995 年,艾瑞克·伽马(ErichGamma)、理査德·海尔姆(Richard Helm)、拉尔夫·约翰森(Ralph Johnson)、约翰·威利斯迪斯(John Vlissides)等 4 位作者合作出版了《设计模式:可复用面向对象软件的基础》一书,在此书中收录了 23 个设计模式,这是设计模式领域里程碑的事件,导致了软件设计模式的突破。这 4 位作者在软件开发领域里也以他们的“四人组”(Gang of Four,GoF)著称。 ## 1.2 软件设计模式的概念 软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。 ## 1.3 学习设计模式的必要性 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。 正确使用设计模式具有以下优点。 - 可以提高程序员的思维能力、编程能力和设计能力。 - 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。 - 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。 ## 1.4 设计模式分类 * **创建型模式** 用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF(四人组)书中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。 * **结构型模式** 用于描述如何将类或对象按某种布局组成更大的结构,GoF(四人组)书中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。 * **行为型模式** 用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。GoF(四人组)书中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。 # 2,UML图 统一建模语言(Unified Modeling Language,UML)是用来设计软件的可视化建模语言。它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。 UML 从目标系统的不同角度出发,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。 ## 2.1 类图概述 类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。 ## 2.2 类图的作用 * 在软件工程中,类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化了人们对系统的理解; * 类图是系统分析和设计阶段的重要产物,是系统编码和测试的重要模型。 ## 2.3 类图表示法 ### 2.3.1 类的表示方式 在UML类图中,类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示,比如下图表示一个Employee类,它包含name,age和address这3个属性,以及work()方法。 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/Employee.jpg) 属性/方法名称前加的加号和减号表示了这个属性/方法的可见性,UML类图中表示可见性的符号有三种: * +:表示public * -:表示private * #:表示protected 属性的完整表示方式是: **可见性 名称 :类型 [ = 缺省值]** 方法的完整表示方式是: **可见性 名称(参数列表) [ : 返回类型]** > 注意: > > 1,中括号中的内容表示是可选的 > > 2,也有将类型放在变量名前面,返回值类型放在方法名前面 **举个栗子:** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/demo.png) 上图Demo类定义了三个方法: * method()方法:修饰符为public,没有参数,没有返回值。 * method1()方法:修饰符为private,没有参数,返回值类型为String。 * method2()方法:修饰符为protected,接收两个参数,第一个参数类型为int,第二个参数类型为String,返回值类型是int。 ### 2.3.2 类与类之间关系的表示方式 #### 2.3.2.1 关联关系 关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。我们先介绍一般关联。 关联又可以分为单向关联,双向关联,自关联。 **1,单向关联** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/customer_address.png) 在UML类图中单向关联用一个带箭头的实线表示。上图表示每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。 **2,双向关联** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/customer_product.png) 从上图中我们很容易看出,所谓的双向关联就是双方各自持有对方类型的成员变量。 在UML类图中,双向关联用一个不带箭头的直线表示。上图中在Customer类中维护一个List\,表示一个顾客可以购买多个商品;在Product类中维护一个Customer类型的成员变量表示这个产品被哪个顾客所购买。 **3,自关联** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/node.png) 自关联在UML类图中用一个带有箭头且指向自身的线表示。上图的意思就是Node类包含类型为Node的成员变量,也就是“自己包含自己”。 #### 2.3.2.2 聚合关系 聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。 聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。 在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。下图所示是大学和教师的关系图: ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/image-20191229173422328.png) #### 2.3.2.3 组合关系 组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。 在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。 在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。下图所示是头和嘴的关系图: ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/image-20191229173455149.png) #### 2.3.2.4 依赖关系 依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。 在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图,司机驾驶汽车: ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/image-20191229173518926.png) #### 2.3.2.5 继承关系 继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。 在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。例如,Student 类和 Teacher 类都是 Person 类的子类,其类图如下图所示: ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/image-20191229173539838.png) #### 2.3.2.6 实现关系 实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。 在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交通工具,其类图如图 9 所示。 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/image-20191229173554296.png) # 3,软件设计原则 在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据6条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本。 ## 3.1 开闭原则 **对扩展开放,对修改关闭**。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。 想要达到这样的效果,我们需要使用接口和抽象类。 因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。 ## 3.2 里氏代换原则 里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。 如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。 **里氏代换原则错误示范** ```java package com.zhuang.principle.liskov; /** * @Classname Liskov * @Description 里氏代换原则错误示范 * @Date 2021/3/15 13:58 * @Created by dell */ public class Liskov { public static void main(String[] args) { A a = new A(); System.out.println("11-3=" +a.fun1(11,3)); System.out.println("11-8=" +a.fun1(11,8)); System.out.println("==================="); B b = new B(); System.out.println("11-3="+b.fun1(11,3)); System.out.println("1-8="+b.fun1(1,8)); System.out.println("11+3+9="+b.fun2(11,3)); } } class A{ //返回两个数的差 public int fun1(int num1,int num2){ return num1-num2; } } //B类继承A 增加新功能,完成两个数相加,然后和9求和 class B extends A{ @Override public int fun1(int a, int b) { return a+b; } public int fun2(int a, int b) { return fun1(a,b)+9; } } ``` **里氏代换原则正确示范** ```java package com.zhuang.principle.liskov; /** * @Classname Liskov2 * @Description 里氏代换原则 * @Date 2021/3/15 14:13 * @Created by dell */ public class Liskov2 { public static void main(String[] args) { Base base = new Base(); base.add(5,6); base.sub(6,2); Sub sub = new Sub(); sub.mul(5,6); sub.div(10,2); } } class Base { //通用加法运算 public void add(int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); } //通用减法运算 public void sub(int a, int b) { System.out.println(a + "-" + b + "=" + (a - b)); } } class Sub extends Base { //子类特有乘法运算 public void mul(int a, int b) { System.out.println(a + "*" + b + "=" + (a * b)); } //子类特有除法运算 public void div(int a, int b) { System.out.println(a + "/" + b + "=" + (a / b)); } } ``` ## 3.3 依赖倒转原则 高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。 **依赖倒转原则错误示范** ```java package com.zhuang.principle.inversion; /** * @Classname DependenceInversion1 * @Description 依赖倒转原则错误示范 * @Date 2021/3/15 13:20 * @Created by dell */ public class DependenceInversion1 { public static void main(String[] args) { Person person = new Person(); person.receive(new Email()); person.receive(new WeiXin()); } } //定义接口 interface IReceiver{ public String getInfo(); } class WeiXin implements IReceiver{ @Override public String getInfo() { return "发送微信消息..."; } } class Email implements IReceiver{ @Override public String getInfo() { return "发送邮件消息..."; } } //对接口的依赖 class Person{ public void receive(IReceiver receiver){ System.out.println(receiver.getInfo()); } } ``` **依赖倒转原则正确示范** ```java package com.zhuang.principle.inversion; /** * @Classname DependenceInversion2 * @Description 依赖倒转原则正确示范 * @Date 2021/3/15 13:27 * @Created by dell */ public class DependenceInversion2 { public static void main(String[] args) { Client client = new Client(); client.receive(new Emailiml()); client.receive(new WXimpl()); } } interface IReceive{ public void printInfo(Integer uid); } class WXimpl implements IReceive { @Override public void printInfo(Integer uid) { System.out.println("发送微信消息"+uid); } } class Emailiml implements IReceive { @Override public void printInfo(Integer uid) { System.out.println("发送邮件信息"+uid); } } class Client{ public void receive(IReceive receive){ receive.printInfo(12345); } } ``` 面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。 ## 3.4 接口隔离原则 客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。 **接口隔离原则** ```java package com.zhuang.principle.segregation; /** * @Classname Sergregation * @Description 接口隔离原则 * @Date 2021/3/15 13:02 * @Created by dell */ public class Sergregation { public static void main(String[] args) { C c = new C(); c.depend1(new A()); c.depend2(new A());//C类通过接口去依赖A类 c.depend3(new A()); System.out.println("======================="); D d = new D(); d.depend1(new B()); d.depend4(new B());//D类通过接口去依赖B类 d.depend5(new B()); } } interface interface1{ void operation1(); } interface interface2{ void operation2(); void operation3(); } interface interface3{ void operation4(); void operation5(); } class A implements interface1,interface2{ @Override public void operation1() { System.out.println("A 实现了operation1....."); } @Override public void operation2() { System.out.println("A 实现了operation2......"); } @Override public void operation3() { System.out.println("A 实现了operation3......"); } } class B implements interface1,interface3{ @Override public void operation1() { System.out.println("B 实现了operation1....."); } @Override public void operation4() { System.out.println("B 实现了operation4....."); } @Override public void operation5() { System.out.println("B 实现了operation5....."); } } //C类通过接口interface1,interface2依赖使用A类 只会使用到1,2,3方法 class C{ public void depend1(interface1 i){ i.operation1(); } public void depend2(interface2 i){ i.operation2(); } public void depend3(interface2 i){ i.operation3(); } } //D类通过接口interface1,interface3 依赖使用B类,用到1,4,5方法 class D{ public void depend1(interface1 i){ i.operation1(); } public void depend4(interface3 i){ i.operation4(); } public void depend5(interface3 i){ i.operation5(); } } ``` ## 3.5 迪米特法则 迪米特法则又叫最少知识原则。 只和你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。 其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。 迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。 下面看一个例子来理解迪米特法则 【例】明星与经纪人的关系实例 明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如和粉丝的见面会,和媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则。 类图如下: ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%BF%AA%E7%B1%B3%E7%89%B9%E6%B3%95%E5%88%99.png) 代码如下: **明星类(Star)** ```java public class Star { private String name; public Star(String name) { this.name=name; } public String getName() { return name; } } ``` **粉丝类(Fans)** ```java public class Fans { private String name; public Fans(String name) { this.name=name; } public String getName() { return name; } } ``` **媒体公司类(Company)** ```java public class Company { private String name; public Company(String name) { this.name=name; } public String getName() { return name; } } ``` **经纪人类(Agent)** ```java public class Agent { private Star star; private Fans fans; private Company company; public void setStar(Star star) { this.star = star; } public void setFans(Fans fans) { this.fans = fans; } public void setCompany(Company company) { this.company = company; } public void meeting() { System.out.println(fans.getName() + "与明星" + star.getName() + "见面了。"); } public void business() { System.out.println(company.getName() + "与明星" + star.getName() + "洽淡业务。"); } } ``` ## 3.6 合成复用原则 合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。 通常类的复用分为继承复用和合成复用两种。 继承复用虽然有简单和易实现的优点,但它也存在以下缺点: 1. 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。 2. 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。 3. 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。 采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点: 1. 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。 2. 对象间的耦合度低。可以在类的成员位置声明抽象。 3. 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。 下面看一个例子来理解合成复用原则 【例】汽车分类管理程序 汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。类图如下: ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E5%90%88%E6%88%90%E5%A4%8D%E7%94%A8%E5%8E%9F%E5%88%99.png) 从上面类图我们可以看到使用继承复用产生了很多子类,如果现在又有新的动力源或者新的颜色的话,就需要再定义新的类。我们试着将继承复用改为聚合复用看一下。 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E5%90%88%E6%88%90%E5%A4%8D%E7%94%A8%E5%8E%9F%E5%88%991.png) # 4,创建者模式 创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。 这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。 创建型模式分为: * 单例模式 * 工厂方法模式 * 抽象工程模式 * 原型模式 * 建造者模式 # 5,单例模式 ## 5.1 单例模式的定义和特点 **单例(Singleton)模式的定义:**指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。 在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。 单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型。**J2EE 标准中的 ServletgContext 和 ServletContextConfig、Spring 框架应用中的 ApplicationContext、数据库中的连接池等也都是单例模式。** **单例模式有 3 个特点:** 1. 单例类只有一个实例对象; 2. 该单例对象必须由单例类自行创建; 3. 单例类对外提供一个访问该单例的全局访问点。 **单例模式的优点**: - 单例模式可以保证内存里只有一个实例,减少了内存的开销。 - 可以避免对资源的多重占用。 - 单例模式设置全局访问点,可以优化和共享资源的访问。 **单例模式的缺点:** - 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。 - 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。 - 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。 ## 5.2 单例模式的结构与实现 ### 5.2.1 单例模式的结构 1. 单例类:包含一个实例且能自行创建这个实例的类。 2. 访问类:使用单例的类。 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_14-52-45.png) ### 5.2 代码实现 > 单例设计模式分类两种: > > ​ 饿汉式:类加载就会导致该单实例对象被创建 > > ​ 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建 **饿汉式(静态变量)** ```java package com.zhuang.singleton.type1; /** * @Classname SingletonTest01 * @Description 饿汉式(静态变量) * @Date 2021/3/17 9:26 * @Created by dell */ public class SingletonTest01 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); //判断是否为单例 System.out.println(instance == instance2); System.out.println("intstance的哈希值" + instance.hashCode()); System.out.println("intstance2的哈希值" + instance2.hashCode()); } } class Singleton { //1.构造器私有化,外部能new、 private Singleton() { } //本类内部创建对象实例 private final static Singleton instance = new Singleton(); //对外部提供一个公有的静态方法 public static Singleton getInstance() { return instance; } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_07-40-53.png) 说明: ​ 该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。 **静态代码块** ```java package com.zhuang.singleton.type2; /** * @Classname SingletonTest02 * @Description 静态代码块 * @Date 2021/3/17 9:35 * @Created by dell */ public class SingletonTest02 { public static void main(String[] args) { Singleton2 instance = Singleton2.getInstance(); Singleton2 instance2 = Singleton2.getInstance(); //判断是否为单例 System.out.println(instance == instance2); System.out.println("intstance的哈希值" + instance.hashCode()); System.out.println("intstance2的哈希值" + instance2.hashCode()); } } class Singleton2 { //1.构造器私有化,外部能new、 private Singleton2() { } //本类内部创建对象实例 private static Singleton2 instance; /* 在静态代码块中创建对象 */ static { instance = new Singleton2(); } //对外部提供一个公有的静态方法 public static Singleton2 getInstance() { return instance; } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_07-43-38.png) 说明: ​ 该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本上一样,**当然该方式也存在内存浪费问题。** **懒汉式 线程不安全** ```java package com.zhuang.singleton.type3; /** * @Classname SingletonTest03 * @Description 懒汉式 线程不安全 * @Date 2021/3/17 9:39 * @Created by dell */ public class SingletonTest03 { public static void main(String[] args) { System.out.println("懒汉式,线程不安全!!!"); Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); //判断是否为单例 System.out.println(instance == instance2); System.out.println("intstance的哈希值" + instance.hashCode()); System.out.println("intstance2的哈希值" + instance2.hashCode()); } } class Singleton { private static Singleton instance; private Singleton() { } //提供一个静态的公有方法 当使用到该方法时,才去创建instance public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_07-43-52.png) 说明: ​ 从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。 **懒汉式(线程安全 , 同步方法)** ```java package com.zhuang.singleton.type4; /** * @Classname SingletonTest04 * @Description 懒汉式(线程安全 , 同步方法) * @Date 2021/3/17 9:46 * @Created by dell */ public class SingletonTest04 { public static void main(String[] args) { System.out.println("懒汉式,线程安全!!!"); Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); //判断是否为单例 System.out.println(instance == instance2); System.out.println("intstance的哈希值" + instance.hashCode()); System.out.println("intstance2的哈希值" + instance2.hashCode()); } } class Singleton { private static Singleton instance; private Singleton() { } //提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题 public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_07-44-06.png) 说明: ​ 该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。 **懒汉式(线程安全 , 同步代码块)** ```java package com.zhuang.singleton.type5; /** * @Classname SingletonTest05 * @Description 懒汉式(线程安全 , 同步代码块) * @Date 2021/3/17 9:50 * @Created by dell */ public class SingletonTest05 { public static void main(String[] args) { System.out.println("懒汉式,线程安全!,同步代码块"); Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); //判断是否为单例 System.out.println(instance == instance2); System.out.println("intstance的哈希值" + instance.hashCode()); System.out.println("intstance2的哈希值" + instance2.hashCode()); } } class Singleton { private static Singleton instance; private Singleton() { } //提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题 public static synchronized Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { instance = new Singleton(); } } return instance; } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_07-44-38.png) 双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。 要解决双重检查锁模式带来空指针异常的问题,只需要使用 `volatile` 关键字, `volatile` 关键字可以保证可见性和有序性。 ```java package com.zhuang.singleton.type6; /** * @Classname SingletonTest06 * @Description 双重检查,推荐使用 * @Date 2021/3/17 9:54 * @Created by dell */ public class SingletonTest06 { public static void main(String[] args) { System.out.println("懒汉式,双重检查,推荐使用"); Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); //判断是否为单例 System.out.println(instance == instance2); System.out.println("intstance的哈希值" + instance.hashCode()); System.out.println("intstance2的哈希值" + instance2.hashCode()); } } class Singleton { private static volatile Singleton instance; private Singleton() { } //提供一个静态的公有方法,加入双重检查代码,加入同步处理的代码,解决懒加载的问题 //保证效率。推荐使用 public static synchronized Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { instance = new Singleton(); } } return instance; } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_07-45-29.png) 小结: **添加 `volatile` 关键字之后的双重检查锁模式是一种比较好的单例实现模式**,能够保证在多线程的情况下线程安全也不会有性能问题。 **静态内部类实现单例模式!** ```java package com.zhuang.singleton.type7; /** * @Classname SingletonTest07 * @Description 静态内部类实现单例模式! * @Date 2021/3/17 9:59 * @Created by dell */ public class SingletonTest07 { public static void main(String[] args) { System.out.println("静态内部类实现单例模式"); Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); //判断是否为单例 System.out.println(instance == instance2); System.out.println("intstance的哈希值" + instance.hashCode()); System.out.println("intstance2的哈希值" + instance2.hashCode()); } } class Singleton { private static Singleton instance; private Singleton() { } //写一个静态内部类,该类中有个静态属性,Singleton public static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } //提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE; public static synchronized Singleton getInstance() { return SingletonInstance.INSTANCE; } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_07-45-43.png) 说明: ​ 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder 并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。 小结: ​ 静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。 **枚举的方式实现单例模式** ```java package com.zhuang.singleton.type8; /** * @Classname SingletonTest08 * @Description 枚举的方式实现单例模式 * @Date 2021/3/17 10:06 * @Created by dell */ public class SingletonTest08 { public static void main(String[] args) { System.out.println("枚举的方式实现单例模式,推荐使用"); Singleton instance = Singleton.INSTANCE; Singleton instance2 = Singleton.INSTANCE; System.out.println(instance == instance2); //判断是否为单例 System.out.println(instance == instance2); System.out.println("intstance的哈希值" + instance.hashCode()); System.out.println("intstance2的哈希值" + instance2.hashCode()); } } /* 枚举 */ enum Singleton { INSTANCE;//属性 public void method() { System.out.println("method()方法被调用..."); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_07-47-27.png) 说明: ​ 枚举方式属于恶汉式方式。 ## 5.3 单例模式的应用场景 - 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。 - 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。 - 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。 - 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。 - 频繁访问数据库或文件的对象。 - 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。 - 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。 ## 5.4 存在的问题 破坏单例模式: 使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射。 * 序列化反序列化 **Singleton类:** ```java public class Singleton implements Serializable { //私有构造方法 private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } //对外提供静态方法获取该对象 public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } ``` **Test类:** ```java public class Test { public static void main(String[] args) throws Exception { //往文件中写对象 //writeObject2File(); //从文件中读取对象 Singleton s1 = readObjectFromFile(); Singleton s2 = readObjectFromFile(); //判断两个反序列化后的对象是否是同一个对象 System.out.println(s1 == s2); } private static Singleton readObjectFromFile() throws Exception { //创建对象输入流对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\dell\\Desktop\\a.txt")); //第一个读取Singleton对象 Singleton instance = (Singleton) ois.readObject(); return instance; } public static void writeObject2File() throws Exception { //获取Singleton类的对象 Singleton instance = Singleton.getInstance(); //创建对象输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\dell\\Desktop\\a.txt")); //将instance对象写出到文件中 oos.writeObject(instance); } } ``` > 上面代码运行结果是`false`,表明序列化和反序列化已经破坏了单例设计模式。 * 反射 **Singleton类:** ```java public class Singleton { //私有构造方法 private Singleton() {} private static volatile Singleton instance; //对外提供静态方法获取该对象 public static Singleton getInstance() { if(instance != null) { return instance; } synchronized (Singleton.class) { if(instance != null) { return instance; } instance = new Singleton(); return instance; } } } ``` **Test类:** ```java public class Test { public static void main(String[] args) throws Exception { //获取Singleton类的字节码对象 Class clazz = Singleton.class; //获取Singleton类的私有无参构造方法对象 Constructor constructor = clazz.getDeclaredConstructor(); //取消访问检查 constructor.setAccessible(true); //创建Singleton类的对象s1 Singleton s1 = (Singleton) constructor.newInstance(); //创建Singleton类的对象s2 Singleton s2 = (Singleton) constructor.newInstance(); //判断通过反射创建的两个Singleton对象是否是同一个对象 System.out.println(s1 == s2); } } ``` > 上面代码运行结果是`false`,表明序列化和反序列化已经破坏了单例设计模式 > 注意:枚举方式不会出现这两个问题。 **问题的解决** * 序列化、反序列方式破坏单例模式的解决方法 在Singleton类中添加`readResolve()`方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。 **Singleton类:** ```java public class Singleton implements Serializable { //私有构造方法 private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } //对外提供静态方法获取该对象 public static Singleton getInstance() { return SingletonHolder.INSTANCE; } /** * 下面是为了解决序列化反序列化破解单例模式 */ private Object readResolve() { return SingletonHolder.INSTANCE; } } ``` **源码解析:** ObjectInputStream类 ```java public final Object readObject() throws IOException, ClassNotFoundException{ ... // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { Object obj = readObject0(false);//重点查看readObject0方法 ..... } private Object readObject0(boolean unshared) throws IOException { ... try { switch (tc) { ... case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法 ... } } finally { depth--; bin.setBlockDataMode(oldMode); } } private Object readOrdinaryObject(boolean unshared) throws IOException { ... //isInstantiable 返回true,执行 desc.newInstance(),通过反射创建新的单例类, obj = desc.isInstantiable() ? desc.newInstance() : null; ... // 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { // 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量 // 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。 Object rep = desc.invokeReadResolve(obj); ... } return obj; } ``` * 反射方式破解单例的解决方法 ```java public class Singleton { //私有构造方法 private Singleton() { /* 反射破解单例模式需要添加的代码 */ if(instance != null) { throw new RuntimeException(); } } private static volatile Singleton instance; //对外提供静态方法获取该对象 public static Singleton getInstance() { if(instance != null) { return instance; } synchronized (Singleton.class) { if(instance != null) { return instance; } instance = new Singleton(); return instance; } } } ``` 说明: 这种方式比较好理解。当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。 ## 5.5 JDK源码解析-Runtime类 Runtime类就是使用的单例设计模式。 1. 通过源代码查看使用的是哪儿种单例模式 ```java public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class Runtime are instance * methods and must be invoked with respect to the current runtime object. * * @return the Runtime object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} ... } ``` 从上面源代码中可以看出Runtime类使用的是恶汉式(静态属性)方式来实现单例模式的。 2. 使用Runtime类中的方法 ```java public class RuntimeDemo { public static void main(String[] args) throws IOException { //获取Runtime类对象 Runtime runtime = Runtime.getRuntime(); //返回 Java 虚拟机中的内存总量。 System.out.println(runtime.totalMemory()); //返回 Java 虚拟机试图使用的最大内存量。 System.out.println(runtime.maxMemory()); //创建一个新的进程执行指定的字符串命令,返回进程对象 Process process = runtime.exec("ipconfig"); //获取命令执行后的结果,通过输入流获取 InputStream inputStream = process.getInputStream(); byte[] arr = new byte[1024 * 1024* 100]; int b = inputStream.read(arr); System.out.println(new String(arr,0,b,"gbk")); } } ``` # 6,原型模式 ## 6.1原形模式的定义和特点 原型(Prototype)模式的定义如下:**用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。**用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。 **原型模式的优点:** - Java自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。 - 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。 **原型模式的缺点:** - 需要为每一个类都配置一个 clone 方法 - clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。 - 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。 ## 6.2 原型模式的结构与实现 ### 6.2.1 原形模式的结构 原型模式包含以下主要角色。 1. 抽象原型类:规定了具体原型对象必须实现的接口。 2. 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。 3. 访问类:使用具体原型类中的 clone() 方法来复制新的对象。 ### 6.2.2 代码实现 > 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。 > > 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。 #### 6.2.2.1 浅拷贝 **IdCard** ```java package com.zhuang.prototype.shallowclone; /** * @Classname IdCard * @Description 浅拷贝的示例 * @Date 2021/3/19 12:16 * @Created by dell */ public class IdCard { private String id; public IdCard(String id) { this.id = id; } @Override public String toString() { return "IdCard{" + "id=" + id + '}'; } } ``` **Person** ```java package com.zhuang.prototype.shallowclone; /** * @Classname Person * @Description 浅拷贝的示例 * @Date 2021/3/19 12:17 * @Created by dell */ public class Person implements Cloneable { private String name; private int age; private IdCard idCard; public Person() { } public Person(String name, int age, IdCard idCard) { this.name = name; this.age = age; this.idCard = idCard; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public IdCard getIdCard() { return idCard; } public void setIdCard(IdCard idCard) { this.idCard = idCard; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", idCard=" + idCard + ", idCard.hashCode=" + idCard.hashCode() + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } ``` **PersonTest** ```java package com.zhuang.prototype.shallowclone; /** * @Classname PersonTest * @Description 浅拷贝测试类 * @Date 2021/3/19 12:17 * @Created by dell */ public class PersonTest { public static void main(String[] args) throws Exception { Person person = new Person("张三", 20, new IdCard("10086")); Person person1 = (Person) person.clone(); Person person2 = (Person) person.clone(); System.out.println(person); System.out.println(person1); System.out.println(person2); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_07-51-19.png) 我们发现可以通过实现`implements Cloneable`来完成浅拷贝,基本变量是值传递克隆,而引用对象`IdCard`则是引用传递,这不符合我们面向对象思想,每一个`Person`应该都有一个独立的`IdCard`,而不是共用一个,而要解决这种问题,我们需要使用深克隆 #### 6.2.2.2 深拷贝(第一种) **IdCard** ```java package com.zhuang.prototype.deepclone.one; /** * @Classname IdCard * @Description 深克隆的示例 * @Date 2021/3/19 12:33 * @Created by dell */ //实现Cloneable接口 public class IdCard implements Cloneable { private String id; public IdCard(String id) { this.id = id; } public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String toString() { return "IdCard{" + "id='" + id + '\'' + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } ``` **Person** ```java package com.zhuang.prototype.deepclone.one; /** * @Classname Person * @Description 深克隆的示例 * @Date 2021/3/19 12:33 * @Created by dell */ public class Person implements Cloneable { private String name; private int age; private IdCard idCard; public Person(String name, int age, IdCard idCard) { this.name = name; this.age = age; this.idCard = idCard; } public IdCard getIdCard() { return idCard; } public void setIdCard(IdCard idCard) { this.idCard = idCard; } @Override public String toString() { return "Person{" + "personHashCode=" + this.hashCode() + ", name='" + name + '\'' + ", age=" + age + ", idCard=" + idCard + ", idCardHashCode=" + idCard.hashCode() + '}'; } //深克隆需要自己手动实现,在对象引用中也要实现clone方法 @Override protected Object clone() throws CloneNotSupportedException { //完成基本数据类型的拷贝 //通过new关键字创建的对象是引用类型 Object person = super.clone(); //对引用类型单独处理 Person p = (Person) person; IdCard idCard = (IdCard) p.getIdCard().clone(); //实现自己的克隆 p.setIdCard(idCard); return p; } } ``` **PersonTest** ```java package com.zhuang.prototype.deepclone.one; import java.io.Serializable; /** * @Classname PersonTest * @Description 深克隆测试类 * @Date 2021/3/19 12:33 * @Created by dell */ public class PersonTest implements Serializable { public static void main(String[] args) throws Exception { Person person = new Person("张三", 20, new IdCard("10086")); Person person1 = (Person) person.clone(); Person person2 = (Person) person.clone(); System.out.println(person); System.out.println(person1); System.out.println(person2); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_07-51-19.png) 使用这种深克隆的方式,完美的解决了当数据类型为引用类型时,只是拷贝原引用对象地址而不是一个全新的引用对象的引用,但是这种实现有一个很大的弊端,需要在每一个对象中都实现clone方法,如果类全是你自己写的,那自然没问题,实现一下就行了,不过有点麻烦。但是,如果你引用的是第三方的一个类,无法修改源代码,这种方式,显然就无法实现深克隆了 #### 6.2.2.2 深拷贝(第二种) **IdCard** ```java package com.zhuang.prototype.deepclone.two; /** * @Classname IdCard * @Description 深克隆的示例2 * @Date 2021/3/19 12:33 * @Created by dell */ //实现Serializable接口 public class IdCard implements Serializable { private String id; public IdCard(String id) { this.id = id; } public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String toString() { return "IdCard{" + "id='" + id + '\'' + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } ``` **Person** ```java package com.zhuang.prototype.deepclone.two; import java.io.*; /** * @Classname Person * @Description 深克隆的示例2 * @Date 2021/3/19 12:33 * @Created by dell */ public class Person implements Serializable { private String name; private int age; private IdCard idCard; public Person(String name, int age, IdCard idCard) { this.name = name; this.age = age; this.idCard = idCard; } public IdCard getIdCard() { return idCard; } public void setIdCard(IdCard idCard) { this.idCard = idCard; } @Override public String toString() { return "Person{" + "personHashCode=" + this.hashCode() + ", name='" + name + '\'' + ", age=" + age + ", idCard=" + idCard + ", idCardHashCode=" + idCard.hashCode() + '}'; } //序列化的方式 public Person deelClone() { //创建流对象 ByteArrayOutputStream bos = null; ObjectOutputStream oos = null; ByteArrayInputStream bis = null; ObjectInputStream ois = null; try { //序列化 bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this); //反序列化 bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); return (Person) ois.readObject(); } catch (Exception e) { e.printStackTrace(); return null; } finally { try { ois.close(); bis.close(); oos.close(); bos.close(); } catch (IOException e) { e.printStackTrace(); } } } } ``` **PersonTest** ```java package com.zhuang.prototype.deepclone.two; /** * @Classname PersonTest * @Description 深克隆测试类 * @Date 2021/3/19 12:33 * @Created by dell */ public class PersonTest { public static void main(String[] args) throws Exception { //创建一个对象 Person person = new Person("张三", 20, new IdCard("10086")); //克隆两个对象 Person person1 = (Person) person.deelClone(); Person person2 = (Person) person.deelClone(); System.out.println("深拷贝(第二种 实现序列化接口)"); //打印三人信息 System.out.println(person); System.out.println(person1); System.out.println(person2); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_07-52-49.png) 这种方式我们需要手动编写deepClone方法,使用Java流中的序列化与反序列化来实现深克隆,但是这种实现,需要在每一个类中都继承序列化Serializable接口,这种方式,如果你调用的是第三方类,也有可能第三方类上没有实现Serializable序列化接口,但是一般来说,大多都会实现,总的来说,这种比较推荐使用,而且效率也高 ## 6.3 原型模式的应用场景 - 对象之间相同或相似,即只是个别的几个属性不同的时候。 - 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。 - 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。 - 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。 - 在 Spring中,原型模式应用的非常广泛,**例如 scope='prototype'、JSON.parseObject()** 等都是原型模式的具体应用。 ## 6.4 原型模式的注意事项和细节 - 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能提高效率 - 不用重新初始化对象,动态地获得对象运行时的状态 - 如果原始对象发生变化(增加或减少属性),其他克隆对象也会发生相应的变化,无需修改代码 # 7,工厂模式 ## 7.1 工厂模式的定义和特点 **工厂模式的定义**:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。 按实际业务场景划分,工厂模式有 3 种不同的实现方式,分别是简单工厂模式、工厂方法模式和抽象工厂模式。 我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。 在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。 简单来说,简单工厂模式有一个具体的工厂类,可以生成多个不同的产品,属于创建型设计模式。简单工厂模式不在 GoF 23 种设计模式之列。 简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了“开闭原则”。 **优点:** 1. 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。 2. 客户端无需知道所创建具体产品的类名,只需知道参数即可。 3. 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。 **缺点:** 1. 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。 2. 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度 3. 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂 4. 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。 ## 7.2 工厂模式的结构与实现 简单工厂模式的主要角色如下: - 简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。 - 抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。 - 具体产品(ConcreteProduct):是简单工厂模式的创建目标。 **Shape** ```java package com.zhuang.factory.simplefactory; /** * @Classname Shape * @Description 产品接口类 * @Date 2021/3/18 15:40 * @Created by dell */ public interface Shape { void draw(); } ``` **Circle** ```java package com.zhuang.factory.simplefactory; /** * @Classname Circle * @Description 产品实现类 * @Date 2021/3/18 15:43 * @Created by dell */ public class Circle implements Shape { @Override public void draw() { System.out.println("Rectangle-->绘制圆形"); } } ``` **Rectangle** ```java package com.zhuang.factory.simplefactory; /** * @Classname Rectangle * @Description 产品实现类 * @Date 2021/3/18 15:40 * @Created by dell */ public class Rectangle implements Shape { @Override public void draw() { System.out.println("Rectangle-->绘制长方形"); } } ``` **Square** ```java package com.zhuang.factory.simplefactory; /** * @Classname Square * @Description 产品实现类 * @Date 2021/3/18 15:40 * @Created by dell */ public class Square implements Shape { @Override public void draw() { System.out.println("Rectangle-->绘制方形"); } } ``` **ShapeFactory** ```java package com.zhuang.factory.simplefactory; /** * @Classname ShapeFactory * @Description 简单工厂类 * @Date 2021/3/18 15:43 * @Created by dell */ public class ShapeFactory { public static Shape createShape(String shapeType) { if ("Rectangle".equalsIgnoreCase(shapeType)) { return new Rectangle(); } if ("Circle".equalsIgnoreCase(shapeType)) { return new Circle(); } if ("Square".equalsIgnoreCase(shapeType)) { return new Square(); } return null; } } ``` **ShapeFactoryTest** ```java package com.zhuang.factory.simplefactory; /** * @Classname ShapeFactoryTest * @Description 简单工厂测试类 * @Date 2021/3/18 15:47 * @Created by dell */ public class ShapeFactoryTest { public static void main(String[] args) { Shape rectangle = ShapeFactory.createShape("Rectangle"); rectangle.draw(); Shape circle = ShapeFactory.createShape("Circle"); circle.draw(); Shape square = ShapeFactory.createShape("Square"); square.draw(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-01-45.png) **Java.util.Calendar 源码使用到了简单工厂模式** ## 7.3 抽象工厂模式的定义和特点 抽象工厂模式(Abstract Factory)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类,每个生成的工厂都能按照工厂模式提供对象。 使用抽象工厂模式一般要满足以下条件。 - 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。 - 系统一次只可能消费其中某一族产品,即同族的产品一起使用。 **主要优点如下:** - 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。 - 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。 - 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。 **其缺点是:** - 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。 ### 7.4 抽象工厂模式的结构与实现 #### 7.4.1 抽象工厂模式的结构 1. 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。 2. 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。 3. 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。 4. 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_19-59-50.png) #### 7.3.2 代码实现 **Shape** ```java package com.zhuang.factory.absfactory; /** * @Classname Shape * @Description 产品接口类 * @Date 2021/3/18 15:40 * @Created by dell */ public interface Shape { void draw(); } ``` **Circle** ```java package com.zhuang.factory.absfactory; /** * @Classname Circle * @Description 产品实现类 * @Date 2021/3/18 15:43 * @Created by dell */ public class Circle implements Shape { @Override public void draw() { System.out.println("Rectangle-->绘制圆形"); } } ``` **Square** ```java package com.zhuang.factory.absfactory; /** * @Classname Square * @Description 产品实现类 * @Date 2021/3/18 15:40 * @Created by dell */ public class Square implements Shape { @Override public void draw() { System.out.println("Rectangle-->绘制方形"); } } ``` **Rectangle** ```java package com.zhuang.factory.absfactory; /** * @Classname Rectangle * @Description 产品实现类 * @Date 2021/3/18 15:40 * @Created by dell */ public class Rectangle implements Shape { @Override public void draw() { System.out.println("Rectangle-->绘制长方形"); } } ``` **ShapeFactory** ```java package com.zhuang.factory.absfactory; /** * @Classname ShapeFactory * @Description 简单工厂类 * @Date 2021/3/18 15:43 * @Created by dell */ public class ShapeFactory extends AbstractFactory { @Override public Shape createShape(String shapeType) { if ("Rectangle".equalsIgnoreCase(shapeType)) { return new Rectangle(); } if ("Circle".equalsIgnoreCase(shapeType)) { return new Circle(); } if ("Square".equalsIgnoreCase(shapeType)) { return new Square(); } return null; } @Override public Color createColor(String colorType) { return null; } } ``` **Color** ```java package com.zhuang.factory.absfactory; /** * @Classname Color * @Description 颜色产品接口类 * @Date 2021/3/18 16:03 * @Created by dell */ public interface Color { void fill(); } ``` **Red** ```java package com.zhuang.factory.absfactory; /** * @Classname Red * @Description 颜色产品实现类 * @Date 2021/3/18 16:03 * @Created by dell */ public class Red implements Color { @Override public void fill() { System.out.println("Red-->填充红色"); } } ``` **Yellow** ```java package com.zhuang.factory.absfactory; /** * @Classname Yellow * @Description 颜色产品实现类 * @Date 2021/3/18 16:04 * @Created by dell */ public class Yellow implements Color { @Override public void fill() { System.out.println("Red-->填充黄色"); } } ``` **Black** ```java package com.zhuang.factory.absfactory; /** * @Classname Black * @Description 颜色产品实现类 * @Date 2021/3/18 16:05 * @Created by dell */ public class Black implements Color { @Override public void fill() { System.out.println("Red-->填充黑色"); } } ``` **ColorFactory** ```java package com.zhuang.factory.absfactory; import com.zhuang.factory.simplefactory.Circle; import com.zhuang.factory.simplefactory.Rectangle; import com.zhuang.factory.simplefactory.Square; /** * @Classname ColorFactory * @Description 颜色工厂类的编写 * @Date 2021/3/18 16:05 * @Created by dell */ public class ColorFactory extends AbstractFactory { @Override public Shape createShape(String shapeType) { return null; } @Override public Color createColor(String colorType) { if ("Red".equalsIgnoreCase(colorType)) { return new Red(); } if ("Black".equalsIgnoreCase(colorType)) { return new Black(); } if ("Yellow".equalsIgnoreCase(colorType)) { return new Yellow(); } return null; } } ``` **AbstractFactory** ```java package com.zhuang.factory.absfactory; /** * @Classname AbstractFactory * @Description 产品家族抽象类 * @Date 2021/3/18 16:06 * @Created by dell */ public abstract class AbstractFactory { public abstract Shape createShape(String shapeType); public abstract Color createColor(String colorType); } ``` **AbstractFactoryProducer** ```java package com.zhuang.factory.absfactory; /** * @Classname AbstractFactoryProducer * @Description 抽象类的工厂类 * @Date 2021/3/18 16:10 * @Created by dell */ public class AbstractFactoryProducer { public static AbstractFactory createFactory(String choice) { if ("Shape".equalsIgnoreCase(choice)) { return new ShapeFactory(); } if ("Color".equalsIgnoreCase(choice)) { return new ColorFactory(); } return null; } } ``` **AbstractFactoryProducerTest** ```java package com.zhuang.factory.absfactory; /** * @Classname AbstractFactoryProcucerTest * @Description 抽象类的工厂类测试类 * @Date 2021/3/18 16:15 * @Created by dell */ public class AbstractFactoryProducerTest { public static void main(String[] args) { AbstractFactory shapeFactory = AbstractFactoryProducer.createFactory("Shape"); assert shapeFactory != null; Shape rectangle = shapeFactory.createShape("Rectangle"); Shape circle = shapeFactory.createShape("Circle"); Shape square = shapeFactory.createShape("Square"); rectangle.draw(); circle.draw(); square.draw(); System.out.println("===================================="); AbstractFactory colorFactory = AbstractFactoryProducer.createFactory("Color"); Color red = colorFactory.createColor("Red"); Color yellow = colorFactory.createColor("Yellow"); Color black = colorFactory.createColor("Black"); red.fill(); yellow.fill(); black.fill(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-02-04.png) ### 7.5 抽象工厂模式的应用场景 1. 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。 2. 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。 3. 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。 # 8,建造者模式 ## 8.1 建造者模式的定义和特点 建造者(Builder)模式的定义:**指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示**,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。 **该模式的主要优点如下:** 1. 封装性好,构建和表示分离。 2. 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。 3. 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。 **其缺点如下:** 1. 产品的组成部分必须相同,这限制了其使用范围。 2. 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。 ## 8.2 建造者模式的结构与实现 ### 8.2.1 建造者模式的结构 1. 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。 2. 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。 3. 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。 4. 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_20-14-58.png) ### 8.2.2 代码实现 **House 产品角色** ```java package com.zhuang.builder; /** * @Classname House * @Description 产品实现类 * @Date 2021/3/20 11:49 * @Created by dell */ public class House { private String ground; private String wall; private String roofed; public House() { } public House(String ground, String wall, String roofed) { this.ground = ground; this.wall = wall; this.roofed = roofed; } public String getGround() { return ground; } public void setGround(String ground) { this.ground = ground; } public String getWall() { return wall; } public void setWall(String wall) { this.wall = wall; } public String getRoofed() { return roofed; } public void setRoofed(String roofed) { this.roofed = roofed; } @Override public String toString() { return "House{" + "ground='" + ground + '\'' + ", wall='" + wall + '\'' + ", roofed='" + roofed + '\'' + '}'; } } ``` **HouseBuilder 抽象建造者** ```java package com.zhuang.builder; /** * @Classname HouseBuilder * @Description 抽象建造者 * @Date 2021/3/20 11:49 * @Created by dell */ public abstract class HouseBuilder { //创建产品对象 protected House house = new House(); //生产产品流程 public abstract void buildGround(); public abstract void buildWall(); public abstract void buildRoofed(); //返回产品对象 public House getHouse() { return house; } } ``` **HighHouse 具体建造者** ```java package com.zhuang.builder; /** * @Classname HighHouse * @Description 具体建造者 * @Date 2021/3/20 11:51 * @Created by dell */ public class HighHouse extends HouseBuilder { @Override public void buildGround() { house.setGround("100平"); System.out.println("高楼:打地基"); } @Override public void buildWall() { house.setWall("50米"); System.out.println("高楼:砌墙50米"); } @Override public void buildRoofed() { house.setRoofed("天窗"); System.out.println("别墅:盖天窗"); } } ``` **VillaHouse 具体建造者** ```java package com.zhuang.builder; /** * @Classname VillaHouse * @Description 具体建造者 * @Date 2021/3/20 11:51 * @Created by dell */ public class VillaHouse extends HouseBuilder { @Override public void buildGround() { house.setGround("200平"); System.out.println("别墅:打地基"); } @Override public void buildWall() { house.setWall("10米"); System.out.println("别墅:砌墙10米"); } @Override public void buildRoofed() { house.setRoofed("天花板"); System.out.println("别墅:盖天花板"); } } ``` **HouseDirector 指挥者** ```java package com.zhuang.builder; /** * @Classname HouseDirector * @Description 工程指挥者 * @Date 2021/3/20 11:50 * @Created by dell */ public class HouseDirector { private HouseBuilder houseBuilder; public HouseDirector(HouseBuilder houseBuilder) { this.houseBuilder = houseBuilder; } public House build() { houseBuilder.buildGround(); houseBuilder.buildWall(); houseBuilder.buildRoofed(); return houseBuilder.getHouse(); } } ``` **Client** ```java package com.zhuang.builder; /** * @Classname Client * @Description 产品试用客户端 * @Date 2021/3/20 11:51 * @Created by dell */ public class Client { public static void main(String[] args) { House house1 = new HouseDirector(new VillaHouse()).build(); System.out.println(house1); System.out.println("============================================"); House house2 = new HouseDirector(new HighHouse()).build(); System.out.println(house2); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-06-26.png) ## 8.3 建造者模式的应用场景 - 相同的方法,不同的执行顺序,产生不同的结果。 - 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。 - 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。 - 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。 # 9,创建者模式对比 ### 9.1 工厂方法模式VS建造者模式 工厂方法模式注重的是**整体对象的创建方式**;而建造者模式注重的是**部件构建的过程**,意在通过一步一步地精确构造创建出一个复杂的对象。 我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。 ### 9.2 抽象工厂模式VS建造者模式 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。 建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。 - 建造者模式**更加注重方法的调用顺序**,工厂模式**注重创建对象**。 - 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样 - 关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。 - 建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。 # 10,适配器模式 ## 10.1 适配器模式的定义和特点 适配器模式(Adapter)的定义如下:**将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。**适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高。 **该模式的主要优点如下:** - 客户端通过适配器可以透明地调用目标接口。 - 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。 - 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。 - 在很多业务场景中符合开闭原则。 **其缺点是:** - 适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。 - 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。 ## 10.2 适配器模式的结构与实现 ### 10.2.1 适配器模式的结构 1. 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。 2. 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。 3. 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。 ### 10.2.2 代码实现 #### 10.2.2.1**类适配器模式** **Voltage5V 目标接口** ```java package com.zhuang.adapter.classadapter; /** * @Classname Voltage5V * @Description 定义直流电 * @Date 2021/3/21 14:14 * @Created by dell */ public interface Voltage5V { //定义一个标准充电器来实现 public int output5V(); } ``` **Voltage220V** ```java package com.zhuang.adapter.classadapter; /** * @Classname Voltage220V * @Description 创建交流电 * @Date 2021/3/21 14:13 * @Created by dell */ public class Voltage220V { public int output220V() { System.out.println("voltage 输出220伏"); return 220; } } ``` **VoltageAdapter** ```java package com.zhuang.adapter.classadapter; /** * @Classname VoltageAdapter * @Description 创建充电器 * @Date 2021/3/21 14:14 * @Created by dell */ public class VoltageAdapter extends Voltage220V implements Voltage5V { @Override public int output5V() { //获取交流电220V int output220V = output220V(); //转为5V int output5V = output220V / 44; System.out.println("VoltageAdapter 输出5伏"); return output5V; } } ``` **Phone** ```java package com.zhuang.adapter.classadapter; /** * @Classname Phone * @Description 手机类 * @Date 2021/3/21 14:15 * @Created by dell */ public class Phone { public void charging(Voltage5V voltage5V) { if (voltage5V.output5V() == 5) { System.out.println("电压5伏,可以充电"); } else if (voltage5V.output5V() > 5) { System.out.println("电压过大,不可以充电"); } } } ``` **Client** ```java package com.zhuang.adapter.classadapter; /** * @Classname Client * @Description 客户端类 * @Date 2021/3/21 14:15 * @Created by dell */ public class Client { public static void main(String[] args) { System.out.println("==类适配器=="); Phone phone = new Phone(); phone.charging(new VoltageAdapter()); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-07-10.png) **类适配器模式注意事项和细节** - Java是单继承机制,所以类适配器需要继承适配者(Adaptee,指Voltage220V)类,这点算是一个缺点,除此之外还必须要求目标(Target,指Voltage5V)必须是接口,有一定局限性; - 适配者Voltage220V类的方法在适配器VoltageAdapter类中都会暴露出来,也增加了使用的成本。但是由于其继承了适配者Voltage220V类,所以它可以根据需求重写该类的方法,使得适配器VoltageAdapter类的灵活性增强了。 #### 10.2.2.2 对象适配器模式 **Voltage5V** ```java package com.zhuang.adapter.objectadapter; /** * @Classname Voltage5V * @Description 充电5V * @Date 2021/3/21 14:32 * @Created by dell */ public interface Voltage5V { //定义一个标准充电器来实现 public int output5V(); } ``` **Voltage220V** ```java package com.zhuang.adapter.objectadapter; /** * @Classname Voltage220V * @Description 输出220V类 * @Date 2021/3/21 14:32 * @Created by dell */ public class Voltage220V { public int output220V() { System.out.println("voltage 输出220伏"); return 220; } } ``` **VoltageAdapter** ```java package com.zhuang.adapter.objectadapter; /** * @Classname VoltageAdapter * @Description 适配器类 * @Date 2021/3/21 14:33 * @Created by dell */ public class VoltageAdapter implements Voltage5V { private Voltage220V voltage220V; public VoltageAdapter(Voltage220V voltage220V) { this.voltage220V = voltage220V; } @Override public int output5V() { //获取交流电220V int output220V = voltage220V.output220V(); //转为5V int output5V = output220V / 44; System.out.println("VoltageAdapter 输出5伏"); return output5V; } } ``` **Phone** ```java package com.zhuang.adapter.objectadapter; /** * @Classname Phone * @Description 手机类 * @Date 2021/3/21 14:33 * @Created by dell */ public class Phone { public void charging(Voltage5V voltage5V) { if (voltage5V.output5V() == 5) { System.out.println("电压5伏,可以充电"); } else if (voltage5V.output5V() > 5) { System.out.println("电压过大,不可以充电"); } } } ``` **Client** ```java package com.zhuang.adapter.objectadapter; /** * @Classname Client * @Description 对象适配器测试类 * @Date 2021/3/21 14:33 * @Created by dell */ public class Client { public static void main(String[] args) { System.out.println("==对象适配器=="); Phone phone = new Phone(); phone.charging(new VoltageAdapter(new Voltage220V())); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-07-31.png) **对象适配器模式注意事项和细节** - 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 - 根据合成复用原则,使用组合替代继承, 所以它解决了类适配器中VoltageAdapter必须继承Voltage220V的局限性问题,也不再强制要求Voltage5V必须是接口。使用成本更低,更灵活。因此,对象适配器模式是适配器模式常用的一种。 #### 10.2.2.3 接口适配器模式 **Animation** ```java package com.zhuang.adapter.interfaceadapter; /** * @Classname Animation * @Description 动画接口 * @Date 2021/3/21 14:47 * @Created by dell */ public interface Animation { public void method1(); public void method2(); public void method3(); public void method4(); public void method5(); } ``` **AnimationAdapter** ```java package com.zhuang.adapter.interfaceadapter; /** * @Classname AnimationAdapter * @Description 接口适配器类 * @Date 2021/3/21 14:48 * @Created by dell */ public class AnimationAdapter implements Animation { //全部都空实现 @Override public void method1() { } @Override public void method2() { } @Override public void method3() { } @Override public void method4() { } @Override public void method5() { } } ``` **JFrameAnimation** ```java package com.zhuang.adapter.interfaceadapter; /** * @Classname JFrameAnimation * @Description 适配器子类 * @Date 2021/3/21 14:49 * @Created by dell */ public class JFrameAnimation extends AnimationAdapter { @Override public void method1() { System.out.println("method1()被调用了..."); } @Override public void method2() { System.out.println("method2()被调用了..."); } } ``` **Client** ```java package com.zhuang.adapter.interfaceadapter; /** * @Classname Client * @Description 客户端类 * @Date 2021/3/21 14:50 * @Created by dell */ public class Client { public static void main(String[] args) { JFrameAnimation animation = new JFrameAnimation(); animation.method1(); animation.method2(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-07-44.png) ## 10.3 SpringMVC源码解析 **Controller** ```java package com.zhuang.adapter.springmvc; /** * @Classname Controller * @Description springmvc的Controller源码 * @Date 2021/3/21 14:53 * @Created by dell */ //多种Controller实现 public interface Controller { } class HttpController implements Controller { public void doHttpHandler() { System.out.println("http..."); } } class SimpleController implements Controller { public void doSimplerHandler() { System.out.println("simple..."); } } class AnnotationController implements Controller { public void doAnnotationHandler() { System.out.println("annotation..."); } } ``` **DispatchServlet** ```java package com.zhuang.adapter.springmvc; import java.util.ArrayList; import java.util.List; /** * @Classname DispatchServlet * @Description springmvc的DispatchServlet源码 * @Date 2021/3/21 14:52 * @Created by dell */ public class DispatchServlet { public static List handlerAdapters = new ArrayList(); public DispatchServlet() { handlerAdapters.add(new AnnotationHandlerAdapter()); handlerAdapters.add(new HttpHandlerAdapter()); handlerAdapters.add(new SimpleHandlerAdapter()); } public void doDispatch() { // 此处模拟SpringMVC从request取handler的对象, // 适配器可以获取到希望的Controller HttpController controller = new HttpController(); // AnnotationController controller = new AnnotationController(); //SimpleController controller = new SimpleController(); // 得到对应适配器 HandlerAdapter adapter = getHandler(controller); // 通过适配器执行对应的controller对应方法 adapter.handle(controller); } public HandlerAdapter getHandler(Controller controller) { //遍历:根据得到的controller(handler), 返回对应适配器 for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(controller)) { return adapter; } } return null; } public static void main(String[] args) { new DispatchServlet().doDispatch(); // http... } } ``` **HandlerAdapter** ```java package com.zhuang.adapter.springmvc; /** * @Classname HandlerAdapter * @Description springmvc的HandlerAdapter源码 * @Date 2021/3/21 14:53 * @Created by dell */ ///定义一个Adapter接口 public interface HandlerAdapter { public boolean supports(Object handler); public void handle(Object handler); } // 多种适配器类 class SimpleHandlerAdapter implements HandlerAdapter { @Override public void handle(Object handler) { ((SimpleController) handler).doSimplerHandler(); } @Override public boolean supports(Object handler) { return (handler instanceof SimpleController); } } class HttpHandlerAdapter implements HandlerAdapter { @Override public void handle(Object handler) { ((HttpController) handler).doHttpHandler(); } @Override public boolean supports(Object handler) { return (handler instanceof HttpController); } } class AnnotationHandlerAdapter implements HandlerAdapter { @Override public void handle(Object handler) { ((AnnotationController) handler).doAnnotationHandler(); } @Override public boolean supports(Object handler) { return (handler instanceof AnnotationController); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-08-41.png) # 11,桥接模式 ## 11.1 桥接模式的定义和特点 桥接(Bridge)模式的定义如下:**将抽象与实现分离,使它们可以独立变化。**它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。 通过上面的讲解,我们能很好的感觉到桥接模式遵循了里氏替换原则和依赖倒置原则,最终实现了开闭原则,对修改关闭,对扩展开放。 **桥接(Bridge)模式的优点是:** - 抽象与实现分离,扩展能力强 - 符合开闭原则 - 符合合成复用原则 - 其实现细节对客户透明 **缺点是:** 由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。 ## 11.2 桥接模式的结构与实现 ### 11.2.1 桥接模式的结构 1. 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。 2. 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。 3. 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。 4. 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。 ![](C:\Users\dell\Desktop\Notes\设计模式\img\Snipaste_2021-03-30_20-43-27.png) ### 11.2.2 代码实现 **Brand 抽象化角色** ```java package com.zhuang.bridge; /** * @Classname Brand * @Description 品牌类 * @Date 2021/3/22 9:31 * @Created by dell */ public interface Brand { void open(); void call(); void close(); } ``` **Vivo 拓展抽象化角色** ```java package com.zhuang.bridge; /** * @Classname Vivo * @Description 手机品牌 实现品牌接口 * @Date 2021/3/22 9:30 * @Created by dell */ public class Vivo implements Brand { @Override public void open() { System.out.println("Vivo手机开机"); } @Override public void call() { System.out.println("Vivo手机打电话"); } @Override public void close() { System.out.println("Vivo手机关机"); } } ``` **XiaoMi 拓展抽象化角色** ```java package com.zhuang.bridge; /** * @Classname XiaoMi * @Description 手机品牌 实现品牌接口 * @Date 2021/3/22 9:30 * @Created by dell */ public class XiaoMi implements Brand { @Override public void open() { System.out.println("XiaoMi手机开机"); } @Override public void call() { System.out.println("XiaoMi手机打电话"); } @Override public void close() { System.out.println("XiaoMi手机关机"); } } ``` **Phone** ```java package com.zhuang.bridge; /** * @Classname Phone * @Description 手机类 抽象类 * @Date 2021/3/22 9:30 * @Created by dell */ public abstract class Phone { //组合品牌 private Brand brand; //构造器 public Phone(Brand brand) { super(); this.brand = brand; } protected void open() { this.brand.open(); } protected void close() { this.brand.close(); } protected void call() { this.brand.call(); } } ``` **FoldedPhone** ```java package com.zhuang.bridge; /** * @Classname FoldedPhone * @Description 折叠手机类 * @Date 2021/3/22 9:31 * @Created by dell */ public class FoldedPhone extends Phone { //构造器 public FoldedPhone(Brand brand) { super(brand); } @Override public void open() { super.open(); System.out.println("折叠手机样式"); } @Override public void call() { super.call(); System.out.println("折叠手机样式"); } @Override public void close() { super.close(); System.out.println("折叠手机样式"); } } ``` **UpRightPhone** ```java package com.zhuang.bridge; /** * @Classname UpRightPhone * @Description 直立手机类 * @Date 2021/3/22 9:33 * @Created by dell */ public class UpRightPhone extends Phone { //构造器 public UpRightPhone(Brand brand) { super(brand); } @Override public void open() { super.open(); System.out.println("直立手机样式"); } @Override public void call() { super.call(); System.out.println("直立手机样式"); } @Override public void close() { super.close(); System.out.println("直立手机样式"); } } ``` **Client** ```java package com.zhuang.bridge; /** * @Classname Client * @Description 客户端类 * @Date 2021/3/22 9:30 * @Created by dell */ public class Client { public static void main(String[] args) { Phone phone1 = new FoldedPhone(new XiaoMi()); phone1.open(); phone1.call(); phone1.close(); System.out.println("=============================="); Phone phone2 = new UpRightPhone(new Vivo()); phone2.open(); phone2.call(); phone2.close(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-09-15.png) ## 11.3 桥接模式的应用场景 当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。 1. 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。 2. 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。 3. 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。 # 12,装饰器模式 ## 12.1 装饰器模式的定义和特点 装饰器(Decorator)模式的定义:**指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式**,它属于对象结构型模式。 **装饰器模式的主要优点有:** - 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用 - 通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果 - 装饰器模式完全遵守开闭原则 **其主要缺点是:** - 装饰器模式会增加许多子类,过度使用会增加程序得复杂性。 ## 12.2 装饰器模式的结构与实现 通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰器模式的目标 ### 12.2.1 装饰器模式的结构 1. 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。 2. 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。 3. 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。 4. 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。 ### 12.2.2 代码实现 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_20-52-56.png) **FastFood 抽象构件角色** ```java package com.zhuang.decorator; /** * @Classname FastFood * @Description 快餐类 接口 * @Date 2021/3/23 21:54 * @Created by dell */ public abstract class FastFood { private float price; private String desc; //描述 public FastFood() { } public FastFood(float price, String desc) { this.price = price; this.desc = desc; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } //抽象方法 获取价格 public abstract float cost(); } ``` **FiredRice 具体构建角色** ```java package com.zhuang.decorator; /** * @Classname FiredRice * @Description 炒饭类 继承快餐类 * @Date 2021/3/23 21:54 * @Created by dell */ public class FiredRice extends FastFood { public FiredRice() { super(10, "炒饭"); } @Override public float cost() { return getPrice(); } } ``` **FiredNoodles 具体构建角色** ```java package com.zhuang.decorator; /** * @Classname FiredNoodles * @Description 炒面类 继承 快餐类 * @Date 2021/3/23 21:54 * @Created by dell */ public class FiredNoodles extends FastFood { public FiredNoodles() { super(15, "炒面"); } @Override public float cost() { return getPrice(); } } ``` **Garnish 抽象装饰角色** ```java package com.zhuang.decorator; /** * @Classname Garnish * @Description 抽象配料类 继承快餐类 * @Date 2021/3/23 21:58 * @Created by dell */ public abstract class Garnish extends FastFood{ private FastFood fastFood; public FastFood getFastFood() { return fastFood; } public void setFastFood(FastFood fastFood) { this.fastFood = fastFood; } public Garnish(FastFood fastFood,float price,String desc){ super(price,desc); this.fastFood = fastFood; } } ``` **Egg 具体装饰角色** ```java package com.zhuang.decorator; /** * @Classname Egg * @Description 鸡蛋配料类 继承配料类 * @Date 2021/3/23 21:55 * @Created by dell */ public class Egg extends Garnish { public Egg(FastFood fastFood) { //鸡蛋1元 super(fastFood, 1, "鸡蛋"); } @Override public float cost() { return getPrice() + getFastFood().getPrice(); } @Override public String getDesc() { return super.getDesc() + getFastFood().getDesc(); } } ``` **Bacon 具体装饰角色** ```java package com.zhuang.decorator; /** * @Classname Bacon * @Description 培根类 继承配料类 * @Date 2021/3/23 22:03 * @Created by dell */ public class Bacon extends Garnish { public Bacon(FastFood fastFood) { //培根2元 super(fastFood, 2, "培根"); } @Override public float cost() { return getPrice() + getFastFood().getPrice(); } @Override public String getDesc() { return super.getDesc() + getFastFood().getDesc(); } } ``` **Client** ```java package com.zhuang.decorator; /** * @Classname Client * @Description 装饰器模式测试类 * @Date 2021/3/23 21:53 * @Created by dell */ public class Client { public static void main(String[] args) { //点一份炒饭 FastFood rice = new FiredRice(); //价格 System.out.println(rice.getDesc() + "-->" + rice.cost() + "元"); System.out.println("============================="); //点一份加鸡蛋的炒饭 FastFood eggRice = new FiredRice(); //加鸡蛋 eggRice = new Egg(eggRice); System.out.println(eggRice.getDesc() + "-->" + eggRice.cost() + "元"); System.out.println("============================="); //点一份加培根的炒面 FastFood baconNoodles = new FiredNoodles(); //加培根 baconNoodles = new Bacon(baconNoodles); System.out.println(baconNoodles.getDesc() + "-->" + baconNoodles.cost() + "元"); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-09-51.png) ## 12.3 装饰器模式的应用场景 - 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。 不能采用继承的情况主要有两类: - 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长; - 第二类是因为类定义不能继承(如final类) - 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。 - 当对象的功能要求可以动态地添加,也可以再动态地撤销时。 ## 12.4 JDK源码解析 IO流中的包装类使用到了装饰者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。 我们以BufferedWriter举例来说明,先看看如何使用BufferedWriter ```java public class Demo { public static void main(String[] args) throws Exception{ //创建BufferedWriter对象 //创建FileWriter对象 FileWriter fw = new FileWriter("C:\\Users\\dell\\Desktop\\a.txt"); BufferedWriter bw = new BufferedWriter(fw); //写数据 bw.write("hello Buffered"); bw.close(); } } ``` 结构 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_20-59-18.png) **BufferedWriter使用装饰者模式对Writer子实现类进行了增强,添加了缓冲区,提高了写数据的效率。** 静态代理和装饰者模式的区别: - 相同点: - 都要实现与目标类相同的业务接口 - 在两个类中都要声明目标对象 - 都可以在不修改目标类的前提下增强目标方法 - 不同点: - 目的不同 装饰者是为了增强目标对象 静态代理是为了保护和隐藏目标对象 - 获取目标对象构建的地方不同 装饰者是由外界传递进来,可以通过构造方法传递 静态代理是在代理类内部创建,以此来隐藏目标对象 # 13,外观模式 ## 13.1 外观模式的定义和特点 外观(Facade)模式又叫作门面模式,**是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。**该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。 外观(Facade)模式是“迪米特法则”的典型应用,**它有以下主要优点。** 1. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。 2. 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。 3. 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。 **外观(Facade)模式的主要缺点如下。** 1. 不能很好地限制客户使用子系统类,很容易带来未知风险。 2. 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。 ## 13.2 外观模式的结构与实现 ### 13.2.1 外观模式的结构 外观(Facade)模式包含以下主要角色。 1. 外观(Facade)角色:为多个子系统对外提供一个共同的接口。 2. 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。 3. 客户(Client)角色:通过一个外观角色访问各个子系统的功能。 ### 13.2.2 代码实现 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_21-03-54.png) **AirCondition** ```java package com.zhuang.facade; /** * @Classname AirCondition * @Description 空调类 * @Date 2021/3/24 19:23 * @Created by dell */ public class AirCondition { public void on() { System.out.println("空调打开..."); } public void off() { System.out.println("空调关闭..."); } } ``` **Light** ```java package com.zhuang.facade; /** * @Classname Light * @Description 电灯类 * @Date 2021/3/24 19:23 * @Created by dell */ public class Light { public void on() { System.out.println("电灯打开..."); } public void off() { System.out.println("电灯关闭..."); } } ``` **TV** ```java package com.zhuang.facade; /** * @Classname TV * @Description 电视类 * @Date 2021/3/24 19:23 * @Created by dell */ public class TV { public void on() { System.out.println("电视打开..."); } public void off() { System.out.println("电视关闭..."); } } ``` **SmartAppliancesFacade** ```java package com.zhuang.facade; /** * @Classname SmartAppliancesFacade * @Description 智能音箱类 外观类 * @Date 2021/3/24 19:24 * @Created by dell */ public class SmartAppliancesFacade { private Light light; private TV tv; private AirCondition airCondition; public SmartAppliancesFacade() { light = new Light(); tv = new TV(); airCondition = new AirCondition(); } //私有打开方法 外界访问不了 //一键打开 private void on() { light.on(); tv.on(); airCondition.on(); } //私有关闭方法 外界访问不了 //一键关闭 private void off() { light.off(); tv.off(); airCondition.off(); } //判断方法 public void say(String message) { if (message.contains("打开")) { on(); } else if (message.contains("关闭")) { off(); } else { System.out.println("你说的指令我听不懂!!!"); } } } ``` **Client** ```java package com.zhuang.facade; /** * @Classname Client * @Description 外观模式测试类 * @Date 2021/3/24 19:24 * @Created by dell */ public class Client { public static void main(String[] args) { SmartAppliancesFacade smartAppliancesFacade = new SmartAppliancesFacade(); smartAppliancesFacade.say("打开家电"); System.out.println("================="); smartAppliancesFacade.say("关闭家电"); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-10-37.png) ## 13.3 外观模式的应用场景 - 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。 - 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。 - 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。 ## 13.4 源码解析 使用tomcat作为web容器时,接收浏览器发送过来的请求,tomcat会将请求信息封装成ServletRequest对象,如下图①处对象。但是大家想想ServletRequest是一个接口,它还有一个子接口HttpServletRequest,而我们知道该request对象肯定是一个HttpServletRequest对象的子实现类对象,到底是哪个类的对象呢?可以通过输出request对象,我们就会发现是一个名为RequestFacade的类的对象。 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_21-09-43.png) RequestFacade类就使用了外观模式 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_21-10-14.png) **为什么在此处使用外观模式呢?** 定义 RequestFacade 类,分别实现 ServletRequest ,同时定义私有成员变量 Request ,并且方法的实现调用 Request 的实现。然后,将 RequestFacade上转为 ServletRequest 传给 servlet 的 service 方法,这样即使在 servlet 中被下转为 RequestFacade ,也不能访问私有成员变量对象中的方法。既用了 Request ,又能防止其中方法被不合理的访问。 # 14,享元模式 ## 14.1 享元模式的定义和特点 享元(Flyweight)模式的定义:**运用共享技术来有效地支持大量细粒度对象的复用。**它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。 享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。 其主要缺点是: 1. 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。 2. 读取享元模式的外部状态会使得运行时间稍微变长。 ## 14.2 享元模式的结构与实现 ### 14.2.1 享元模式的结构 享元(Flyweight )模式中存在以下两种状态: 1. 内部状态,即不会随着环境的改变而改变的可共享部分。 2. 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。 享元模式的主要有以下角色: - 抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。 - 具体享元(Concrete Flyweight)角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。 - 非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。 - 享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。 ### 14.2.1代码实现 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_21-14-51.png) **IBox 定义不同的形状** ```java package com.zhuang.flyweight; /** * @Classname IBox * @Description I图形类(具体享元角色) * @Date 2021/3/25 15:33 * @Created by dell */ public class IBox extends AbstractBox { @Override public String getShape() { return "I"; } } ``` **LBox 定义不同的形状** ```java package com.zhuang.flyweight; /** * @Classname IBox * @Description L图形类(具体享元角色) * @Date 2021/3/25 15:33 * @Created by dell */ public class LBox extends AbstractBox { @Override public String getShape() { return "L"; } } ``` **OBox 定义不同的形状** ```java package com.zhuang.flyweight; /** * @Classname IBox * @Description O图形类(具体享元角色) * @Date 2021/3/25 15:33 * @Created by dell */ public class OBox extends AbstractBox { @Override public String getShape() { return "O"; } } ``` **BoxFactory 提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法** ```java package com.zhuang.flyweight; import java.util.HashMap; /** * @Classname BoxFactory * @Description 工厂类 将类设计为单例模式 * @Date 2021/3/25 15:33 * @Created by dell */ public class BoxFactory { private HashMap map; //在构造方法中初始化 private BoxFactory() { map = new HashMap(); IBox iBox = new IBox(); LBox lBox = new LBox(); OBox oBox = new OBox(); map.put("I", iBox); map.put("L", lBox); map.put("O", oBox); } //声明一个方法获取工厂 public static BoxFactory getInstance() { return SingletonHolder.INSTANCE; } private static class SingletonHolder { private static final BoxFactory INSTANCE = new BoxFactory(); } //根据图形名称获取图形对象 public AbstractBox getShape(String name) { return map.get(name); } } ``` **AbstractBox 对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为** ```java package com.zhuang.flyweight; /** * @Classname AbstractBox * @Description 抽象享元角色 抽象类 * @Date 2021/3/25 15:32 * @Created by dell */ public abstract class AbstractBox { //获取图形的方法 public abstract String getShape(); //显示图形及颜色 public void display(String color) { System.out.println("方块形状:" + this.getShape() + "颜色:" + color); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-11-07.png) ## 14.3 享元模式的应用场景 当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。 享元模式其实是工厂方法模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能。 前面分析了享元模式的结构与特点,下面分析它适用的应用场景。**享元模式是通过减少内存中对象的数量来节省内存空间的**,所以以下几种情形适合采用享元模式。 1. 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。 2. 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。 3. 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式 ## 14.4 JDK源码解析 Integer类使用了享元模式。我们先看下面的例子: ```java public class Demo { public static void main(String[] args) { Integer i1 = 127; Integer i2 = 127; System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2)); Integer i3 = 128; Integer i4 = 128; System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4)); } } ``` > 结果是 true false 为什么第一个输出语句输出的是true,第二个输出语句输出的是false?通过反编译软件进行反编译,代码如下: ```java public class Demo { public static void main(String[] args) { Integer i1 = Integer.valueOf((int)127); Integer i2 Integer.valueOf((int)127); System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString()); Integer i3 = Integer.valueOf((int)128); Integer i4 = Integer.valueOf((int)128); System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString()); } } ``` 上面代码可以看到,直接给Integer类型的变量赋值基本数据类型数据的操作底层使用的是 `valueOf()` ,所以只需要看该方法即可 ```java public final class Integer extends Number implements Comparable { public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} } } ``` 可以看到 `Integer` 默认先创建并缓存 `-128 ~ 127` 之间数的 `Integer` 对象,当调用 `valueOf` 时如果参数在 `-128 ~ 127` 之间则计算下标并从缓存中返回,否则创建一个新的 `Integer` 对象。 # 15,模板方法模式 ## 15.1 模板方法模式的定义和特点 模板方法(Template Method)模式的定义如下:**定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。**它是一种类行为型模式。 **该模式的主要优点如下。** 1. 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。 2. 它在父类中提取了公共的部分代码,便于代码复用。 3. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。 **该模式的主要缺点如下。** 1. 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。 2. 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。 3. 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。 ## 15.2 模板方法模式的结构与实现 ### 15.2.1 模板方法模式的结构 模板方法(Template Method)模式包含以下主要角色: - 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。 - 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。 - 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种: - 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。 - 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。 - 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。 一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。 - 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_21-25-01.png) ### 15.2.2 代码实现 **AbstractClass** ```java package com.zhuang.template; /** * @Classname AbstractClass * @Description 抽象类 * @Date 2021/3/26 20:06 * @Created by dell */ public abstract class AbstractClass { public final void work() { //起床 this.wake(); //刷牙 this.brush(); //吃早饭 this.breakfast(); //交通工具 this.transport(); //睡觉 this.sleep(); } //步骤一样 直接实现 public void wake() { System.out.println("起床..."); } //步骤一样 直接实现 public void brush() { System.out.println("刷牙..."); } // 步骤不一样 (一个是吃面包 一个是喝牛奶) public abstract void breakfast(); // 步骤不一样 (一个是开车 一个是坐地铁) public abstract void transport(); // 步骤一样 直接实现 public void sleep() { System.out.println("睡觉..."); } } ``` **ConcreteClass_BreakFast** ```java package com.zhuang.template; /** * @Classname ConcreteClass_BreakFast * @Description 具体类 早饭类 继承 * @Date 2021/3/26 20:13 * @Created by dell */ public class ConcreteClass_BreakFast extends AbstractClass { @Override public void breakfast() { System.out.println("吃面包..."); } @Override public void transport() { System.out.println("坐公交..."); } } ``` **ConcreteClass_Transport** ```java package com.zhuang.template; /** * @Classname ConcreteClass_Transport * @Description 交通工具类 继承 * @Date 2021/3/26 20:14 * @Created by dell */ public class ConcreteClass_Transport extends AbstractClass { @Override public void breakfast() { System.out.println("喝牛奶..."); } @Override public void transport() { System.out.println("乘地铁..."); } } ``` **Client** ```java package com.zhuang.template; /** * @Classname Client * @Description 模板方法模式 测试类 * @Date 2021/3/26 20:16 * @Created by dell */ public class Client { public static void main(String[] args) { //吃面包 坐公交 System.out.println("周一"); AbstractClass breakFast = new ConcreteClass_BreakFast(); breakFast.work(); System.out.println("========================"); System.out.println("周五"); AbstractClass transport = new ConcreteClass_Transport(); transport.work(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-11-39.png) **钩子方法** **AbstractClass** ```java package com.zhuang.template.hook_method; /** * @Classname AbstractClass * @Description 抽象类 * @Date 2021/3/26 20:06 * @Created by dell */ public abstract class AbstractClass { public final void work() { //起床 this.wake(); //刷牙 this.brush(); //吃早饭 this.breakfast(); //交通工具 if (isSunday()) { this.transport(); } //睡觉 this.sleep(); } //步骤一样 直接实现 public void wake() { System.out.println("起床..."); } //步骤一样 直接实现 public void brush() { System.out.println("刷牙..."); } // 步骤不一样 (一个是吃面包 一个是喝牛奶) public abstract void breakfast(); // 步骤不一样 (一个是开车 一个是坐地铁) public abstract void transport(); // 步骤一样 直接实现 public void sleep() { System.out.println("睡觉..."); } //钩子方法 是否为周末 周末不用交通工具 boolean isSunday() { return false; } } ``` **ConcreteClass_BreakFast** ```java package com.zhuang.template.hook_method; /** * @Classname ConcreteClass_BreakFast * @Description 具体类 早饭类 继承 * @Date 2021/3/26 20:13 * @Created by dell */ public class ConcreteClass_BreakFast extends AbstractClass { @Override public void breakfast() { System.out.println("吃面包..."); } @Override public void transport() { System.out.println("坐公交..."); } } ``` **ConcreteClass_Transport** ```java package com.zhuang.template.hook_method; /** * @Classname ConcreteClass_Transport * @Description 交通工具类 继承 * @Date 2021/3/26 20:14 * @Created by dell */ public class ConcreteClass_Transport extends AbstractClass { @Override public void breakfast() { System.out.println("喝牛奶..."); } @Override public void transport() { System.out.println("乘地铁..."); } } ``` **ConcreteClass_Sunday** ```java package com.zhuang.template.hook_method; /** * @Classname ConcreteClass_Sunday * @Description 周末 不用上班 空实现交通方法 * @Date 2021/3/26 20:28 * @Created by dell */ public class ConcreteClass_Sunday extends AbstractClass{ @Override public void breakfast() { System.out.println("吃面包或者喝牛奶..."); } @Override public void transport() { //空实现 } @Override boolean isSunday() { System.out.println("今天周末,休息..."); return true; } } ``` **Client** ```java package com.zhuang.template.hook_method; /** * @Classname Client * @Description 模板方法 测试钩子方法 * @Date 2021/3/26 20:26 * @Created by dell */ public class Client { public static void main(String[] args) { AbstractClass sunday = new ConcreteClass_Sunday(); sunday.work(); } } ``` ## 15.3 模板方法模式的应用场景 1. 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。 2. 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。 3. 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。 ## 15.4 JDK源码解析 InputStream类就使用了模板方法模式。在InputStream类中定义了多个 `read()` 方法,如下: ```java public abstract class InputStream implements Closeable { //抽象方法,要求子类必须重写 public abstract int read() throws IOException; public int read(byte b[]) throws IOException { return read(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = read(); //调用了无参的read方法,该方法是每次读取一个字节数据 if (c == -1) { return -1; } b[off] = (byte)c; int i = 1; try { for (; i < len ; i++) { c = read(); if (c == -1) { break; } b[off + i] = (byte)c; } } catch (IOException ee) { } return i; } } ``` 从上面代码可以看到,无参的 `read()` 方法是抽象方法,要求子类必须实现。而 `read(byte b[])` 方法调用了 `read(byte b[], int off, int len)` 方法,所以在此处重点看的方法是带三个参数的方法。 在该方法中第18行、27行,可以看到调用了无参的抽象的 `read()` 方法。 **总结如下: 在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据呢?由子类实现**。 # 16,组合模式 ## 16.1 组合模式的定义和特点 组合(Composite Pattern)模式的定义:有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式。 组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,树形结构图如下 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_22-22-40.png) 由上图可以看出,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。 这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。 组合模式的主要优点有: 1. 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码; 2. 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”; 其主要缺点是: 1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系; 2. 不容易限制容器中的构件; 3. 不容易用继承的方法来增加构件的新功能; ## 16.2 组合模式的结构与实现 ### 16.2.1 组合模式的结构 1. 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除) 2. 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。 3. 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。 ### 16.2.2 代码实现 如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称。 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_22-23-54.png) ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_22-24-18.png) **MenuComponent MenuComponent定义为抽象类,因为有一些共有的属性和行为要在该类中实现,Menu和MenuItem类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法,举例来说,Menu类可以包含子菜单,因此需要覆盖add()、remove()、getChild()方法,但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常,你也可以根据自己的需要改写默认实现。** ```java package com.zhuang.combination; /** * @Classname MenuComponent * @Description 菜单组件 不管菜单还是菜单项,都应该继承该类 抽象类 * @Date 2021/3/24 17:02 * @Created by dell */ public abstract class MenuComponent { protected String name; protected int level; //添加菜单 public void add(MenuComponent menuComponent) { throw new UnsupportedOperationException(); } //移除菜单 public void remove(MenuComponent menuComponent) { throw new UnsupportedOperationException(); } //获取指定的子菜单 public MenuComponent getChild(int i) { throw new UnsupportedOperationException(); } //获取菜单名称 public String getName() { return name; } //打印方法 public void print() { throw new UnsupportedOperationException(); } } ``` **Menu类已经实现了除了getName方法的其他所有方法,因为Menu类具有添加菜单,移除菜单和获取子菜单的功能** ```java package com.zhuang.combination; import java.util.ArrayList; import java.util.List; /** * @Classname Menu * @Description 菜单类 继承菜单组件 * @Date 2021/3/24 17:05 * @Created by dell */ public class Menu extends MenuComponent { private List menuComponentList; public Menu(String name, int level) { this.name = name; this.level = level; menuComponentList = new ArrayList(); } @Override public void add(MenuComponent menuComponent) { menuComponentList.add(menuComponent); } @Override public void remove(MenuComponent menuComponent) { menuComponentList.remove(menuComponent); } @Override public MenuComponent getChild(int i) { return menuComponentList.get(i); } @Override public void print() { for (int i = 0; i < level; i++) { System.out.print("--"); } System.out.println(name); for (MenuComponent menuComponent : menuComponentList) { menuComponent.print(); } } } ``` **MenuItem是菜单项,不能再有子菜单,所以添加菜单,移除菜单和获取子菜单的功能并不能实现。** ```java package com.zhuang.combination; /** * @Classname MenuItem * @Description 菜单选项 继承菜单组件 * @Date 2021/3/24 17:10 * @Created by dell */ public class MenuItem extends MenuComponent { public MenuItem(String name, int level) { this.name = name; this.level = level; } @Override public void print() { for (int i = 0; i < level; i++) { System.out.print("--"); } System.out.println(name); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-16-43.png) 在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式。 - 透明组合模式 透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 `MenuComponent` 声明了 `add`、`remove` 、`getChild` 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。 透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码) - 安全组合模式 在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 `Menu` 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_22-27-51.png) ## 16.3 组合模式的应用场景 1. 在需要表示一个对象整体与部分的层次结构的场合。 2. 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。 # 17,代理模式 ## 17.1 代理模式的定义和特点 代理模式的定义:**由于某些原因需要给某对象提供一个代理以控制对该对象的访问。**这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。 **代理模式的主要优点有:** - 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用; - 代理对象可以扩展目标对象的功能; - 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性 **其主要缺点是:** - 代理模式会造成系统设计中类的数量增加 - 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢; - 增加了系统的复杂度; ## 17.2 代理模式的结构与实现 ### 17.2.1 代理模式的结构 1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。 2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。 3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。 根据代理的创建时期,代理模式分为静态代理和动态代理。 - 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。 - 动态:在程序运行时,运用反射机制动态创建而成 ### 17.2.2 代码实现 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_22-35-52.png) #### 17.2.2.1**静态代理** **SellTickets** ```java package com.zhuang.proxy.static_proxy; /** * @Classname SellTickets * @Description 卖票接口 * @Date 2021/3/26 8:01 * @Created by dell */ public interface SellTickets { void sell(); } ``` **Transition** ```java package com.zhuang.proxy.static_proxy; /** * @Classname Transition * @Description 火车站,具有卖票功能,实现接口 * @Date 2021/3/26 8:01 * @Created by dell */ public class Transition implements SellTickets { @Override public void sell() { System.out.println("火车站卖票"); } } ``` **ProxyPoint** ```java package com.zhuang.proxy.static_proxy; /** * @Classname ProxyPoint * @Description 代售点 实现接口 * @Date 2021/3/26 8:02 * @Created by dell */ public class ProxyPoint implements SellTickets { private Transition transition = new Transition(); @Override public void sell() { System.out.println("代售点收取服务费"); transition.sell(); } } ``` **Client** ```java package com.zhuang.proxy.static_proxy; /** * @Classname Client * @Description 静态代理客户端测试类 * @Date 2021/3/26 8:02 * @Created by dell */ public class Client { public static void main(String[] args) { ProxyPoint proxyPoint = new ProxyPoint(); proxyPoint.sell(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-18-39.png) 从上面代码中可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强 #### 17.2.2.2 **JDK动态代理** 使用动态代理实现上面案例,先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。 **SellTickets** ```java package com.zhuang.proxy.jdk_proxy; /** * @Classname SellTickets * @Description 卖票接口 * @Date 2021/3/26 8:01 * @Created by dell */ public interface SellTickets { void sell(); } ``` **Transition** ```java package com.zhuang.proxy.jdk_proxy; /** * @Classname Transition * @Description 火车站,具有卖票功能,实现接口 * @Date 2021/3/26 8:01 * @Created by dell */ public class Transition implements SellTickets { @Override public void sell() { System.out.println("火车站卖票"); } } ``` **ProxyFactory** ```java package com.zhuang.proxy.jdk_proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @Classname ProxyFactory * @Description 代理工厂 用来创建代理对象 * @Date 2021/3/26 8:11 * @Created by dell */ public class ProxyFactory { private Transition transition = new Transition(); public SellTickets getProxyObject() { //使用Proxy获取代理对象 /* newProxyInstance ClassLoader loader, 类加载器 Class[] interfaces, 接口 InvocationHandler h 方法 invoke 方法参数说明 proxy 代理对象 method 对应于在代理对象调用的接口方法的Method实例 args 代理对象调用接口方法时传递的实际参数 */ SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(transition.getClass().getClassLoader(), transition.getClass().getInterfaces() , new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代售点收取服务费(JDK动态代理方式)"); //执行真实对象 Object result = method.invoke(transition, args); return result; } }); return sellTickets; } } ``` **Client** ```java package com.zhuang.proxy.jdk_proxy; /** * @Classname Client * @Description JDK动态代理 测试类 * @Date 2021/3/26 8:20 * @Created by dell */ public class Client { public static void main(String[] args) { //获取代理对象 ProxyFactory factory = new ProxyFactory(); SellTickets proxyObject = factory.getProxyObject(); proxyObject.sell(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-20-12.png) 使用了动态代理,我们思考下面问题: * ProxyFactory是代理类吗? ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构: ```java package com.sun.proxy; import com.itheima.proxy.dynamic.jdk.SellTickets; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements SellTickets { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy0(InvocationHandler invocationHandler) { super(invocationHandler); } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException noSuchMethodException) { throw new NoSuchMethodError(noSuchMethodException.getMessage()); } catch (ClassNotFoundException classNotFoundException) { throw new NoClassDefFoundError(classNotFoundException.getMessage()); } } public final boolean equals(Object object) { try { return (Boolean)this.h.invoke(this, m1, new Object[]{object}); } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)this.h.invoke(this, m2, null); } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode() { try { return (Integer)this.h.invoke(this, m0, null); } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void sell() { try { this.h.invoke(this, m3, null); return; } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } } ``` 从上面的类中,我们可以看到以下几个信息: * 代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。 * 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。 * 动态代理的执行流程是什么样? 下面是摘取的重点代码: ```java //程序运行过程中动态生成的代理类 public final class $Proxy0 extends Proxy implements SellTickets { private static Method m3; public $Proxy0(InvocationHandler invocationHandler) { super(invocationHandler); } static { m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]); } public final void sell() { this.h.invoke(this, m3, null); } } //Java提供的动态代理相关类 public class Proxy implements java.io.Serializable { protected InvocationHandler h; protected Proxy(InvocationHandler h) { this.h = h; } } //代理工厂类 public class ProxyFactory { private TrainStation station = new TrainStation(); public SellTickets getProxyObject() { SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理点收取一些服务费用(JDK动态代理方式)"); Object result = method.invoke(station, args); return result; } }); return sellTickets; } } //测试访问类 public class Client { public static void main(String[] args) { //获取代理对象 ProxyFactory factory = new ProxyFactory(); SellTickets proxyObject = factory.getProxyObject(); proxyObject.sell(); } } ``` 执行流程如下: 1. 在测试类中通过代理对象调用sell()方法 2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法 3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法 4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法 #### 17.2.2.3 **CGLB代理** **TrainStation** ```java package com.zhuang.proxy.cglib_proxy; /** * @Classname TrainStation * @Description 火车站类 * @Date 2021/3/26 8:31 * @Created by dell */ public class TrainStation { public void sell() { System.out.println("火车站卖票"); } } ``` **ProxyFactory** ```java package com.zhuang.proxy.cglib_proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @Classname ProxyFactory * @Description 代理工厂 实现MethodInterceptor * @Date 2021/3/26 8:32 * @Created by dell */ public class ProxyFactory implements MethodInterceptor { private TrainStation target = new TrainStation(); public TrainStation getProxyObject() { //创建Enhancer对象 类似于JDK动态代理 Enhancer enhancer = new Enhancer(); //设置父类的字节码对象 enhancer.setSuperclass(target.getClass()); //设置回调函数 enhancer.setCallback(this); //创建代理对象 TrainStation obj = (TrainStation) enhancer.create(); return obj; } @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("代收点收取一些代理费用(CGLIB动态代理方式)"); Object result = methodProxy.invokeSuper(o, args); return result; } } ``` **Client** ```java package com.zhuang.proxy.cglib_proxy; /** * @Classname Client * @Description CGLIB动态代理模式 测试类 * @Date 2021/3/26 8:42 * @Created by dell */ public class Client { public static void main(String[] args) { //创建代理工厂对象 ProxyFactory factory = new ProxyFactory(); //获取代理对象 TrainStation proxyObject = factory.getProxyObject(); proxyObject.sell(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-20-23.png) ### 17.2.3 三种代理的对比 - jdk代理和CGLIB代理 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。 在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。 - 动态代理和静态代理 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题 ## 17.3 代理模式应用场景 - 远程(Remote)代理 本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。 - 防火墙(Firewall)代理 当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。 - 保护(Protect or Access)代理 控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。 # 18,命令模式 ## 18.1 命令模式的定义和特点 命令(Command)模式的定义如下:**将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。**这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。 **命令模式的主要优点如下。** 1. 通过引入中间件(抽象接口)降低系统的耦合度。 2. 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。 3. 可以实现宏命令。命令模式可以与**组合模式**结合,将多个命令装配成一个组合命令,即宏命令。 4. 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。 5. 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。 其缺点是: 1. 可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。 2. 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。 ## 18.2 命令模式的结构与实现 ### 18.2.1 命令模式的结构 1. 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。 2. 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。 3. 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。 4. 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。 ### 18.2.2 代码实现 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_22-49-15.png) **Command** ```java package com.zhuang.command; /** * @Classname Command * @Description 抽象命令类 * @Date 2021/3/27 10:25 * @Created by dell */ public interface Command { void execute(); // 只需要定义一个统一的执行方法 } ``` **OrderCommand** ```java package com.zhuang.command; /** * @Classname OrderCommand * @Description 具体命令类 * @Date 2021/3/27 10:25 * @Created by dell */ public class OrderCommand implements Command{ //持有接受者对象 private SeniorChef receiver; private Order order; public OrderCommand(SeniorChef receiver, Order order) { this.receiver = receiver; this.order = order; } @Override public void execute() { System.out.println(order.getDiningTable()+"桌的订单:"); for (String key : order.getFoodDic().keySet()) { receiver.makefood(order.getFoodDic().get(key),key); } try { Thread.sleep(1000); //模拟做饭 睡眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(order.getDiningTable()+"桌的饭弄好了"); } } ``` **Order** ```java package com.zhuang.command; import java.util.HashMap; import java.util.Map; /** * @Classname Order * @Description 订单类 * @Date 2021/3/27 10:34 * @Created by dell */ public class Order { // 餐桌号码 private int diningTable; //用来存储餐名并记录 private Map foodDic = new HashMap(); public int getDiningTable() { return diningTable; } public void setDiningTable(int diningTable) { this.diningTable = diningTable; } public Map getFoodDic() { return foodDic; } public void setFoodDic(String name, int num) { foodDic.put(name, num); } } ``` **SeniorChef** ```java package com.zhuang.command; /** * @Classname SeniorChef * @Description 厨师类 * @Date 2021/3/27 10:27 * @Created by dell */ public class SeniorChef { //大厨师类 是命令的Receiver public void makefood(int num, String foodName) { System.out.println(num + "份" + foodName); } } ``` **Waitor** ```java package com.zhuang.command; import java.util.ArrayList; /** * @Classname Waitor * @Description 服务员类 * @Date 2021/3/27 10:30 * @Created by dell */ public class Waitor { //可以持有很多命令对象 private ArrayList commands; public Waitor() { commands = new ArrayList(); } public void setCommands(Command cmd) { commands.add(cmd); } //发出命令 订单来了 大厨师开始执行命令 public void orderUp() { System.out.println("来活了..."); for (int i = 0; i < commands.size(); i++) { Command cmd = commands.get(i); if (cmd != null) { cmd.execute(); } } } } ``` **Client** ```java package com.zhuang.command; /** * @Classname Client * @Description 命令模式 测试类 * @Date 2021/3/27 10:44 * @Created by dell */ public class Client { public static void main(String[] args) { //创建order Order order1 = new Order(); order1.setDiningTable(1); order1.getFoodDic().put("西红柿炒鸡蛋", 1); order1.getFoodDic().put("罐装可乐", 2); Order order2 = new Order(); order2.setDiningTable(2); order2.getFoodDic().put("酸溜土豆丝", 1); order2.getFoodDic().put("王老吉", 1); //创建接受者 SeniorChef receiver = new SeniorChef(); //将订单和接受者封装成命令对象 OrderCommand cmd1 = new OrderCommand(receiver, order1); OrderCommand cmd2 = new OrderCommand(receiver, order2); //创建调用者 waitor Waitor invoke = new Waitor(); invoke.setCommands(cmd1); invoke.setCommands(cmd2); //将订单给柜台 呼叫厨师 invoke.orderUp(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-20-52.png) ## 18.3 命令模式应用场景 - 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。 - 系统需要在不同的时间指定请求、将请求排队和执行请求。 - 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。 ## 18.4 JDK源码解析 Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法 ```java //命令接口(抽象命令角色) public interface Runnable { public abstract void run(); } //调用者 public class Thread implements Runnable { private Runnable target; public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } private native void start0(); } ``` 会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。 ```java /** * jdk Runnable 命令模式 * TurnOffThread : 属于具体 */ public class TurnOffThread implements Runnable{ private Receiver receiver; public TurnOffThread(Receiver receiver) { this.receiver = receiver; } public void run() { receiver.turnOFF(); } } ``` ```java /** * 测试类 */ public class Demo { public static void main(String[] args) { Receiver receiver = new Receiver(); TurnOffThread turnOffThread = new TurnOffThread(receiver); Thread thread = new Thread(turnOffThread); thread.start(); } } ``` # 19,访问者模式 ## 19.1 访问者模式的定义和特点 访问者(Visitor)模式的定义:**将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。**它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。 **访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。** 1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。 2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。 3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。 4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。 **访问者(Visitor)模式的主要缺点如下。** 1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。 2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。 3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。 ## 19.2 访问者模式的结构与实现 ### 19.2.1 访问者模式的结构 1. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。 2. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。 3. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。 4. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。 5. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。 ### 19.2.2 代码实现 现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。 - 访问者角色:给宠物喂食的人 - 具体访问者角色:主人、其他人 - 抽象元素角色:动物抽象类 - 具体元素角色:宠物狗、宠物猫 - 结构对象角色:主人家 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_23-00-33.png) **Person** ```java package com.zhuang.visitor; /** * @Classname Person * @Description 抽象访问者接口 * @Date 2021/3/27 16:48 * @Created by dell */ public interface Person { //喂宠物狗 void feed(Dog dog); //喂宠物猫 void feed(Cat cat); } ``` **Owner** ```java package com.zhuang.visitor; /** * @Classname Owner * @Description 具体访问者角色 主人类 * @Date 2021/3/27 16:49 * @Created by dell */ public class Owner implements Person { @Override public void feed(Dog dog) { System.out.println("主人喂食宠物狗..."); } @Override public void feed(Cat cat) { System.out.println("主人喂食宠物猫..."); } } ``` **Someone** ```java package com.zhuang.visitor; /** * @Classname Someone * @Description 具体访问者角色 其他人类 * @Date 2021/3/27 16:49 * @Created by dell */ public class Someone implements Person { @Override public void feed(Dog dog) { System.out.println("其他人喂食宠物狗..."); } @Override public void feed(Cat cat) { System.out.println("其他人喂食宠物猫..."); } } ``` **Animal** ```java package com.zhuang.visitor; /** * @Classname Animal * @Description 定义抽象节点 * @Date 2021/3/27 16:50 * @Created by dell */ public interface Animal { void accept(Person person); } ``` **Dog** ```java package com.zhuang.visitor; /** * @Classname Dog * @Description 具体节点 实现Animal接口 * @Date 2021/3/27 16:48 * @Created by dell */ public class Dog implements Animal { @Override public void accept(Person person) { person.feed(this); System.out.println("真香~,汪汪汪!!!"); } } ``` **Cat** ```java package com.zhuang.visitor; /** * @Classname Cat * @Description 用一句话描述类的作用 * @Date 2021/3/27 16:49 * @Created by dell */ public class Cat implements Animal { @Override public void accept(Person person) { person.feed(this); System.out.println("真香~,喵喵喵!!!"); } } ``` **Home** ```java package com.zhuang.visitor; import java.util.ArrayList; import java.util.List; /** * @Classname Home * @Description 定义对象结构 主人的家 * @Date 2021/3/27 16:50 * @Created by dell */ public class Home { private List nodeList = new ArrayList(); //添加方法 public void add(Animal animal) { nodeList.add(animal); } public void aciton(Person person) { for (Animal node : nodeList) { node.accept(person); } } } ``` **Client** ```java package com.zhuang.visitor; /** * @Classname Client * @Description 访问者模式 测试类 * @Date 2021/3/27 16:50 * @Created by dell */ public class Client { public static void main(String[] args) { Home home = new Home(); home.add(new Dog()); home.add(new Cat()); Owner owner = new Owner(); home.aciton(owner); System.out.println("==========================="); Someone someone = new Someone(); home.aciton(someone); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-23-25.png) ## 19.3 扩展 访问者模式用到了一种双分派的技术。 **1,分派:** 变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 `Map map = new HashMap()` ,map变量的静态类型是 `Map` ,实际类型是 `HashMap` 。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。 **静态分派(Static Dispatch)** 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。 **动态分派(Dynamic Dispatch)** 发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。 **2,动态分派:** 通过方法的重写支持动态分派。 ```java public class Animal { public void execute() { System.out.println("Animal"); } } public class Dog extends Animal { @Override public void execute() { System.out.println("我是狗..."); } } public class Cat extends Animal { @Override public void execute() { System.out.println("我是猫..."); } } public class Client { public static void main(String[] args) { Animal a = new Dog(); a.execute(); Animal a1 = new Cat(); a1.execute(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-27-14.png) 上面代码的结果大家应该直接可以说出来,这不就是多态吗!运行执行的是子类中的方法。 Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。 **3,静态分派:** 通过方法重载支持静态分派。 ```java public class Animal { } public class Dog extends Animal { } public class Cat extends Animal { } public class Execute { public void execute(Animal a) { System.out.println("Animal"); } public void execute(Dog d) { System.out.println("我是狗..."); } public void execute(Cat c) { System.out.println("我是猫..."); } } public class Client { public static void main(String[] args) { Animal a = new Animal(); Animal a1 = new Dog(); Animal a2 = new Cat(); Execute exe = new Execute(); exe.execute(a); exe.execute(a1); exe.execute(a2); } } ``` 运行结果: ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-29-35.png) 这个结果可能出乎一些人的意料了,为什么呢? **重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。** **4,双分派:** 所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。 ```java public class Animal { public void accept(Execute exe) { exe.execute(this); } } public class Dog extends Animal { public void accept(Execute exe) { exe.execute(this); } } public class Cat extends Animal { public void accept(Execute exe) { exe.execute(this); } } public class Execute { public void execute(Animal a) { System.out.println("animal"); } public void execute(Dog d) { System.out.println("我是狗..."); } public void execute(Cat c) { System.out.println("我是猫..."); } } public class Client { public static void main(String[] args) { Animal a = new Animal(); Animal d = new Dog(); Animal c = new Cat(); Execute exe = new Execute(); a.accept(exe); d.accept(exe); c.accept(exe); } } ``` 在上面代码中,客户端将Execute对象做为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也`将自己this作为参数传递进去,这里就完成了第二次分派`,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。 说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改。 运行结果如下: ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-29-35.png) **双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。** # 20,迭代器模式 ## 20.1 迭代器模式的定义和特点 迭代器(Iterator)模式的定义:**提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。**迭代器模式是一种对象行为型模式,**其主要优点如下**。 1. 访问一个聚合对象的内容而无须暴露它的内部表示。 2. 遍历任务交由迭代器完成,这简化了聚合类。 3. 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。 4. 增加新的聚合类和迭代器类都很方便,无须修改原有代码。 5. 封装性良好,为遍历不同的聚合结构提供一个统一的接口。 **其主要缺点是:** - 增加了类的个数,这在一定程度上增加了系统的复杂性。 ## 20.2 迭代器模式的结构与实现 ### 20.2.1 迭代器模式的结构 1. 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。 2. 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。 3. 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。 4. 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。 ### 20.2.2 代码实现 定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_23-08-48.png) **Student** ```java package com.zhuang.Iterator; /** * @Classname Student * @Description 学生类 * @Date 2021/3/28 12:47 * @Created by dell */ public class Student { private String name; private String number; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } public Student() { } public Student(String name, String number) { this.name = name; this.number = number; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", number='" + number + '\'' + '}'; } } ``` **StudentIterator** ```java package com.zhuang.Iterator; /** * @Classname StudentIterator * @Description 迭代器接口 * @Date 2021/3/28 12:43 * @Created by dell */ public interface StudentIterator { boolean hasNext(); Student next(); } ``` **StudentIteratorImpl** ```java package com.zhuang.Iterator; import java.util.List; /** * @Classname StudentIteratorImpl * @Description 具体的迭代器类 * @Date 2021/3/28 12:43 * @Created by dell */ public class StudentIteratorImpl implements StudentIterator { private List list; private int position = 0; public StudentIteratorImpl(List list) { this.list = list; } @Override public boolean hasNext() { return position < list.size(); } @Override public Student next() { Student currentStudent = list.get(position); position++; return currentStudent; } } ``` **StudentAggregate** ```java package com.zhuang.Iterator; /** * @Classname StudentAggregate * @Description 抽象容器类 * @Date 2021/3/28 12:44 * @Created by dell */ public interface StudentAggregate { //添加学生的功能 void addStudent(Student student); //删除学生的功能 void removeStudent(Student student); //获取迭代器功能的对象 StudentIterator getStudentIterator(); } ``` **StudentAggregateImpl** ```java package com.zhuang.Iterator; import java.util.ArrayList; import java.util.List; /** * @Classname StudentAggregateImpl * @Description 具体的容器类 * @Date 2021/3/28 12:44 * @Created by dell */ public class StudentAggregateImpl implements StudentAggregate { private List list = new ArrayList(); @Override public void addStudent(Student student) { this.list.add(student); } @Override public void removeStudent(Student student) { this.list.remove(student); } @Override public StudentIterator getStudentIterator() { return new StudentIteratorImpl(list); } } ``` **Client** ```java package com.zhuang.Iterator; /** * @Classname Client * @Description 迭代器模式 测试类 * @Date 2021/3/28 12:54 * @Created by dell */ public class Client { public static void main(String[] args) { StudentAggregateImpl aggregate = new StudentAggregateImpl(); //添加元素 aggregate.addStudent(new Student("张三", "001")); aggregate.addStudent(new Student("李四", "002")); aggregate.addStudent(new Student("王五", "003")); aggregate.addStudent(new Student("赵六", "004")); aggregate.addStudent(new Student("田七", "005")); //遍历聚合对象 StudentIterator iterator = aggregate.getStudentIterator(); //遍历 while (iterator.hasNext()) { Student student = iterator.next(); System.out.println(student); } } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-29-54.png) ## 20.3 迭代器模式的应用场景 - 当需要为聚合对象提供多种遍历方式时。 - 当需要为遍历不同的聚合结构提供一个统一的接口时。 - 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。 ## 20.4 JDK源码解析 迭代器模式在JAVA的很多集合类中被广泛应用,接下来看看JAVA源码中是如何使用迭代器模式的。 ```java List list = new ArrayList<>(); Iterator iterator = list.iterator(); //list.iterator()方法返回的肯定是Iterator接口的子实现类对象 while (iterator.hasNext()) { System.out.println(iterator.next()); } ``` 看完这段代码是不是很熟悉,与我们上面代码基本类似。单列集合都使用到了迭代器,我们以ArrayList举例来说明 - List:抽象聚合类 - ArrayList:具体的聚合类 - Iterator:抽象迭代器 - list.iterator():返回的是实现了 `Iterator` 接口的具体迭代器对象 具体的来看看 ArrayList的代码实现 ```java public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable { public Iterator iterator() { return new Itr(); } private class Itr implements Iterator { int cursor; // 下一个要返回元素的索引 int lastRet = -1; // 上一个返回元素的索引 int expectedModCount = modCount; Itr() {} //判断是否还有元素 public boolean hasNext() { return cursor != size; } //获取下一个元素 public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } ... } ``` 这部分代码还是比较简单,大致就是在 `iterator` 方法中返回了一个实例化的 `Iterator` 对象。Itr是一个内部类,它实现了 `Iterator` 接口并重写了其中的抽象方法。 > 注意: > > 当我们在使用JAVA开发的时候,想使用迭代器模式的话,只要让我们自己定义的容器类实现`java.util.Iterable`并实现其中的iterator()方法使其返回一个 `java.util.Iterator` 的实现类就可以了。 # 21,观察者模式 ## 21.1 观察者模式的定义和特点 观察者(Observer)模式的定义:**指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。**这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。 观察者模式是一种对象行为型模式,**其主要优点如下**。 1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。 2. 目标与观察者之间建立了一套触发机制。 **它的主要缺点如下。** 1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。 2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。 ## 21.2 观察者模式的结构与实现 实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则 ### 21.2.1 观察者模式的结构 1. 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。 2. 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。 3. 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。 4. 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。 ### 21.2.1 代码实现 在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_23-16-00.png) **Observer** ```java package com.zhuang.observer; /** * @Classname Observer * @Description 抽象观察者 * @Date 2021/3/28 14:14 * @Created by dell */ public interface Observer { //更新的方法 void update(String messages); } ``` **WexinUser** ```java package com.zhuang.observer; /** * @Classname WexinUser * @Description 具体观察者类 实现更新的方法 * @Date 2021/3/28 14:14 * @Created by dell */ public class WexinUser implements Observer { //用户名 private String name; public WexinUser(String name) { this.name = name; } public WexinUser() { } @Override public void update(String messages) { System.out.println(name + "-->" + messages); } } ``` **Subject** ```java package com.zhuang.observer; /** * @Classname Subject * @Description 抽象主题类 * @Date 2021/3/28 14:15 * @Created by dell */ public interface Subject { //增加订阅者 public void attach(Observer observer); //删除订阅者 public void remove(Observer observer); //通知订阅者更新消息 public void notify(String messages); } ``` **SubscriptionSubject** ```java package com.zhuang.observer; import java.util.ArrayList; import java.util.List; /** * @Classname SubscriptionSubject * @Description 具体主题(具体被观察者) * @Date 2021/3/28 14:15 * @Created by dell */ public class SubscriptionSubject implements Subject { //存储订阅公众号的微信用户 private List weixinUserList = new ArrayList(); @Override public void attach(Observer observer) { weixinUserList.add(observer); } @Override public void remove(Observer observer) { weixinUserList.remove(observer); } @Override public void notify(String messages) { for (Observer observer : weixinUserList) { observer.update(messages); } } } ``` **Client** ```java package com.zhuang.observer; /** * @Classname Client * @Description 观察者模式 测试类 * @Date 2021/3/28 14:16 * @Created by dell */ public class Client { public static void main(String[] args) { SubscriptionSubject subject = new SubscriptionSubject(); //创建微信用户 WexinUser user1 = new WexinUser("张三"); WexinUser user2 = new WexinUser("李四"); WexinUser user3 = new WexinUser("王五"); //订阅公众号 subject.attach(user1); subject.attach(user2); subject.attach(user3); //通过订阅用户 subject.notify("您关注的公众号更新啦~~~"); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-30-09.png) ## 20.3 观察者模式应用场景 1. 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。 2. 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。 3. 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。 4. 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。 ## 20.4 JDK源码解析 在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。 **1,Observable类** Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。 * void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。 * void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。 * void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。 **2,Observer 接口** Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。 【例】警察抓小偷 警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。代码如下: 小偷是一个被观察者,所以需要继承Observable类 ```java package com.zhuang.observer.observable_example; import java.util.Observable; /** * @Classname Thief * @Description 小偷类 继承Observable接口 * @Date 2021/3/28 14:36 * @Created by dell */ public class Thief extends Observable { private String name; public Thief(String name) { this.name = name; } public Thief() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public void steal() { System.out.println("小偷:我偷东西,有没有人来抓我!!!"); super.setChanged();//默认为true super.notifyObservers(); } } ``` 警察是一个观察者,所以需要让其实现Observer接口 ```java package com.zhuang.observer.observable_example; import java.util.Observable; import java.util.Observer; /** * @Classname Policemen * @Description 警察类 实现Observe类 实现update方法 * @Date 2021/3/28 14:36 * @Created by dell */ public class Policemen implements Observer { private String name; public Policemen(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Policemen() { } @Override public void update(Observable o, Object arg) { System.out.println("警察:" + ((Thief) o).getName() + "你被我抓到了哈哈哈哈!!!"); } } ``` 客户端代码 ```java package com.zhuang.observer.observable_example; /** * @Classname Client * @Description 测试类 * @Date 2021/3/28 14:36 * @Created by dell */ public class Client { public static void main(String[] args) { //小偷对象 Thief thief = new Thief("法外狂徒张三"); //警察对象 Policemen policemen = new Policemen("小庄警察"); //警察盯着小偷 thief.addObserver(policemen); //小偷行窃 thief.steal(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-30-19.png) # 22,中介者模式 一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下左图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。 如果引入中介者模式,那么同事类之间的关系将变为星型结构,从下右图中可以看到,任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。 ![](C:\Users\dell\Desktop\Notes\设计模式\img\Snipaste_2021-03-30_23-23-04.png) ## 22.1 中介者模式的定义和特点 中介者(Mediator)模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。 中介者模式是一种对象行为型模式,其主要优点如下。 1. 类之间各司其职,符合迪米特法则。 2. 降低了对象之间的耦合性,使得对象易于独立地被复用。 3. 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。 其主要缺点是:中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。 ## 22.2 中介者模式的结构与实现 ### 22.2.1 中介者模式的结构 - 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。 - 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。 - 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。 - 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。 ### 22.2.2 代码实现 现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_23-24-04.png) **Mediator** ```java package com.zhuang.mediator; /** * @Classname Mediator * @Description 抽象中介者 * @Date 2021/3/28 20:45 * @Created by dell */ public abstract class Mediator { //声明一个联络方法 public abstract void constact(String message, Person person); } ``` **Person** ```java package com.zhuang.mediator; /** * @Classname Person * @Description 抽象同事类 * @Date 2021/3/28 20:45 * @Created by dell */ public abstract class Person { protected String name; protected Mediator mediator; public Person(String name, Mediator mediator) { this.name = name; this.mediator = mediator; } } ``` **HouseOwner** ```java package com.zhuang.mediator; /** * @Classname HouseOwner * @Description 具体同事类 房屋拥有者 * @Date 2021/3/28 20:45 * @Created by dell */ public class HouseOwner extends Person{ public HouseOwner(String name, Mediator mediator) { super(name, mediator); } //与中介者联系 public void constact(String message){ mediator.constact(message,this); } //获取信息 public void getMessage(String message){ System.out.println("房主"+name+"获取的信息:"+message); } } ``` **Tenant** ```java package com.zhuang.mediator; /** * @Classname Tenant * @Description 具体同事类 承租人 * @Date 2021/3/28 20:46 * @Created by dell */ public class Tenant extends Person{ public Tenant(String name, Mediator mediator) { super(name, mediator); } //与中介者联系 public void constact(String message){ mediator.constact(message,this); } //获取信息 //获取信息 public void getMessage(String message){ System.out.println("租房者"+name+"获取的信息:"+message); } } ``` **MediatorStructure** ```java package com.zhuang.mediator; /** * @Classname MediatorStructure * @Description 中介机构 * @Date 2021/3/28 20:46 * @Created by dell */ public class MediatorStructure extends Mediator { //中介结构必须知道所有房主和租房者的信息 private HouseOwner houseOwner; private Tenant tenant; public HouseOwner getHouseOwner() { return houseOwner; } public void setHouseOwner(HouseOwner houseOwner) { this.houseOwner = houseOwner; } public Tenant getTenant() { return tenant; } public void setTenant(Tenant tenant) { this.tenant = tenant; } @Override public void constact(String message, Person person) { if (person == houseOwner) { //如果是房主,则租房者获得信息 tenant.getMessage(message); } else { //反之是房主获得信息 houseOwner.getMessage(message); } } } ``` **Client** ```java package com.zhuang.mediator; /** * @Classname Client * @Description 中介者模式 测试类 * @Date 2021/3/28 20:47 * @Created by dell */ public class Client { public static void main(String[] args) { //房主 租房者 中介机构 MediatorStructure mediator = new MediatorStructure(); //房主和租房者只需要知道中介机构即可 HouseOwner houseOwner = new HouseOwner("张三", mediator); Tenant tenant = new Tenant("李四", mediator); //中介机构需要知道房主和租房者 mediator.setHouseOwner(houseOwner); mediator.setTenant(tenant); tenant.constact("需要租一间房子"); houseOwner.constact("我有一间房子,你要租吗???"); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-30-43.png) ## 22.3 中介者模式应用场景 - 当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。 - 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。 ## 22.4 中介者模式的注意事项和细节 - 多个类相互耦合,会形成网状结构,使用中介者模式将网状结构分离为星型结构,进行解耦 - 减少类间依赖,降低了耦合,符合迪米特法则 - 中介者承担较多责任,出问题,系统会受到影响 - 设计不当,实际使用,特别注意 # 23,备忘录模式 ## 23.1 中介者模式的定义和特点 备忘录(Memento)模式的定义:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。**该模式又叫快照模式。 备忘录模式是一种对象行为型模式,**其主要优点如下。** - 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。 - 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。 - 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。 **其主要缺点是:** - 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。 ## 23.2 备忘录模式的结构与实现 ### 23.2.1 备忘录模式的结构 1. 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。 2. 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。 3. 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。 4. 备忘录有两个等效的接口: - **窄接口**:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。 - **宽接口**:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。 ### 23.2.2 代码实现 游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。 要实现上述案例,有两种方式: - “白箱”备忘录模式 - “黑箱”备忘录模式 #### 23.2.2.1**“白箱”备忘录模式** 备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_23-34-20.png) **GameRole** ```java package com.zhuang.memento.white_box; /** * @Classname GameRole * @Description 游戏角色类 * @Date 2021/3/29 10:14 * @Created by dell */ public class GameRole { private int vit;//生命力 private int atk;//攻击力 private int def;//防御力 //初始化状态 public void initState() { this.vit = 100; this.atk = 100; this.def = 100; } //战斗 public void fight() { this.vit = 0; this.atk = 0; this.def = 0; } //保存角色状态 public RoleStateMemento saveState() { return new RoleStateMemento(vit, atk, def); } //恢复角色状态 public void recoverState(RoleStateMemento roleStateMemento) { this.vit = roleStateMemento.getVit(); this.atk = roleStateMemento.getAtk(); this.def = roleStateMemento.getDef(); } //展示状态 public void stateDisplay() { System.out.println("角色生命力" + vit); System.out.println("角色攻击力" + atk); System.out.println("角色防御力" + def); } public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getAtk() { return atk; } public void setAtk(int atk) { this.atk = atk; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } } ``` **RoleStateMemento** ```java package com.zhuang.memento.white_box; /** * @Classname RoleStateMemento * @Description 游戏状态存储类 备忘录类 * @Date 2021/3/29 10:14 * @Created by dell */ public class RoleStateMemento { private int vit; private int atk; private int def; public RoleStateMemento(int vit, int atk, int def) { this.vit = vit; this.atk = atk; this.def = def; } public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getAtk() { return atk; } public void setAtk(int atk) { this.atk = atk; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } } ``` **RoleStateCaretaker** ```java package com.zhuang.memento.white_box; /** * @Classname RoleStateCaretaker * @Description 角色状态管理类 * @Date 2021/3/29 10:15 * @Created by dell */ public class RoleStateCaretaker { private RoleStateMemento roleStateMemento; public RoleStateMemento getRoleStateMemento() { return roleStateMemento; } public void setRoleStateMemento(RoleStateMemento roleStateMemento) { this.roleStateMemento = roleStateMemento; } } ``` **Client** ```java package com.zhuang.memento.white_box; /** * @Classname Client * @Description 备忘录模式 白箱 测试类 * @Date 2021/3/29 10:15 * @Created by dell */ public class Client { public static void main(String[] args) { System.out.println("--------------大战Boss前------------------------"); //大战Boss前 GameRole gameRole = new GameRole(); gameRole.initState(); gameRole.stateDisplay(); //保存进度 RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker(); roleStateCaretaker.setRoleStateMemento(gameRole.saveState()); System.out.println("--------------大战Boss后------------------------"); //大战Boss 损耗严重 gameRole.fight(); gameRole.stateDisplay(); System.out.println("--------------满血复活------------------------"); gameRole.recoverState(roleStateCaretaker.getRoleStateMemento()); gameRole.stateDisplay(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-32-42.png) #### 23.2.2.2**黑箱备忘录模式** 备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将**备忘录类**设计成**发起人类**的内部成员类。 将 `RoleStateMemento` 设为 `GameRole` 的内部类,从而将 `RoleStateMemento` 对象封装在 `GameRole` 里面;在外面提供一个标识接口 `Memento` 给 `RoleStateCaretaker` 及其他对象使用。这样 `GameRole` 类看到的是 `RoleStateMemento` 所有的接口,而`RoleStateCaretaker` 及其他对象看到的仅仅是标识接口 `Memento` 所暴露出来的接口,从而维护了封装型。类图如下: ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-30_23-39-26.png) **GameRole** ```java package com.zhuang.memento.black_box; import com.zhuang.memento.white_box.RoleStateMemento; /** * @Classname GameRole * @Description 游戏角色类 * @Date 2021/3/29 10:14 * @Created by dell */ public class GameRole { private int vit;//生命力 private int atk;//攻击力 private int def;//防御力 //初始化状态 public void initState() { this.vit = 100; this.atk = 100; this.def = 100; } //战斗 public void fight() { this.vit = 0; this.atk = 0; this.def = 0; } //保存角色状态 public Memento saveState() { return new RoleStateMemento(vit, atk, def); } //恢复角色状态 public void recoverState(Memento memento) { RoleStateMemento roleStateMemento = (RoleStateMemento) memento; this.vit = roleStateMemento.getVit(); this.atk = roleStateMemento.getAtk(); this.def = roleStateMemento.getDef(); } //展示状态 public void stateDisplay() { System.out.println("角色生命力" + vit); System.out.println("角色攻击力" + atk); System.out.println("角色防御力" + def); } public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getAtk() { return atk; } public void setAtk(int atk) { this.atk = atk; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } //在内部定义备忘录内部类 RoleStateMemento(该内部类设置为私有的) private class RoleStateMemento implements Memento { private int vit; private int atk; private int def; public RoleStateMemento(int vit, int atk, int def) { this.vit = vit; this.atk = atk; this.def = def; } public int getVit() { return vit; } public void setVit(int vit) { this.vit = vit; } public int getAtk() { return atk; } public void setAtk(int atk) { this.atk = atk; } public int getDef() { return def; } public void setDef(int def) { this.def = def; } } } ``` **Memento** ```java package com.zhuang.memento.black_box; /** * @Classname Memento * @Description 窄接口 * @Date 2021/3/29 10:40 * @Created by dell */ public interface Memento { } ``` **RoleStateCaretaker** ```java package com.zhuang.memento.black_box; /** * @Classname RoleStateCaretaker * @Description 角色状态管理类 * @Date 2021/3/29 10:15 * @Created by dell */ public class RoleStateCaretaker { private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } } ``` **Client** ```java package com.zhuang.memento.black_box; /** * @Classname Client * @Description 备忘录模式 黑箱 测试类 * @Date 2021/3/29 10:15 * @Created by dell */ public class Client { public static void main(String[] args) { System.out.println("--------------大战Boss前------------------------"); //大战Boss前 GameRole gameRole = new GameRole(); gameRole.initState(); gameRole.stateDisplay(); //保存进度 RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker(); roleStateCaretaker.setMemento(gameRole.saveState()); System.out.println("--------------大战Boss后------------------------"); //大战Boss 损耗严重 gameRole.fight(); gameRole.stateDisplay(); System.out.println("--------------满血复活------------------------"); gameRole.recoverState(roleStateCaretaker.getMemento()); gameRole.stateDisplay(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_08-32-54.png) ## 23.3 备忘录模式应用场景 - 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。 - 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,idea等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。 ## 23.4 备忘录模式的注意事项和细节 - 给用户提供一种可以恢复的机制,可以使用户能够比较方便回到某个历史的状态 - 实现了信息的封装,使用户不需要关心保存细节 - 类成员变量过多,占用比较大的资源,每一次保存都会消耗一定内存 # 24,解释器模式 设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。 ```java //用于两个整数相加 public static int add(int a,int b){ return a + b; } //用于两个整数相加 public static int add(int a,int b,int c){ return a + b + c; } //用于n个整数相加 public static int add(Integer ... arr) { int sum = 0; for (Integer i : arr) { sum += i; } return sum; } ``` 上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如 1+2+3+4+5、1+2+3-4等等。 显然,现在需要一种翻译识别机器,能够解析由数字以及 + - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。 ## 24.1 解释器模式的定义和特点 解释器(Interpreter)模式的定义:**给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。**也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。 这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_12-22-17.png) **解释器模式是一种类行为型模式,其主要优点如下。** 1. 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。 2. 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。 **解释器模式的主要缺点如下。** 1. 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。 2. 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。 3. 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。 ## 24.2 解释器模式的结构与实现 ### 24.2.1 解释器模式的结构 - 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。 - 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。 - 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。 - 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。 - 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。 ### 24.2.2 代码实现 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_12-22-58.png) **AbstractExpression** ```java package com.zhuang.interpreter; /** * @Classname AbstractExpression * @Description 抽象角色 * @Date 2021/3/31 9:45 * @Created by dell */ public abstract class AbstractExpression { public abstract int interpret(Context context); } ``` **Value** ```java package com.zhuang.interpreter; /** * @Classname Value * @Description 终结符表达式角色 * @Date 2021/3/31 9:48 * @Created by dell */ public class Value extends AbstractExpression { private int value; public Value(int value) { this.value = value; } @Override public int interpret(Context context) { return value; } @Override public String toString() { return new Integer(value).toString(); } } ``` **Plus** ```java package com.zhuang.interpreter; /** * @Classname Plus * @Description 非终结符表达式角色 加法表达式 * @Date 2021/3/31 9:46 * @Created by dell */ public class Plus extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public Plus(AbstractExpression left, AbstractExpression right) { this.left = left; this.right = right; } @Override public int interpret(Context context) { return left.interpret(context) + right.interpret(context); } @Override public String toString() { return "(" + left.toString() + " + " + right.toString() + ")"; } } ``` **Minus** ```java package com.zhuang.interpreter; /** * @Classname Minus * @Description 非终结符表达式角色 减法表达式 * @Date 2021/3/31 9:46 * @Created by dell */ public class Minus extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public Minus(AbstractExpression left, AbstractExpression right) { this.left = left; this.right = right; } @Override public int interpret(Context context) { return left.interpret(context) - right.interpret(context); } @Override public String toString() { return "(" + left.toString() + " - " + right.toString() + ")"; } } ``` **Variable** ```java package com.zhuang.interpreter; /** * @Classname Variable * @Description 终结符表达式角色 变量表达式 * @Date 2021/3/31 9:46 * @Created by dell */ public class Variable extends AbstractExpression { private String name; public Variable(String name) { this.name = name; } @Override public int interpret(Context context) { return context.getValue(this); } @Override public String toString() { return name; } } ``` **Context** ```java package com.zhuang.interpreter; import java.util.HashMap; import java.util.Map; /** * @Classname Context * @Description 环境类 * @Date 2021/3/31 9:46 * @Created by dell */ public class Context { private Map map = new HashMap(); public void assign(Variable var, Integer value) { map.put(var, value); } public int getValue(Variable var) { Integer value = map.get(var); return value; } } ``` **Client** ```java package com.zhuang.interpreter; /** * @Classname Client * @Description 解释权模式 测试类 * @Date 2021/3/31 9:46 * @Created by dell */ public class Client { public static void main(String[] args) { Context context = new Context(); Variable a = new Variable("a"); Variable b = new Variable("b"); Variable c = new Variable("c"); Variable d = new Variable("d"); Variable e = new Variable("e"); context.assign(a, 2); context.assign(b, 3); context.assign(c, 4); context.assign(d, 5); context.assign(e, 6); AbstractExpression expression = new Minus(new Plus(new Plus(new Plus(a, b), c), d), e); System.out.println(expression + "=" + expression.interpret(context)); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_09-59-41.png) ## 24.3 解释器模式使用场景 - 当语言的文法较为简单,且执行效率不是关键问题时。 - 当问题重复出现,且可以用一种简单的语言来进行表达时。 - 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。 # 25,状态模式 ## 25.1 状态模式的定义和特点 状态(State)模式的定义:**对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。** **状态模式是一种对象行为型模式,其主要优点如下。** 1. 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。 2. 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。 3. 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。 **状态模式的主要缺点如下。** 1. 状态模式的使用必然会增加系统的类与对象的个数。 2. 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。 3. 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。 ## 25.2 状态模式的结构与实现 【例】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作 ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_12-31-24.png) ```java public interface ILift { //电梯的4个状态 //开门状态 public final static int OPENING_STATE = 1; //关门状态 public final static int CLOSING_STATE = 2; //运行状态 public final static int RUNNING_STATE = 3; //停止状态 public final static int STOPPING_STATE = 4; //设置电梯的状态 public void setState(int state); //电梯的动作 public void open(); public void close(); public void run(); public void stop(); } public class Lift implements ILift { private int state; @Override public void setState(int state) { this.state = state; } //执行关门动作 @Override public void close() { switch (this.state) { case OPENING_STATE: System.out.println("电梯关门了。。。");//只有开门状态可以关闭电梯门,可以对应电梯状态表来看 this.setState(CLOSING_STATE);//关门之后电梯就是关闭状态了 break; case CLOSING_STATE: //do nothing //已经是关门状态,不能关门 break; case RUNNING_STATE: //do nothing //运行时电梯门是关着的,不能关门 break; case STOPPING_STATE: //do nothing //停止时电梯也是关着的,不能关门 break; } } //执行开门动作 @Override public void open() { switch (this.state) { case OPENING_STATE://门已经开了,不能再开门了 //do nothing break; case CLOSING_STATE://关门状态,门打开: System.out.println("电梯门打开了。。。"); this.setState(OPENING_STATE); break; case RUNNING_STATE: //do nothing 运行时电梯不能开门 break; case STOPPING_STATE: System.out.println("电梯门开了。。。");//电梯停了,可以开门了 this.setState(OPENING_STATE); break; } } //执行运行动作 @Override public void run() { switch (this.state) { case OPENING_STATE://电梯不能开着门就走 //do nothing break; case CLOSING_STATE://门关了,可以运行了 System.out.println("电梯开始运行了。。。"); this.setState(RUNNING_STATE);//现在是运行状态 break; case RUNNING_STATE: //do nothing 已经是运行状态了 break; case STOPPING_STATE: System.out.println("电梯开始运行了。。。"); this.setState(RUNNING_STATE); break; } } //执行停止动作 @Override public void stop() { switch (this.state) { case OPENING_STATE: //开门的电梯已经是是停止的了(正常情况下) //do nothing break; case CLOSING_STATE://关门时才可以停止 System.out.println("电梯停止了。。。"); this.setState(STOPPING_STATE); break; case RUNNING_STATE://运行时当然可以停止了 System.out.println("电梯停止了。。。"); this.setState(STOPPING_STATE); break; case STOPPING_STATE: //do nothing break; } } } public class Client { public static void main(String[] args) { Lift lift = new Lift(); lift.setState(ILift.STOPPING_STATE);//电梯是停止的 lift.open();//开门 lift.close();//关门 lift.run();//运行 lift.stop();//停止 } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_10-46-54.png) **问题分析** - 使用了大量的switch…case这样的判断(if…else也是一样),使程序的可阅读性变差。 - 扩展性很差。如果新加了断电的状态,我们需要修改上面判断逻辑 ### 25.2.1 状态模式的结构 - 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。 - 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。 - 具体状态(Concrete State)角色:实现抽象状态所对应的行为。 ### 25.2.2 代码实现 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_12-33-29.png) **LiftState** ```java package com.zhuang.state.after; /** * @Classname LiftState * @Description 抽象状态类 * @Date 2021/3/31 10:50 * @Created by dell */ public abstract class LiftState { //定义一个环境角色,也就是封装状态的变化引起的功能变化 protected Context context; public void setContext(Context context) { this.context = context; } //电梯开门动作 public abstract void open(); //电梯关门动作 public abstract void close(); //电梯运行动作 public abstract void run(); //电梯停止动作 public abstract void stop(); } ``` **Context** ```java package com.zhuang.state.after; /** * @Classname Context * @Description 定义所有电梯门状态 * @Date 2021/3/31 10:53 * @Created by dell */ public class Context { //定义出所有的电梯状态 //开门状态,这时候电梯只能关闭 public final static OpeningState OPENNING_STATE = new OpeningState(); //关闭状态,这时候电梯可以运行、停止和开门 public final static ClosingState CLOSEING_STATE = new ClosingState(); //运行状态,这时候电梯只能停止 public final static RunningState RUNNING_STATE = new RunningState(); //停止状态,这时候电梯可以开门、运行 public final static StoppingState STOPPING_STATE = new StoppingState(); //定义一个当前电梯状态 private LiftState liftState; public LiftState getLiftState() { return this.liftState; } public void setLiftState(LiftState liftState) { //当前环境改变 this.liftState = liftState; //把当前的环境通知到各个实现类中 this.liftState.setContext(this); } public void open() { this.liftState.open(); } public void close() { this.liftState.close(); } public void run() { this.liftState.run(); } public void stop() { this.liftState.stop(); } } ``` **OpeningState** ```java package com.zhuang.state.after; /** * @Classname OpeningState * @Description 开启状态 * @Date 2021/3/31 10:51 * @Created by dell */ public class OpeningState extends LiftState { //开启当然可以关闭了,我就想测试一下电梯门开关功能 @Override public void open() { System.out.println("电梯门开启..."); } @Override public void close() { //状态修改 super.context.setLiftState(Context.CLOSEING_STATE); //动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作 super.context.getLiftState().close(); } //电梯门不能开着就跑,这里什么也不做 @Override public void run() { //do nothing } //开门状态已经是停止的了 @Override public void stop() { //do nothing } } ``` **ClosingState** ```java package com.zhuang.state.after; /** * @Classname ClosingState * @Description 关闭状态 * @Date 2021/3/31 10:52 * @Created by dell */ public class ClosingState extends LiftState { @Override //电梯门关闭,这是关闭状态要实现的动作 public void close() { System.out.println("电梯门关闭..."); } //电梯门关了再打开,逗你玩呢,那这个允许呀 @Override public void open() { super.context.setLiftState(Context.OPENNING_STATE); super.context.open(); } //电梯门关了就跑,这是再正常不过了 @Override public void run() { super.context.setLiftState(Context.RUNNING_STATE); super.context.run(); } //电梯门关着,我就不按楼层 @Override public void stop() { super.context.setLiftState(Context.STOPPING_STATE); super.context.stop(); } } ``` **RunningState** ```java package com.zhuang.state.after; /** * @Classname RunningState * @Description 运行状态 * @Date 2021/3/31 10:52 * @Created by dell */ public class RunningState extends LiftState { @Override public void open() { //什么也不做 } @Override public void close() { //什么也不做 } @Override public void run() { System.out.println("电梯正在运行..."); } @Override public void stop() { //停止 super.context.setLiftState(Context.OPENNING_STATE); super.context.stop(); } } ``` **StoppingState** ```java package com.zhuang.state.after; /** * @Classname StoppingState * @Description 停止状态 * @Date 2021/3/31 10:51 * @Created by dell */ public class StoppingState extends LiftState { @Override public void open() { //状态修改 super.context.setLiftState(Context.OPENNING_STATE); //动作委托给CloseState来执行 也就是委托给了ClosingState子类执行动作 super.context.getLiftState().open(); } @Override public void close() { //状态修改 super.context.setLiftState(Context.CLOSEING_STATE); //动作委托给CloseState来执行 也就是委托给了ClosingState子类执行动作 super.context.getLiftState().close(); } @Override public void run() { //状态修改 super.context.setLiftState(Context.RUNNING_STATE); //动作委托给CloseState来执行 也就是委托给了ClosingState子类执行动作 super.context.getLiftState().run(); } @Override public void stop() { System.out.println("电梯停止了..."); } } ``` **Client** ```java package com.zhuang.state.after; /** * @Classname Client * @Description 状态模式 测试类 * @Date 2021/3/31 10:53 * @Created by dell */ public class Client { public static void main(String[] args) { //开门状态 System.out.println("开门状态-->"); Context context1 = new Context(); context1.setLiftState(new OpeningState()); context1.open(); context1.close(); context1.run(); context1.stop(); System.out.println("========================="); //关门状态 System.out.println("关门状态-->"); Context context2 = new Context(); context2.setLiftState(new ClosingState()); context2.open(); context2.close(); context2.run(); context2.stop(); System.out.println("========================="); //运行状态 System.out.println("运行状态-->"); Context context3 = new Context(); context3.setLiftState(new RunningState()); context3.open(); context3.close(); context3.run(); context3.stop(); System.out.println("========================="); //停止状态 System.out.println("停止状态-->"); Context context4 = new Context(); context4.setLiftState(new StoppingState()); context4.open(); context4.close(); context4.run(); context4.stop(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_12-17-58.png) ## 25.3 状态模式应用场景 - 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。 - 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。 # 26,策略模式 ## 26.1 策略模式的定义和特点 策略(Strategy)模式的定义:**该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。**策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。 **策略模式的主要优点如下。** 1. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if...else 语句、switch...case 语句。 2. 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。 3. 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。 4. 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。 5. 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。 **其主要缺点如下。** 1. 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。 2. 策略模式造成很多的策略类,增加维护难度。 ## 26.2 策略模式的结构与实现 ### 26.2 .1 策略模式的结构 1. 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。 2. 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。 3. 环境(Context)类:持有一个策略类的引用,最终给客户端调用。 ### 26.2.2 代码实现 针对不同节日不同的促销活动 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_17-39-00.png) **Strategy** ```java package com.zhuang.strategy; /** * @Classname Strategy * @Description 定义共同接口 * @Date 2021/3/31 15:29 * @Created by dell */ public interface Strategy { void show(); } ``` **StrategyA** ```java package com.zhuang.strategy; /** * @Classname StrategyA * @Description 定义具体策略角色 每个节日的具体促销活动 * @Date 2021/3/31 15:29 * @Created by dell */ public class StrategyA implements Strategy { @Override public void show() { System.out.println("A促销 买一送一"); } } ``` **StrategyB** ```java package com.zhuang.strategy; /** * @Classname StrategyB * @Description 定义具体策略角色 每个节日的具体促销活动 * @Date 2021/3/31 15:30 * @Created by dell */ public class StrategyB implements Strategy { @Override public void show() { System.out.println("B促销 满100减20"); } } ``` **StrategyC** ```java package com.zhuang.strategy; /** * @Classname StrategyC * @Description 定义具体策略角色 每个节日的具体促销活动 * @Date 2021/3/31 15:30 * @Created by dell */ public class StrategyC implements Strategy { @Override public void show() { System.out.println("C促销 满500元可兑换小礼品"); } } ``` **SalesMan** ```java package com.zhuang.strategy; /** * @Classname SalesMan * @Description 定义环境角色 用于连接上下文 把促销活动推销给顾客 * @Date 2021/3/31 15:32 * @Created by dell */ public class SalesMan { //持有抽象策略角色的引用 private Strategy strategy; public SalesMan(Strategy strategy) { this.strategy = strategy; } public Strategy getStrategy() { return strategy; } public void setStrategy(Strategy strategy) { this.strategy = strategy; } //展示促销活动 public void salesManShow() { strategy.show(); } } ``` **Client** ```java package com.zhuang.strategy; /** * @Classname Client * @Description 策略模式 测试类 * @Date 2021/3/31 15:34 * @Created by dell */ public class Client { public static void main(String[] args) { SalesMan salesMan = new SalesMan(new StrategyA()); //儿童节 salesMan.salesManShow(); System.out.println("======================="); //劳动节 salesMan.setStrategy(new StrategyB()); salesMan.salesManShow(); System.out.println("======================="); //端午节 salesMan.setStrategy(new StrategyC()); salesMan.salesManShow(); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_15-37-23.png) ## 26.3 策略模式应用场景 - 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。 - 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。 - 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。 - 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。 - 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。 ## 26.4 JDK源码解析 `Comparator` 中的策略模式。在Arrays类中有一个 `sort()` 方法,如下: ```java public class Arrays{ public static void sort(T[] a, Comparator c) { if (c == null) { sort(a); } else { if (LegacyMergeSort.userRequested) legacyMergeSort(a, c); else TimSort.sort(a, 0, a.length, c, null, 0, 0); } } } ``` Arrays就是一个环境角色类,这个sort方法可以传一个新策略让Arrays根据这个策略来进行排序。就比如下面的测试类。 ```java public class demo { public static void main(String[] args) { Integer[] data = {12, 2, 3, 2, 4, 5, 1}; // 实现降序排序 Arrays.sort(data, new Comparator() { public int compare(Integer o1, Integer o2) { return o2 - o1; } }); System.out.println(Arrays.toString(data)); //[12, 5, 4, 3, 2, 2, 1] } } ``` 这里我们在调用Arrays的sort方法时,第二个参数传递的是Comparator接口的子实现类对象。所以Comparator充当的是抽象策略角色,而具体的子实现类充当的是具体策略角色。环境角色类(Arrays)应该持有抽象策略的引用来调用。那么,Arrays类的sort方法到底有没有使用Comparator子实现类中的 `compare()` 方法吗?让我们继续查看TimSort类的 `sort()` 方法,代码如下: ```java class TimSort { static void sort(T[] a, int lo, int hi, Comparator c, T[] work, int workBase, int workLen) { assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length; int nRemaining = hi - lo; if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted // If array is small, do a "mini-TimSort" with no merges if (nRemaining < MIN_MERGE) { int initRunLen = countRunAndMakeAscending(a, lo, hi, c); binarySort(a, lo, hi, lo + initRunLen, c); return; } ... } private static int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator c) { assert lo < hi; int runHi = lo + 1; if (runHi == hi) return 1; // Find end of run, and reverse range if descending if (c.compare(a[runHi++], a[lo]) < 0) { // Descending while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0) runHi++; reverseRange(a, lo, runHi); } else { // Ascending while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0) runHi++; } return runHi - lo; } } ``` 上面的代码中最终会跑到 `countRunAndMakeAscending()` 这个方法中。我们可以看见,只用了compare方法,所以在调用Arrays.sort方法只传具体compare重写方法的类对象就行,这也是Comparator接口中必须要子类实现的一个方法。 # 27,责任链模式 ## 27.1 责任链模式的定义和特点 责任链(Chain of Responsibility)模式的定义:**为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。** 注意:责任链模式也叫职责链模式。 在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。 **责任链模式是一种对象行为型模式,其主要优点如下。** 1. 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。 2. 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。 3. 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。 4. 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。 5. 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。 **其主要缺点如下。** 1. 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。 2. 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。 3. 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。 ## 27.2 责任链模式的结构与实现 ### 27.2.1 责任链模式的结构 1. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。 2. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。 3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。 ### 27.2.1 代码实现 开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行 **关系类图** ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_17-45-53.png) **LeaveRequest** ```java package com.zhuang.responsibility; /** * @Classname LeaveRequest * @Description 请假条 * @Date 2021/3/31 16:21 * @Created by dell */ public class LeaveRequest { //姓名 private String name; // 请假天数 private int num; // 请假内容 private String content; public LeaveRequest(String name, int num, String content) { this.name = name; this.num = num; this.content = content; } public String getName() { return name; } public int getNum() { return num; } public String getContent() { return content; } } ``` **Handler** ```java package com.zhuang.responsibility; /** * @Classname Handler * @Description 用一句话描述类的作用 * @Date 2021/3/31 16:23 * @Created by dell */ public abstract class Handler { protected final static int NUM_ONE = 1; protected final static int NUM_THREE = 3; protected final static int NUM_SEVEN = 7; //该领导处理的请假天数区间 private int numStart; private int numEnd; //领导上还有领导 private Handler nextHandler; //设置请假天数范围 public Handler(int numStart) { this.numStart = numStart; } //设置请假天数范围 public Handler(int numStart, int numEnd) { this.numStart = numStart; this.numEnd = numEnd; } //设置上级领导 public void setNextHandler(Handler nextHandler) { this.nextHandler = nextHandler; } //提交请假条 public final void submit(LeaveRequest leaveRequest) { if (this.numStart == 0) { return; } //请假天数达到领导处理要求 if (leaveRequest.getNum() >= this.numStart) { this.handleLeave(leaveRequest); //如果还有上级 并且请假天数超过当前领导的处理范围 if (this.nextHandler != null && leaveRequest.getNum() > numEnd) { //继续提交 this.nextHandler.submit(leaveRequest); } else { System.out.println("流程结束!!!"); } } } //各级领导处理请假条方法 protected abstract void handleLeave(LeaveRequest leave); } ``` **GroupLeader** ```java package com.zhuang.responsibility; /** * @Classname GroupLeader * @Description 小组长类 * @Date 2021/3/31 16:33 * @Created by dell */ public class GroupLeader extends Handler { //1-3天的假 public GroupLeader() { super(Handler.NUM_ONE, Handler.NUM_THREE); } @Override protected void handleLeave(LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "!"); System.out.println("小组长审批通过:同意!"); } } ``` **Manager** ```java package com.zhuang.responsibility; /** * @Classname Manager * @Description 部门经理类 * @Date 2021/3/31 16:36 * @Created by dell */ public class Manager extends Handler { //3-7天的假 public Manager() { super(Handler.NUM_THREE, Handler.NUM_SEVEN); } @Override protected void handleLeave(LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "!"); System.out.println("部门经理审批通过:同意!"); } } ``` **GeneralManager** ```java package com.zhuang.responsibility; /** * @Classname GeneralManager * @Description 总经理类 * @Date 2021/3/31 16:38 * @Created by dell */ public class GeneralManager extends Handler{ //7天以上的假 public GeneralManager() { super(Handler.NUM_THREE, Handler.NUM_SEVEN); } @Override protected void handleLeave(LeaveRequest leave) { System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "!"); System.out.println("总经理审批通过:同意!"); } } ``` **Client** ```java package com.zhuang.responsibility; /** * @Classname Client * @Description 责任链模式 测试类 * @Date 2021/3/31 16:39 * @Created by dell */ public class Client { public static void main(String[] args) { //请假条 LeaveRequest leave = new LeaveRequest("小庄", 3, "出去旅游"); //各位领导 Manager manager = new Manager(); GroupLeader groupLeader = new GroupLeader(); GeneralManager generalManager = new GeneralManager(); /* 小组长上司是经理 经理上司是总经理 */ groupLeader.setNextHandler(manager); manager.setNextHandler(generalManager); //提交 groupLeader.submit(leave); } } ``` ![](https://gitee.com/zhuang-kang/note-picture/raw/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8FSnipaste_2021-03-31_16-50-58.png) ## 27.3 责任链模式的应用场景 1. 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。 2. 可动态指定一组对象处理请求,或添加新的处理者。 3. 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。