admin 管理员组文章数量: 887021
Java 23种设计模式
- (一)、设计模式相关内容介绍
- 1.软件设计模式概述
- (1).软件设计模式的产生背景
- (2).软件设计模式的概念
- (3).学习设计模式的重要性
- (4).设计模式分类
- 2.UML图
- (1).类图概述
- (2).类图的作用
- (3).类图表示法
- 3.软件设计原则
- (1).开闭原则 (重写不修改)
- (2).里氏代换原则 (添加不重写)
- (3).依赖倒转原则 (找接口不找具体类)
- (4).接口隔离原则 (接口最小化)
- (5).迪米特法则 (直接与间接)
- (6).合成复用原则
- (二)、创建者模式(5种)
- 1.单列模式
- (1).单列模式的结构
- (2).单列模式的实现
- (3).饿汉式的两种方式
- (4).懒汉式的七种方式
- (5).存在问题-> 破坏单列模式2种
- (6).解决问题-> 解决单列模式2种
- (7).JDK源码解析-Runtime类
- 2.原型模式
- (1).概述
- (2).结构
- (3).实现
- (4).案列
- (5).使用场景
- (6).扩展(深克隆)
- 3.工厂方法模式 (单点咖啡和甜点)
- (1).概述
- (2).简单工厂模式
- (3).工厂方法模式
- 4.抽象工厂模式 (咖啡-套餐)
- (1).概述
- (2).结构
- (3).实现
- (4).优缺点
- (5).使用场景
- (6).JDK源码解析- Collection.iterator方法
- 5.工厂模式的扩展💔 -(这个不属于23种)
- 6.建造者模式
- (1).概述
- (2).结构
- (3).实列
- (4).优缺点
- (5).使用场景
- (6).模式扩展
- 7.创建者模式对比
- (1).工厂方法模式VS建造者模式
- (2).抽象工厂模式VS建造者模式
- (三)、结构性模式(7种)
- 1.代理模式
- (1).概述
- (2).结构
- (3).静态代理
- (4).JDK动态代理
- (5).GCLIB动态代理
- (6).三种代理的对比
- (7).代理模式的优缺点
- (8).使用场景
- 2.适配器模式
- (1).概述
- (2).结构
- (3).类适配器模式实现
- (4).对象适配器模式
- (5).应用场景
- (6).JDK源码解析
- 3.桥接模式
- (1).概述
- (2).结构
- (3).案列
- (4).使用场景
- 4.装饰者模式
- (1).概述
- (2).结构
- (3).案列
- (4).使用场景
- (5).JDK源码解析
- (6).代理和装饰者的区别
- 5.外观模式
- (1).概述
- (2).结构
- (3).案例
- (4).使用场景
- (5).源码分析
- 6.享元模式
- (1).概述
- (2).结构
- (3).享元模式案列
- (6).优缺点和使用场景
- 7.组合模式
- (1).概述
- (2).结构
- (3).案列实现
- (4).组合模式的分类
- (5).使用场景
- (7).JDK源码分析
- (四)、行为型模式(11种)
- 1.模板方法模式
- (1).概述
- (2).结构
- (3).案列实现
- (4).优缺点
- (4).使用场景
- (6).JDK源码分析
- 2.策略模式
- (1).概述
- (2).结构
- (3).案列实现
- (4).使用场景
- (5).JDK源码解析
- 3.命令模式
- (1).命令模式
- (2).结构
- (3).案列实现
- (4).使用场景
- (5).JDK源码解析
- 4.职责链模式
- (1).概述
- (2).结构
- (3).案列实现
- (4).优缺点
- (5).JDK源码分析
- 5.状态模式
- (1).概述
- (2).结构
- (3).案列实现
- (4).使用场景
- 6.观察者模式
- (1).概述
- (2).结构
- (3).实列实现
- (4).使用场景
- (5).JDK中提供的实现
- 7.中介者模式
- (1).概述
- (2).结构
- (3).案列实现
- (4).优缺点
- 8.迭代器模式
- (1).概述
- (2).结构
- (3).案列实现:
- (4).优缺点
- (5).JDK源码解析
- 9.访问者模式
- (1).概述
- (2).结构
- (3).案列实现
- (4).使用场景
- (6).扩展
- 10.备忘录模式
- (1).概述
- (2).结构
- (3).案列实现
- (4).优缺点
- 11.解释器模式
- (1).概述
- (2).结构
- (3).优缺点
(一)、设计模式相关内容介绍
Gitee地址: https://gitee/lwt121788/design-patterns
1.软件设计模式概述
(1).软件设计模式的产生背景
"设计模式"最初并不是出现在软件设计领域种的,而是被用于建筑领域的设计中。
1977年 克里斯托弗-亚历山大 提出了253种建筑领域的基本模式。
1990年 软件工程领域开始讨论设计模式的话题。
1995年 四个人出版了《设计模式-可复用面向对象软件的基础》一书收录了23种设计模式。
(2).软件设计模式的概念
软件设计模式,又被称作设计模式,是一般被反复使用、多数人知晓的、经过分类编目的。代码设计经验总结的。它描述了在软件设计种遇到的常用问题的解决方案。
(3).学习设计模式的重要性
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承和多态性以及类的关联关系和组合关系的充分理解。
- 可以提高程序员的思维能力、编程能力和设计能力。
- 使程序设计更加标准化、代码编制更加工程化、是软件开发效率大大提高、从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读取性强、可靠性高、灵活性好、可维护性强。
(4).设计模式分类
1.创建型模式:
-
用于描述"怎么创建对象",他的特点是"将对象的创建与使用分离"
。GOF 书种 提供了单列、原型、工厂方法、抽象工厂、建造者等5种创建型模式。
- 结构型模式:
-
用于描述如何将类或对象按某种布局组成更强大的结构
,GOF书中提供了代理、适配器、桥接、装饰、外观、享元、组合等7种结构型模式
- 行为型模式:
-
用于描述类或对象之间怎么样相互协作共同完成单个对象无法单独完成的任务,以及怎么样分配职责
。GOF书中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、爹嗲气、访问者、备忘录、解释器等11种行为型模式。
2.UML图
统一建模语言、是用来设计软件的可视化建模语言。它的特点使简单、统一、图形化、能表达软件设计种的动态与静态信息。
UML 从目标系统的不同角度触发,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等9种图
。
(1).类图概述
类图是显示了模型的静态结构,特别是模型种存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性地信息。类图是面向对象建模地主要组成部分。
(2).类图的作用
- 在软件工程中,类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化了人们对系统的理解。
- 类图是系统分析和设计阶段的重要产物,是系统编码和测试的重要模型。
(3).类图表示法
1. 类的表示方法
在UML类图中,类使用包含类名、属性和方法
且带有分割线的矩形来表示.
表示有三个私有属性: name的类型为string。sex的类型为int。age的类型为int。一个公有的方法student的返回类型是void、参数列表为null。
属性/方法名称前加号和减号表示了这个属性/方法的可见性,UML类图中表示可见性的符号有三种:
- +: 表示public
- -: 表示private
- #:表示protected
中括号中的内容表示是: 可选的。 也有将类型放在变量名前面的,返回值放在方法名的前面
属性的完整表示方式: 可见性符号 属性名称:属性名称类型 [=初始化值(可有可无)]
属性的完整表示方式: 可见性 名称(参数列表) [: 返回类型]
2. 类与类之间关系的表示方法
1. 关联关系🖤
关联关系是对象之间的一种索引关系,用于表示一类对象与另一类对象之间的联系。列如师生、师徒、夫妻等等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系
。
关联又可以分为单向关联、双向关联、自关联。
(1).单向关联:
在UML图中单向关联用一个带箭头的实现表示。上图表示每一个顾客都有一个地址,这通过Customer类持有一个类型为Address的成员变量类实现。
(2).双向关联:
在UML类图中,双向关联用一个不带箭头的直线表示。上图中在Customer类中维护一个List表示一个顾客可以购买多个商品,在Product类中维护一个Customer类型的成员变量表示这个产品被哪个顾客所购买。
(3).自关联:
自关联在UML类图中用一个带有箭头且指向自身的实线表示,上图的意思就是Node类包含类型Node的成员变量,也就是自己包含自己。
2. 聚合关系🖤
聚合关系是关联关系的一种,是强关联关系,是整体部分之间的关系。
聚合关系样式通过成员对象来实现的,其中成员对象是整体对象的一部分,但成员对象可以脱离整个对象而独立存在。列如: 学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
在UML类图中,聚合关系可以用带空心菱形的实现来表示,菱形指向整体
.下图所示是大学和教师的关系图。
3. 组合关系🖤
组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。
在组合关系中,整体对象可以控制部分对象的生命周期
,一旦整体对象不存在了,部分对象也将不存在,部分对象不能脱离整体对象的存在,列如头和嘴的关系,头不在了嘴也就不在了。
在UML类图中,组合关系用带实心的菱形来表示,菱形指向整体。
4. 依赖关系🖤
依赖关系是一种使用关系,它是随想之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某一个类的方法通过局部变量、方法的参数或者静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
在UML类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图,司机驾驶汽车。
5. 继承关系🖤
继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。
在UML类图中,泛化关系用带空心的三角箭头的实现来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。列如: Student类和Teacher类都是Person的子类。
6.实现关系🖤
实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
在UML类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。列如: 汽车和船实现了交通工具。
3.软件设计原则
在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性
,使程序员尽量根据6条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本。
(1).开闭原则 (重写不修改)
对扩展开放,对修改关闭。 在程序需要扩展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,就是为了使程序的扩展性好,易于维护和升级
。
想要达到这样的效果,我们需要使用接口和抽象类。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定
。而软件中容易变化的细节可以从抽象派生来的实现类来进行扩展,当软键需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
eg: 鼠标和键盘的USB。插上即用,拔掉即失。还有搜狗输入法的换肤操作,实际上就是定义一个抽象类,然后有不同的子类对其实现。用户窗体可以根据需要选择或者增加信的主题,而不需要修改原有的代码。所以它是满足开闭原则的。
抽象皮肤类
package com.jsxs.principles.demo1;
/**
* @Author Jsxs
* @Date 2023/4/15 19:38
* @PackageName:com.jsxs.principles.demo1
* @ClassName: AbstractSkin
* @Description: TODO 抽象皮肤类
* @Version 1.0
*/
public abstract class AbstractSkin {
// 抽象方法时一定要被继承的子类实现的。
public abstract void display();
}
默认皮肤
package com.jsxs.principles.demo1;
/**
* @Author Jsxs
* @Date 2023/4/15 19:40
* @PackageName:com.jsxs.principles.demo1
* @ClassName: DefaltSkin
* @Description: TODO 默认皮肤类
* @Version 1.0
*/
public class DefaultSkin extends AbstractSkin{ //1. 我么需要继承一个抽象皮肤类
@Override
public void display() { //2. 抽象方法我们必须要实现
System.out.println("恭喜你,成功切换为默认的皮肤!");
}
}
jsxs皮肤
package com.jsxs.principles.demo1;
/**
* @Author Jsxs
* @Date 2023/4/15 19:42
* @PackageName:com.jsxs.principles.demo1
* @ClassName: JsxsSkin
* @Description: TODO 吉士先生皮肤
* @Version 1.0
*/
public class JsxsSkin extends AbstractSkin{ //1. 继承抽象类
@Override
public void display() { //2. 必须重写抽象方法
System.out.println("恭喜您,成功切换为吉士先生皮肤!!");
}
}
搜狗输入法
package com.jsxs.principles.demo1;
/**
* @Author Jsxs
* @Date 2023/4/15 19:44
* @PackageName:com.jsxs.principles.demo1
* @ClassName: SougouInpt
* @Description: TODO 聚合关系: 也就是强关联 "部分可以离开整体"
* @Version 1.0
*/
public class SougouInpt {
private AbstractSkin abstractSkin;
public void setKin(AbstractSkin abstractSkin) { //alt+insert
this.abstractSkin = abstractSkin;
}
public void display(){
abstractSkin.display(); // 调用方法
}
}
模拟客户
package com.jsxs.principles.demo1;
/**
* @Author Jsxs
* @Date 2023/4/15 19:56
* @PackageName:com.jsxs.principles.demo1
* @ClassName: Client
* @Description: TODO 实现类-客户端
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 1.创建搜狗输入法对象
SougouInpt sougouInpt = new SougouInpt();
// 2.创建皮肤对象
DefaultSkin defaultSkin = new DefaultSkin();
// 3.将皮肤设置到输入法中
sougouInpt.setKin(defaultSkin);
// 4.显示皮肤
sougouInpt.display();
}
}
以上的代码就是实现了: 开闭原则。我们对增加业务是开放的对修改是关闭的。一个抽象方法、多个实现类、一个聚合关系(部分可以脱离整体)。
(2).里氏代换原则 (添加不重写)
里氏代换原则是面向对象设计的基本原则之一。
里氏代换原则: 任何基类可以出现的地方,子类一定可以出现
。通俗的理解: 子类可以扩展父类的功能,但不能改变原有的功能。换句话说,子类继承父类时,除了**添加新的方法完成新增功能外,尽量不要重写父类的方法**。
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率非常大。
经典列子:
eg: 正方形不是长方形
在数学领域里,正方形为一体是长方形,他是一个长度相等的长方形。所以,我们开发的一个与集合图形相关的软件系统,就可以顺利成章的让正方形继承自长方形。
反列
长方形类
package com.jsxs.principles.demo2;
/**
* @Author Jsxs
* @Date 2023/4/15 21:35
* @PackageName:com.jsxs.principles.demo2
* @ClassName: Rectangle
* @Description: TODO 长方形
* @Version 1.0
*/
public class Rectangle {
private double length;
private double width;
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
}
正方形类
package com.jsxs.principles.demo2;
/**
* @Author Jsxs
* @Date 2023/4/15 21:36
* @PackageName:com.jsxs.principles.demo2
* @ClassName: Square
* @Description: TODO 正方形: “继承长方形”
* @Version 1.0
*/
public class Square extends Rectangle{ //1. 继承长方形
@Override
public void setLength(double length) { //2.重写长方形的长
super.setLength(length);
super.setWidth(length);
}
@Override
public void setWidth(double width) { // 3.重写长方形的宽
super.setWidth(width);
super.setLength(width);
}
}
测试类
package com.jsxs.principles.demo2;
/**
* @Author Jsxs
* @Date 2023/4/15 21:40
* @PackageName:com.jsxs.principles.demo2
* @ClassName: RectangleTest
* @Description: TODO
* @Version 1.0
*/
public class RectangleTest {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setWidth(10);
rectangle.setLength(20);
// 进行扩宽的操作
resize(rectangle);
printLengthAndWidth(rectangle);
// 尝试传递子类而不是不基类
System.out.println("---------------------");
Square square = new Square();
square.setLength(10);
square.setWidth(10);
resize(square);
printLengthAndWidth(square);
}
// 扩展方法
public static void resize(Rectangle rectangle){
// 判断宽如果比长小,那么就进行扩宽的操作
while (rectangle.getWidth()<=rectangle.getLength()){
rectangle.setWidth(rectangle.getWidth()+1);
}
}
// 打印长和宽
public static void printLengthAndWidth(Rectangle rectangle){
System.out.println(rectangle.getWidth());
System.out.println(rectangle.getLength());
}
}
我们运行这段代码就会发现:“假如是长方形传入,那么就会输出长和宽,但是当我们正方形传入,我们就会发现程序会一直处于循环状态,并不会输出任何的日志信息。得出结论:“长方形适合这个代码、正方形不适合这个代码。基类能出现的地方,子类一定可以出现,假如基类能出现的地方,子类不能出现,那么就违反了里氏代换原则。””
正列
接口
package com.jsxs.principles.demo2.after;
/**
* @Author Jsxs
* @Date 2023/4/16 7:52
* @PackageName:com.jsxs.principles.demo2.after
* @ClassName: Quadrilateral
* @Description: TODO 四边形接口
* @Version 1.0
*/
public interface Quadrilateral {
double getLength(); //获取长
double getWidth(); //获取宽
}
长方形
package com.jsxs.principles.demo2.after;
/**
* @Author Jsxs
* @Date 2023/4/16 7:55
* @PackageName:com.jsxs.principles.demo2.after
* @ClassName: Rectangle
* @Description: TODO 长方形实现接口
* @Version 1.0
*/
public class Rectangle implements Quadrilateral{
private double length;
private double width;
@Override
public double getLength() {
return length;
}
@Override
public double getWidth() {
return width;
}
public void setLength(double length) {
this.length = length;
}
public void setWidth(double width) {
this.width = width;
}
}
正方形
package com.jsxs.principles.demo2.after;
/**
* @Author Jsxs
* @Date 2023/4/16 7:53
* @PackageName:com.jsxs.principles.demo2.after
* @ClassName: Square
* @Description: TODO 正方形实现四边形的接口
* @Version 1.0
*/
public class Square implements Quadrilateral{
private double side; //1.定义变量边长
@Override
public double getLength() {
return side;
}
@Override
public double getWidth() {
return side;
}
public void setSide(double side) {
this.side = side;
}
}
测试实现类
package com.jsxs.principles.demo2.after;
/**
* @Author Jsxs
* @Date 2023/4/16 7:56
* @PackageName:com.jsxs.principles.demo2.after
* @ClassName: RectangleTest
* @Description: TODO
* @Version 1.0
*/
public class RectangleTest {
public static void main(String[] args) {
// 1. 创建长方形的对象
com.jsxs.principles.demo2.after.Rectangle rectangle = new com.jsxs.principles.demo2.after.Rectangle(); // 创建一个对象
rectangle.setLength(20);
rectangle.setWidth(10);
// 2.调用方法进行扩宽的操作
resize(rectangle);
printLengthAndWidth(rectangle);
}
// 扩展方法
public static void resize(Rectangle rectangle){ // 这个参数是: after的。这里我们传入的是长方形,因为长方形和正方形没有了父子关系,所以这里只能进行传输正正方形。
// 判断宽如果比长小,那么就进行扩宽的操作
while (rectangle.getWidth()<=rectangle.getLength()){
rectangle.setWidth(rectangle.getWidth()+1);
}
}
// 打印长和宽
public static void printLengthAndWidth(Quadrilateral quadrilateral){
System.out.println(quadrilateral.getWidth());
System.out.println(quadrilateral.getLength());
}
}
(3).依赖倒转原则 (找接口不找具体类)
高层模块不应该依赖底层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单地说: 就是要求对抽象进行编程,不要对实现进行编程
,这样就降低了客户与实现魔窟啊的耦合。
高层模块不应该依赖于底层模块
细节应该依赖于抽象
eg: 组装电脑
现要组装一台电脑,需要陪考吗CPU、硬盘、内存条。只有这些配置都有了,计算机次啊能正常的运行。选择CPU有很多选择,如Inter、AMD等,硬盘可以选择希捷,西数等,内存条可以选择金士顿、海盗船等。
组合关系
硬盘
package com.jsxs.principles.demo3.before;
/**
* @Author Jsxs
* @Date 2023/4/16 8:54
* @PackageName:com.jsxs.principles.demo3.before
* @ClassName: yingpan
* @Description: TODO 硬盘
* @Version 1.0
*/
public class yingpan {
// 1.存储数据的方法
public void save(String date){
System.out.println("硬盘存储数据: "+date);
}
// 2.获取数据的方法
public String get(){
System.out.println("获取硬盘存储数据: ");
return "数据";
}
}
处理器
package com.jsxs.principles.demo3.before;
/**
* @Author Jsxs
* @Date 2023/4/16 8:56
* @PackageName:com.jsxs.principles.demo3.before
* @ClassName: CPU
* @Description: TODO CPU中央处理器
* @Version 1.0
*/
public class CPU {
public void run(){
System.out.println("使用CPU处理器");
}
}
内存条
package com.jsxs.principles.demo3.before;
/**
* @Author Jsxs
* @Date 2023/4/16 8:57
* @PackageName:com.jsxs.principles.demo3.before
* @ClassName: neicun
* @Description: TODO
* @Version 1.0
*/
public class neicun {
public void save(){
System.out.println("使用内存条的数据");
}
}
计算机整体
package com.jsxs.principles.demo3.before;
/**
* @Author Jsxs
* @Date 2023/4/16 8:58
* @PackageName:com.jsxs.principles.demo3.before
* @ClassName: Computer
* @Description: TODO 组合关系
* @Version 1.0
*/
public class Computer { // 电脑统一
private CPU cpu;
private neicun neicun;
private yingpan yingpan;
public CPU getCpu() {
return cpu;
}
public void setCpu(CPU cpu) {
this.cpu = cpu;
}
public com.jsxs.principles.demo3.before.neicun getNeicun() {
return neicun;
}
public void setNeicun(com.jsxs.principles.demo3.before.neicun neicun) {
this.neicun = neicun;
}
public com.jsxs.principles.demo3.before.yingpan getYingpan() {
return yingpan;
}
public void setYingpan(com.jsxs.principles.demo3.before.yingpan yingpan) {
this.yingpan = yingpan;
}
public void run(){
System.out.println("计算机开始开机操作");
String s = yingpan.get();
System.out.println("从硬盘上获取的数据是:"+s);
cpu.run();
neicun.save();
}
}
客户端实现类
package com.jsxs.principles.demo3.before;
/**
* @Author Jsxs
* @Date 2023/4/16 9:01
* @PackageName:com.jsxs.principles.demo3.before
* @ClassName: ComputerTest
* @Description: TODO
* @Version 1.0
*/
public class ComputerTest {
public static void main(String[] args) {
// 1.创建计算机对象
Computer computer = new Computer();
yingpan yingpan = new yingpan();
CPU cpu = new CPU();
neicun neicun = new neicun();
computer.setCpu(cpu);
computer.setYingpan(yingpan);
computer.setNeicun(neicun);
computer.run();
}
}
上面的代码可以看到已经组装了一台电脑,但是视乎组装的电脑只能是Inter的,内存条值能使金士顿的、硬盘只能是希捷的。这对用户来说肯定不是友好的,因为用户很喜欢自定义机箱。如果我们想要更改CPU肯定要修改Computer所以违反了开闭原则。
正列
CPU接口
package com.jsxs.principles.demo3.after;
/**
* @Author Jsxs
* @Date 2023/4/16 9:23
* @PackageName:com.jsxs.principles.demo3.after
* @ClassName: CPUInterface
* @Description: TODO CPU接口
* @Version 1.0
*/
public interface CPUInterface {
public void run();
}
CPU实现类
package com.jsxs.principles.demo3.after;
/**
* @Author Jsxs
* @Date 2023/4/16 9:24
* @PackageName:com.jsxs.principles.demo3.after
* @ClassName: CpuImp
* @Description: TODO
* @Version 1.0
*/
public class CpuImp implements CPUInterface{
@Override
public void run() {
System.out.println("自定义的CPU");
}
}
内存条接口
package com.jsxs.principles.demo3.after;
/**
* @Author Jsxs
* @Date 2023/4/16 9:24
* @PackageName:com.jsxs.principles.demo3.after
* @ClassName: NeicunInterface
* @Description: TODO
* @Version 1.0
*/
public interface NeicunInterface {
public void save();
}
内存条实现类
package com.jsxs.principles.demo3.after;
/**
* @Author Jsxs
* @Date 2023/4/16 9:25
* @PackageName:com.jsxs.principles.demo3.after
* @ClassName: NeicunImp
* @Description: TODO
* @Version 1.0
*/
public class NeicunImp implements NeicunInterface{
@Override
public void save() {
System.out.println("自定义的内存条存取数据");
}
}
硬盘接口
package com.jsxs.principles.demo3.after;
/**
* @Author Jsxs
* @Date 2023/4/16 9:21
* @PackageName:com.jsxs.principles.demo3.after
* @ClassName: HardCode
* @Description: TODO 硬盘接口
* @Version 1.0
*/
public interface HardCode {
public void save(String date); //存储数据
public String get(); //返回数据
}
硬盘实现类
package com.jsxs.principles.demo3.after;
/**
* @Author Jsxs
* @Date 2023/4/16 9:25
* @PackageName:com.jsxs.principles.demo3.after
* @ClassName: HardCodeImp
* @Description: TODO
* @Version 1.0
*/
public class HardCodeImp implements HardCode{
@Override
public void save(String date) {
System.out.println("自定义硬盘存取的数据是: "+date);
}
@Override
public String get() {
System.out.println("自定义的硬盘数据");
return "自定的硬盘数据";
}
}
Computer组合类
package com.jsxs.principles.demo3.after;
/**
* @Author Jsxs
* @Date 2023/4/16 9:26
* @PackageName:com.jsxs.principles.demo3.after
* @ClassName: ComputerImp
* @Description: TODO
* @Version 1.0
*/
public class ComputerImp {
private CPUInterface cpuInterface;
private HardCode hardCode;
private NeicunInterface neicunInterface;
public CPUInterface getCpuInterface() {
return cpuInterface;
}
public void setCpuInterface(CPUInterface cpuInterface) {
this.cpuInterface = cpuInterface;
}
public HardCode getHardCode() {
return hardCode;
}
public void setHardCode(HardCode hardCode) {
this.hardCode = hardCode;
}
public NeicunInterface getNeicunInterface() {
return neicunInterface;
}
public void setNeicunInterface(NeicunInterface neicunInterface) {
this.neicunInterface = neicunInterface;
}
public void run(){
System.out.println("自定义的计算机开始工作!!");
cpuInterface.run();
String s = hardCode.get();
hardCode.save(s);
neicunInterface.save();
}
}
测试类
package com.jsxs.principles.demo3.after;
/**
* @Author Jsxs
* @Date 2023/4/16 9:30
* @PackageName:com.jsxs.principles.demo3.after
* @ClassName: ComputerTest
* @Description: TODO
* @Version 1.0
*/
public class ComputerTest {
public static void main(String[] args) {
// 1.定义三个实现类
CpuImp cpuImp = new CpuImp();
HardCodeImp hardCodeImp = new HardCodeImp();
NeicunImp neicunImp = new NeicunImp();
// 2. 定义一个计算机
ComputerImp computerImp = new ComputerImp();
computerImp.setCpuInterface(cpuImp);
computerImp.setHardCode(hardCodeImp);
computerImp.setNeicunInterface(neicunImp);
computerImp.run();
}
}
(4).接口隔离原则 (接口最小化)
客户端不应该被迫依赖于它不适用的方法;一个类对另一个类的依赖应该建立在最小的接口上。
接口不应该被迫依赖它不适用的方法
一个类对另一个类的依赖应该建立在最小的接口上
eg : 安全门案列
我们需要一个 黑马 品牌的安全门,该安全门具有防火、防水、防盗的功能提取成一个接口,形成一套规范。类图如下:
接口
package com.jsxs.principles.demo4.befor;
/**
* @Author Jsxs
* @Date 2023/4/16 10:13
* @PackageName:com.jsxs.principles.demo4.befor
* @ClassName: SafeDoor
* @Description: TODO 安全门接口
* @Version 1.0
*/
public interface SafeDoor {
void antiTheft(); //防盗
void fireProof(); //防火
void waterProof(); //防水
}
实现类
package com.jsxs.principles.demo4.befor;
/**
* @Author Jsxs
* @Date 2023/4/16 10:15
* @PackageName:com.jsxs.principles.demo4.befor
* @ClassName: HeimaSaferDoor
* @Description: TODO 实现类
* @Version 1.0
*/
public class HeimaSaferDoor implements SafeDoor{
@Override
public void antiTheft() {
System.out.println("防盗");
}
@Override
public void fireProof() {
System.out.println("防火");
}
@Override
public void waterProof() {
System.out.println("防水");
}
}
测试类
package com.jsxs.principles.demo4.befor;
/**
* @Author Jsxs
* @Date 2023/4/16 10:17
* @PackageName:com.jsxs.principles.demo4.befor
* @ClassName: SafeDoorTest
* @Description: TODO
* @Version 1.0
*/
public class SafeDoorTest {
public static void main(String[] args) {
HeimaSaferDoor heimaSaferDoor = new HeimaSaferDoor();
heimaSaferDoor.antiTheft();
heimaSaferDoor.fireProof();
heimaSaferDoor.waterProof();
}
}
上面的设计我们发现问题: 黑马品牌的安全门具有防盗、防火、防水的功能。现在如果我们还需要再创建一个传智品牌的安全们,而它只具有防盗、防水的功能。很显然我们这里设计的接口就违反了接口隔离原则,那么我们如何进行修改呢?
正列:
接口1
package com.jsxs.principles.demo4.after;
/**
* @Author Jsxs
* @Date 2023/4/16 10:29
* @PackageName:com.jsxs.principles.demo4.after
* @ClassName: fireProof
* @Description: TODO
* @Version 1.0
*/
public interface fireProof {
void fire();
}
接口2
package com.jsxs.principles.demo4.after;
/**
* @Author Jsxs
* @Date 2023/4/16 10:29
* @PackageName:com.jsxs.principles.demo4.after
* @ClassName: waterProof
* @Description: TODO 防水
* @Version 1.0
*/
public interface waterProof {
void water();
}
接口三
package com.jsxs.principles.demo4.after;
/**
* @Author Jsxs
* @Date 2023/4/16 10:30
* @PackageName:com.jsxs.principles.demo4.after
* @ClassName: theafProof
* @Description: TODO
* @Version 1.0
*/
public interface theafProof {
void thymef();
}
黑马门
package com.jsxs.principles.demo4.after;
/**
* @Author Jsxs
* @Date 2023/4/16 10:30
* @PackageName:com.jsxs.principles.demo4.after
* @ClassName: HeimaDoor
* @Description: TODO
* @Version 1.0
*/
public class HeimaDoor implements fireProof,waterProof{
@Override
public void fire() {
System.out.println("防火");
}
@Override
public void water() {
System.out.println("防水");
}
}
测试实验
package com.jsxs.principles.demo4.after;
/**
* @Author Jsxs
* @Date 2023/4/16 10:31
* @PackageName:com.jsxs.principles.demo4.after
* @ClassName: DoorTest
* @Description: TODO
* @Version 1.0
*/
public class DoorTest {
public static void main(String[] args) {
HeimaDoor heimaDoor = new HeimaDoor();
heimaDoor.fire();
heimaDoor.water();
}
}
(5).迪米特法则 (直接与间接)
迪米特法则又叫最少知识原则。
只和你的直接朋友交谈,不跟陌生人说话。 其含义是: 如果两个软件实体无需直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发调用
。其目的是降低类之间的耦合度,提高模块的相对独立开发。
迪米特法则中的 "朋友"是指: 当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数
等。这些兑现同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
eg: 明星和经纪人的关系实列
明星由于全身心的投入艺术、所以很多的日常事务都是由经纪人负责处理的,比如粉丝的见面会和媒体公司的业务洽谈等。这里的经纪人是明星的朋友,而粉丝和媒体公司是明星的陌生人,所以适合使用迪米特法则。
明星
package com.jsxs.principles.demo5;
/**
* @Author Jsxs
* @Date 2023/4/16 11:00
* @PackageName:com.jsxs.principles.demo5
* @ClassName: Star
* @Description: TODO
* @Version 1.0
*/
public class Star {
private String name;
public Star(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
粉丝
package com.jsxs.principles.demo5;
/**
* @Author Jsxs
* @Date 2023/4/16 11:01
* @PackageName:com.jsxs.principles.demo5
* @ClassName: Fans
* @Description: TODO 粉丝类
* @Version 1.0
*/
public class Fans {
private String name;
public Fans(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
公司
package com.jsxs.principles.demo5;
/**
* @Author Jsxs
* @Date 2023/4/16 11:02
* @PackageName:com.jsxs.principles.demo5
* @ClassName: Bussiness
* @Description: TODO 媒体公司类
* @Version 1.0
*/
public class Company {
private String name;
public Company(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
经纪人
package com.jsxs.principles.demo5;
/**
* @Author Jsxs
* @Date 2023/4/16 11:03
* @PackageName:com.jsxs.principles.demo5
* @ClassName: Agent
* @Description: TODO 经纪人类
* @Version 1.0
*/
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;
}
// 1.和粉丝见面
public void meeting(){
System.out.println(star.getName()+"见面粉丝"+fans.getName());
}
//2.和媒体公司洽谈
public void business(){
System.out.println(star.getName()+"洽谈公司"+company.getName());
}
}
测试
package com.jsxs.principles.demo5;
/**
* @Author Jsxs
* @Date 2023/4/16 11:06
* @PackageName:com.jsxs.principles.demo5
* @ClassName: StarTest
* @Description: TODO
* @Version 1.0
*/
public class StarTest {
public static void main(String[] args) {
Fans fans = new Fans("李明");
Company company = new Company("CKQN");
Star star = new Star("及你太美");
Agent agent = new Agent();
agent.setStar(star);
agent.setCompany(company);
agent.setFans(fans);
agent.business();
agent.meeting();
}
}
(6).合成复用原则
合成复用原则是指: 尽量先使用组合或则和聚合等关系来实现,其次再开率使用继承关系来实现。
通常类的复用分为继承复用和合成复用两种。
继承复用虽然有简单和容易实现的优点,但它也存在以下缺点:
继承复用破坏了类的封装性
。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为"白箱"复用
子类与父类的耦合度高
。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展和维护
。它限制了复用的灵活性
。从子类继承而来的实现是静态的,在编译时已经定义,所以再运行时不可能发生变化。
采用组合或聚合复用时,可以将已有对象纳入新的对象中,使之称为对象的一部分,新对象可以调用已有对象的功能,它有以下的优点:
它维持了类的封装性
。因为成分对象的内部细节时新对象看不见的,所以这种服用又称为"黑箱"复用。对象的耦合度低
。可以在类的成员位置声明抽象。复用的灵活性高
。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
汽车按动力源划分可分为汽油汽车、电动汽车;按"颜色"划分为白色汽车、黑色汽车和红色汽车等。如果同时看i哦率这两种分类,其组合就很多。类图如下:
继承复用: 假如新增填一个汽车动力、会牵扯到很多的子类
将继承复用更改为集合复用
(二)、创建者模式(5种)
创建者模式的主要关注点: “怎么创建对象”,它的主要特点是:“将对象的创建和使用分离
”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
1.单列模式
单列模式 是Java中最简单的设计模式之一。这种类型的设计属于创建型模式,他提供了一种创建对象的最佳方式。
这种模式会涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其他唯一的方式,可以直接访问,不需要实列化该类的对象。
(1).单列模式的结构
单列模式饿主要有以下角色:
- 单列模式。只能创建一个实列的类
- 访问类。使用单列类
(2).单列模式的实现
单列设计模式分类两种:
- 饿汉式:
类加载就会导致该单实列对象被创建
(加载的时候就创建) - 懒汉式: 类加载不会导致该单列对象被创建,而是
首次使用该对象时才会创建
。
(3).饿汉式的两种方式
- 饿汉式-方式一 (静态变量方式)
单列模式
package com.jsxs.pattern.singletion.demo1;
/**
* @Author Jsxs
* @Date 2023/4/16 14:11
* @PackageName:com.jsxs.pattern.singletion.demo1
* @ClassName: Singletion
* @Description: TODO 静态成员变量
* @Version 1.0
*/
public class Singletion {
// 1.私有构造方法: 外界不能创建这个对象也就是->new
private Singletion() {
}
//2.在本类中创建该类的对象: private->外界不能直接访问。 static->设置为静态的变量,目的是为了让静态方法获取。
private static Singletion singletion=new Singletion();
//3.提供一个公共的访问方式,让外界获取该对象
public static Singletion getInstance(){
return singletion;
}
}
访问类
package com.jsxs.pattern.singletion.demo1;
/**
* @Author Jsxs
* @Date 2023/4/16 14:21
* @PackageName:com.jsxs.pattern.singletion.demo1
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
//1.创建Singletion类的对象
Singletion instance = Singletion.getInstance();
Singletion instance1 = Singletion.getInstance();
// 2.判断创建得到这两个对象是否是一个对象
System.out.println(instance==instance1); // == 判断的时地址
}
}
- 饿汉式-方式二 (静态代码块)
package com.jsxs.pattern.singletion.demo2;
/**
* @Author Jsxs
* @Date 2023/4/16 14:30
* @PackageName:com.jsxs.pattern.singletion.demo2
* @ClassName: Singleton
* @Description: TODO 饿汉式2- 静态代码块---- 静态变量和静态代码块谁先声明谁先执行。
* @Version 1.0
*/
public class Singleton {
// 1.私有构造方法
private Singleton(){}
//2.声明Singleton类型的变量
private static Singleton instance; // 并未初始化
//3.静态代码块中进行赋值
static {
instance=new Singleton();
}
//4.对外提供该类对象的方法
public static Singleton getInstance(){
return instance;
}
}
package com.jsxs.pattern.singletion.demo2;
/**
* @Author Jsxs
* @Date 2023/4/16 14:34
* @PackageName:com.jsxs.pattern.singletion.demo2
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance==instance1);
}
}
说明: 该方式在成员位置声明Singleton类型的静态变量,而对象的创建时在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本一样,当然该方式也存在内存浪费的问题。
(4).懒汉式的七种方式
- 饿汉式-方式1 (静态方式-线程不安全)
package com.jsxs.pattern.singletion.demo3;
/**
* @Author Jsxs
* @Date 2023/4/16 14:43
* @PackageName:com.jsxs.pattern.singletion.demo3
* @ClassName: Singleton
* @Description: TODO 懒汉式-1
* @Version 1.0
*/
public class Singleton {
// 1.私有构造方法
private Singleton(){}
// 2.声明Singleton类型的变量---*******并没有赋值的操作
private static Singleton instace;
//3.对外提供访问方式
public static Singleton getInstance(){
if (instace==null){ //1.如果以前没有创建对象,那么就创建一个对象。如果创建过,那么就直接返回我们第一次创建的对象。
instace=new Singleton();
}
return instace;
}
}
package com.jsxs.pattern.singletion.demo3;
/**
* @Author Jsxs
* @Date 2023/4/16 14:50
* @PackageName:com.jsxs.pattern.singletion.demo3
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
System.out.println( Singleton.getInstance().hashCode());
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1.hashCode());
}
}
我们发现在单线程的情况下: 哈希值是一样的也就是对象是一样的
问题: 多线程
的情况下: 会出现哈希值的不一致->对象不一致。
package com.jsxs.pattern.singletion.demo3;
/**
* @Author Jsxs
* @Date 2023/4/16 14:43
* @PackageName:com.jsxs.pattern.singletion.demo3
* @ClassName: Singleton
* @Description: TODO 懒汉式-1
* @Version 1.0
*/
public class Singleton {
// 1.私有构造方法
private Singleton(){}
// 2.声明Singleton类型的变量---*******并没有赋值的操作
private static Singleton instace;
//3.对外提供访问方式
public static Singleton getInstance(){
if (instace==null){ //1.如果以前没有创建对象,那么就创建一个对象。如果创建过,那么就直接返回我们第一次创建的对象。
try {
Thread.sleep(2000); //等待2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
instace=new Singleton();
}
return instace;
}
}
package com.jsxs.pattern.singletion.demo3;
/**
* @Author Jsxs
* @Date 2023/4/16 14:50
* @PackageName:com.jsxs.pattern.singletion.demo3
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
new Thread(()->{
System.out.println( Singleton.getInstance().hashCode());
}).start();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1.hashCode());
}
}
- 懒汉式-2 (线程安全)
这里我们在创建对象的时候只是添加了 同步锁Synchronized
package com.jsxs.pattern.singletion.demo3;
/**
* @Author Jsxs
* @Date 2023/4/16 14:43
* @PackageName:com.jsxs.pattern.singletion.demo3
* @ClassName: Singleton
* @Description: TODO 懒汉式-2 (线程安全)
* @Version 1.0
*/
public class Singleton {
// 1.私有构造方法
private Singleton(){}
// 2.声明Singleton类型的变量---*******并没有赋值的操作
private static Singleton instace;
//3.对外提供访问方式
public static synchronized Singleton getInstance(){ // -**********添加了同步锁: "没有执行完毕,不释放资源"
if (instace==null){ //1.如果以前没有创建对象,那么就创建一个对象。如果创建过,那么就直接返回我们第一次创建的对象。
try {
Thread.sleep(2000); //等待2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
instace=new Singleton();
}
return instace;
}
}
package com.jsxs.pattern.singletion.demo3;
/**
* @Author Jsxs
* @Date 2023/4/16 14:50
* @PackageName:com.jsxs.pattern.singletion.demo3
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
new Thread(()->{
System.out.println( Singleton.getInstance().hashCode());
}).start();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1.hashCode());
}
}
说明: 该方式实现了懒加载的效果,同时又解决了线程安全问题。但是在GetInstance()方法上添加Synchronized关键字,导致该方法的执行效率特别低。从上面的代码我们可以看出,其实就是在初始化inatance的时候才会出现线程安全问题,一旦初始化完成就不存在了。
- 懒汉式-方式3 (双重检查锁)
再来讨论以下懒汉模式中加锁的问题,对于 getInstace()方法来说,绝大部分的操作时读的操作,读操作是线程安全的,所以我们没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的模式: 双层检查锁模式
(提升效率)
类对象加锁
package com.jsxs.pattern.singletion.demo4;
/**
* @Author Jsxs
* @Date 2023/4/16 15:29
* @PackageName:com.jsxs.pattern.singletion.demo4
* @ClassName: Singleton
* @Description: TODO 懒汉式3 - 双层检查锁: 目的是为了提升性能
* @Version 1.0
*/
public class Singleton {
// 1.私有构造方法
private Singleton(){}
//2.声明Singleton变量
private static Singleton instance;
//3.对外提供公共的访问方式
public static Singleton getInstance(){
//1. 第一次判断
if (instance==null){ //2. 假如为空就进入,否则直接返回。
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.类对象加锁...
synchronized (Singleton.class){
//4.第二次判断
if (instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
package com.jsxs.pattern.singletion.demo4;
/**
* @Author Jsxs
* @Date 2023/4/16 15:41
* @PackageName:com.jsxs.pattern.singletion.demo4
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
new Thread(()->{
System.out.println(Singleton.getInstance().hashCode());
}).start();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1.hashCode());
}
}
双重检查锁模式是一种非常好的单列实现模式,解决了单列、性能、线程安全问题,上面的双重检查锁模式看上去完美无缺,其实是存在问题的,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实列化对象的时候会进行优化和指令重排序操作。要解决双重检查锁模式带来的空指针异常问题,只需要使用Volatile
关键字,Volatile
关键字可以保证可见性和有序性。
在变量上添加关键字Volatile
package com.jsxs.pattern.singletion.demo4;
/**
* @Author Jsxs
* @Date 2023/4/16 15:29
* @PackageName:com.jsxs.pattern.singletion.demo4
* @ClassName: Singleton
* @Description: TODO 懒汉式3 - 双层检查锁: 目的是为了提升性能
* @Version 1.0
*/
public class Singleton {
// 1.私有构造方法
private Singleton(){}
//2.声明Singleton变量
private static volatile Singleton instance; //*******************在变量上添加关键字
//3.对外提供公共的访问方式
public static Singleton getInstance(){
//1. 第一次判断
if (instance==null){ //2. 假如为空就进入,否则直接返回。
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.类对象加锁...
synchronized (Singleton.class){
//4.第二次判断
if (instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
package com.jsxs.pattern.singletion.demo4;
/**
* @Author Jsxs
* @Date 2023/4/16 15:41
* @PackageName:com.jsxs.pattern.singletion.demo4
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
new Thread(()->{
System.out.println(Singleton.getInstance().hashCode());
}).start();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1.hashCode());
}
}
小结: 添加Volatile
关键字之后双重检查锁模式是一种比较好的单列模式实现模式,能够保证在多线程的情况下安全且性能的问题。
- 懒汉式-方式4 (静态内部类方式)
静态内部类单列模式中实列由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的
,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实列化一次,并且严格保证实列化顺序。
package com.jsxs.pattern.singletion.demo5;
/**
* @Author Jsxs
* @Date 2023/4/16 16:02
* @PackageName:com.jsxs.pattern.singletion.demo5
* @ClassName: Singleton
* @Description: TODO 静态内部类的方式
* @Version 1.0
*/
public class Singleton {
// 1.私有构造方法
private Singleton(){}
// 2.定义一个静态内部类
private static class SingletonHolder{ //为了防止外部对其进行修改,我们添加一个 final 的关键字
//在内部类中声明并初始化外部类的对象
private final static Singleton instance=new Singleton();
}
// 3.提供公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
package com.jsxs.pattern.singletion.demo5;
/**
* @Author Jsxs
* @Date 2023/4/16 16:10
* @PackageName:com.jsxs.pattern.singletion.demo5
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1==instance);
}
}
这里不存在线程安全问题,因为JVM在加载外部类的同时,不会加载静态内部类,只要当静态内部类的属性或者方法被调用的时候才会加载静态内部类。因为是静态变量只能被实列化一次...
说明: 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance(),虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全
,也能保证 Singleton类的唯一性。
小结: 静态内部类单列模式是一种优秀的单列模式
,是开源项目中比较常用的一种单列模式。在没有任何加锁的情况下,保证了多线程的安全,并且没有任何影星性能影响和空间的浪费。
- 枚举方式
枚举类实现单列模式是极力推荐的单列模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用枚举的这个特性来实现单列模式,枚举的写法非常简单,而且枚举类型是所用单列实现中唯一一种不会被破坏的单列实现模式
。
package com.jsxs.pattern.singletion.demo6;
/**
* @Author Jsxs
* @Date 2023/4/16 17:39
* @PackageName:com.jsxs.pattern.singletion.demo6
* @ClassName: Singleton
* @Description: TODO 枚举实现方式
* @Version 1.0
*/
public enum Singleton {
INSTANCE;
}
package com.jsxs.pattern.singletion.demo6;
/**
* @Author Jsxs
* @Date 2023/4/16 17:40
* @PackageName:com.jsxs.pattern.singletion.demo6
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
Singleton instance=Singleton.INSTANCE;
Singleton instance1=Singleton.INSTANCE;
System.out.println(instance1==instance);
}
}
枚举方式属于饿汉式方式 -> 如果不考虑内存首选枚举
(5).存在问题-> 破坏单列模式2种
破坏单列模式:
使上面定义的单列类可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射
。
- 序列化反序列化
设置一个静态内部类并且: 实现序列化
package com.jsxs.pattern.singletion.demo7;
import java.io.Serializable;
/**
* @Author Jsxs
* @Date 2023/4/16 17:47
* @PackageName:com.jsxs.pattern.singletion.demo7
* @ClassName: Singleton
* @Description: TODO
* @Version 1.0
*/
public class Singleton implements Serializable {
// 1.私有构造函数
private Singleton(){}
// 2.静态内部类
private static class SingletonHard{
private static final Singleton instance=new Singleton();
}
// 3.提供公共的对象
public static Singleton getInstance(){
return SingletonHard.instance;
}
}
我们首先先写一个数据、然后读取两次
package com.jsxs.pattern.singletion.demo7;
import java.io.*;
/**
* @Author Jsxs
* @Date 2023/4/16 17:55
* @PackageName:com.jsxs.pattern.singletion.demo7
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) throws Exception {
// writeObject2File();
ReadObjectFromFile();
ReadObjectFromFile();
}
// 从文件读取数据(对象)
public static void ReadObjectFromFile() throws Exception {
//1. 创建对象输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\加速器\\a.txt"));
//2. 读取对象
Singleton singleton = (Singleton) ois.readObject();
System.out.println(singleton);
//3. 释放资源
ois.close();
}
// 向文件中写数据 (对象)
public static void writeObject2File() throws Exception {
//1. 获取Singleton对象
Singleton instance = Singleton.getInstance();
// 2.创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\加速器\\a.txt"));
// 3.写对象
oos.writeObject(instance);
//4.释放资源
oos.close();
}
}
小结: 我们发现对象地址不一致-> 也就是破坏了单列模式。
- 反射 (破坏单列模式)
静态内部类
package com.jsxs.pattern.singletion.demo8;
/**
* @Author Jsxs
* @Date 2023/4/16 18:22
* @PackageName:com.jsxs.pattern.singletion.demo8
* @ClassName: Singleton
* @Description: TODO 反射破坏单列模式
* @Version 1.0
*/
public class Singleton {
// 1.定义私有化构造函数: 不让外部类创建对象
private Singleton(){}
// 2.定义静态内部类
private static class SingletonHard{
private static final Singleton instance=new Singleton();
}
// 3.开发端口
public static Singleton getInstance(){
return SingletonHard.instance;
}
}
客户端
package com.jsxs.pattern.singletion.demo8;
import java.lang.reflect.Constructor;
/**
* @Author Jsxs
* @Date 2023/4/16 18:25
* @PackageName:com.jsxs.pattern.singletion.demo8
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1.获取字节码对象
Class clazz = Singleton.class;
// 2.获取无参构造方法对象
Constructor cons = clazz.getDeclaredConstructor();
// 3.取消访问检查
cons.setAccessible(true);
//4. 创建Singleton对象
Singleton o = (Singleton)cons.newInstance();
Singleton o2 = (Singleton)cons.newInstance();
System.out.println(o==o2);
}
}
破坏了单列模式
(6).解决问题-> 解决单列模式2种
1. 序列化、反序列方式破坏单列模式的解决办法
在Singleton类中添加readResolve()
方法,在序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
这个名字是一个固定的变量名~。假如实体类被序列化了,那么会自动地实现这个代码。
// 4. 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
public Object readResolve() {
return SingletonHard.instance;
}
}
序列化
package com.jsxs.pattern.singletion.demo7;
import java.io.Serializable;
/**
* @Author Jsxs
* @Date 2023/4/16 17:47
* @PackageName:com.jsxs.pattern.singletion.demo7
* @ClassName: Singleton
* @Description: TODO
* @Version 1.0
*/
public class Singleton implements Serializable {
// 1.私有构造函数
private Singleton() {
}
// 2.静态内部类
private static class SingletonHard {
private static final Singleton instance = new Singleton();
}
// 3.提供公共的对象
public static Singleton getInstance() {
return SingletonHard.instance;
}
// 4. 当金星反序列化时,会自动调用该方法,将该方法的返回值直接返回
public Object readResolve() {
return SingletonHard.instance;
}
}
实现类
package com.jsxs.pattern.singletion.demo7;
import java.io.*;
/**
* @Author Jsxs
* @Date 2023/4/16 17:55
* @PackageName:com.jsxs.pattern.singletion.demo7
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) throws Exception {
// writeObject2File();
ReadObjectFromFile();
ReadObjectFromFile();
}
// 从文件读取数据(对象)
public static void ReadObjectFromFile() throws Exception {
//1. 创建对象输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\加速器\\a.txt"));
//2. 读取对象
Singleton singleton = (Singleton) ois.readObject();
System.out.println(singleton);
//3. 释放资源
ois.close();
}
// 向文件中写数据 (对象)
public static void writeObject2File() throws Exception {
//1. 获取Singleton对象
Singleton instance = Singleton.getInstance();
// 2.创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\加速器\\a.txt"));
// 3.写对象
oos.writeObject(instance);
//4.释放资源
oos.close();
}
}
我们先进行写入数据、然后再读取数据~
- 反射方式破解单列地解决办法
private static boolean flag=false;
// 1.定义私有化构造函数: 不让外部类创建对象
private Singleton(){
synchronized (Singleton.class){
// 判断flag地值是否是true,如果是true,说明非第一次访问,直接跑一个异常即可。如果是false的话,说明第一次创建对象
if (flag){
throw new RuntimeException("不能创建多个对象");
}
// 将flag的值设置为true
flag=true;
}
}
反射实现
package com.jsxs.pattern.singletion.demo8;
/**
* @Author Jsxs
* @Date 2023/4/16 18:22
* @PackageName:com.jsxs.pattern.singletion.demo8
* @ClassName: Singleton
* @Description: TODO 反射破坏单列模式
* @Version 1.0
*/
public class Singleton {
private static boolean flag=false;
// 1.定义私有化构造函数: 不让外部类创建对象
private Singleton(){
synchronized (Singleton.class){
// 判断flag地值是否是true,如果是true,说明非第一次访问,直接跑一个异常即可。如果是false的话,说明第一次创建对象
if (flag){
throw new RuntimeException("不能创建多个对象");
}
// 将flag的值设置为true
flag=true;
}
}
// 2.定义静态内部类
private static class SingletonHard{
private static final Singleton instance=new Singleton();
}
// 3.开发端口
public static Singleton getInstance(){
return SingletonHard.instance;
}
}
实现类
package com.jsxs.pattern.singletion.demo8;
import java.lang.reflect.Constructor;
/**
* @Author Jsxs
* @Date 2023/4/16 18:25
* @PackageName:com.jsxs.pattern.singletion.demo8
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1.获取字节码对象
Class clazz = Singleton.class;
// 2.获取无参构造方法对象
Constructor cons = clazz.getDeclaredConstructor();
// 3.取消访问检查
cons.setAccessible(true);
//4. 创建Singleton对象
Singleton o = (Singleton)cons.newInstance();
Singleton o2 = (Singleton)cons.newInstance();
System.out.println(o==o2);
}
}
(7).JDK源码解析-Runtime类
Runtime类就是i使用的单列设计模式的饿汉模式
package com.jsxs.pattern.singletion.demo9;
import java.io.IOException;
import java.io.InputStream;
/**
* @Author Jsxs
* @Date 2023/4/16 21:12
* @PackageName:com.jsxs.pattern.singletion.demo9
* @ClassName: RuntomeDemo
* @Description: TODO
* @Version 1.0
*/
public class RuntimeDemo {
public static void main(String[] args) throws IOException {
// 获取Runtime类的对象
Runtime runtime = Runtime.getRuntime();
// 调用runtime的方法exec
Process exec = runtime.exec("ipconfig");
// 调用process对象的获取输入流的方法
InputStream inputStream = exec.getInputStream();
byte[] bytes = new byte[1024 * 1024];
int count=0;
while ((count=inputStream.read())!=-1){
System.out.println(new String(bytes,0,count,"utf-8"));
}
System.out.println("你好");
}
}
2.原型模式
(1).概述
用一个创建的实列作为原型,通过复制该原型对象来创建一个和原型都相同的新对象。
(2).结构
原型模式包含如下角色:
- 抽象原型类: 规定了具体原型对象必须实现的Clone()方法。
JDK帮我们做了
。 - 具体原型类: 实现抽象原型类的cone()方法,它是可以被复制的对象
- 访问类: 使用具体原型类中的clone()方法来复制新的对象。
(3).实现
原型模式的克隆分为浅克隆和深克隆
浅克隆
: 浅克隆:对当前对象进行克隆,并克隆该对象所包含的8种基本数据类型和String类型属性(拷贝一份该对象并重新分配内存,即产生了新的对象);但如果被克隆的对象中包含除8中数据类型和String类型外的其他类型的属性,浅克隆并不会克隆这些属性(即不会为这些属性分配内存,而是引用原来对象中的属性,而不拷贝对象包含的引用指向的对象。)会指向原有对象的地址,内存地址一样
深克隆
: 深克隆:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。不再指向原有对象的地址。
Java中的Object类
中提供了 clone()
方法来实现浅克隆
。Cloneable接口是上面的类图中的抽象原型类,而实现了cloneable接口的子实现类就是具体的原型类。
具体原型类: 这里我们继承coneable接口重写clone()方法实现克隆
package com.jsxs.pattern.prototype.demo;
/**
* @Author Jsxs
* @Date 2023/4/18 13:23
* @PackageName:com.jsxs.pattern.prototype.demo
* @ClassName: Realizetype
* @Description: TODO
* @Version 1.0
*/
public class Realizetype implements Cloneable{
public Realizetype(){
System.out.println("具体的原型对象被创建了");
}
// 1.重写方法
@Override
public Realizetype clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!!!");
return (Realizetype)super.clone();
}
}
访问类
package com.jsxs.pattern.prototype.demo;
/**
* @Author Jsxs
* @Date 2023/4/18 13:33
* @PackageName:com.jsxs.pattern.prototype.demo
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
// 1.创建一个原型类对象
Realizetype realizetype = new Realizetype(); // ->利用无参的构造方法去创建对象
//2.调用Realizetype类中我们重写的clone()方法进行对象的克隆
Realizetype clone = realizetype.clone();
System.out.println("原型对象和克隆的对象是否是同一个对象 "+(realizetype==clone));
}
}
(4).案列
用原型模式生成 "三好学生"奖状
同一学校的"三号学生"奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个:"三好学生"奖状出来,然后再修改奖状上的名字即可。
具体原型类: 这里我们继承coneable接口重写clone()方法实现克隆
package com.jsxs.pattern.prototype.demo2;
/**
* @Author Jsxs
* @Date 2023/4/18 13:52
* @PackageName:com.jsxs.pattern.prototype.demo2
* @ClassName: Citation
* @Description: TODO
* @Version 1.0
*/
public class Citation implements Cloneable{
// 基本属性
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 重写clone方法
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation)super.clone();
}
public void show(){
System.out.println(name+"获得奖状一张!!");
}
}
访问类
package com.jsxs.pattern.prototype.demo2;
/**
* @Author Jsxs
* @Date 2023/4/18 13:54
* @PackageName:com.jsxs.pattern.prototype.demo2
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
// 1.原型对象的创建
Citation citation = new Citation();
citation.setName("李明-原型对象");
// 2.克隆对象
Citation clone = citation.clone();
clone.setName("吉士-克隆对象");
citation.show();
clone.show();
System.out.println(citation==clone);
}
}
(5).使用场景
- 对象的创建非常复杂,可以使用原型模式快捷的创建对象
- 性能和安全要求比较高
(6).扩展(深克隆)
将上面的"三好学生"奖状的案列中的name属性更改为Student类型的属性。
浅克隆
引用对象类
package com.jsxs.pattern.prototype.demo2;
/**
* @Author Jsxs
* @Date 2023/4/18 14:22
* @PackageName:com.jsxs.pattern.prototype.demo2
* @ClassName: Student
* @Description: TODO
* @Version 1.0
*/
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
具体原型类
package com.jsxs.pattern.prototype.demo2;
/**
* @Author Jsxs
* @Date 2023/4/18 13:52
* @PackageName:com.jsxs.pattern.prototype.demo2
* @ClassName: Citation
* @Description: TODO
* @Version 1.0
*/
public class Citation implements Cloneable{
// 基本属性
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
// 重写clone方法
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation)super.clone();
}
public void show(){
System.out.println(student.getName()+"获得奖状一张!!");
}
}
访问类
package com.jsxs.pattern.prototype.demo2;
/**
* @Author Jsxs
* @Date 2023/4/18 13:54
* @PackageName:com.jsxs.pattern.prototype.demo2
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
// 1.原型对象的创建
Citation citation = new Citation();
Student student = new Student();
student.setName("原型对象-张三");
citation.setStudent(student);
// 2.克隆对象
Citation clone = citation.clone();
Student student1 = citation.getStudent();
student1.setName("克隆对象-李四");
clone.show();
citation.show();
System.out.println(student==student1);
}
}
说明: student和student1的对象是一个对象,就会产生studen1对象的属性会覆盖student的属性的效果。对具体原型类中的引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流。
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程
Student引用类-> 序列化
package com.jsxs.pattern.prototype.demo3;
import java.io.Serializable;
/**
* @Author Jsxs
* @Date 2023/4/18 14:22
* @PackageName:com.jsxs.pattern.prototype.demo2
* @ClassName: Student
* @Description: TODO
* @Version 1.0
*/
public class Student implements Serializable { //序列化
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
具体克隆类 -> 序列化
package com.jsxs.pattern.prototype.demo3;
import java.io.Serializable;
/**
* @Author Jsxs
* @Date 2023/4/18 13:52
* @PackageName:com.jsxs.pattern.prototype.demo2
* @ClassName: Citation
* @Description: TODO
* @Version 1.0
*/
public class Citation implements Serializable {
// 基本属性
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public void show(){
System.out.println(student.getName()+"获得奖状一张!!");
}
}
访问类
package com.jsxs.pattern.prototype.demo3;
import java.io.*;
/**
* @Author Jsxs
* @Date 2023/4/18 13:54
* @PackageName:com.jsxs.pattern.prototype.demo2
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client{
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
// 1.原型对象的创建
Citation citation = new Citation();
Student student = new Student();
student.setName("原型对象-张三");
citation.setStudent(student);
// 2.创建对象输出流对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\加速器\\a.txt"));
out.writeObject(citation); //将原型对象写入文件中
out.close(); //关闭资源
ObjectInputStream ipt = new ObjectInputStream(new FileInputStream("E:\\加速器\\a.txt"));
Citation citation1 = (Citation)ipt.readObject();
ipt.close(); //关闭资源
Student student1 = citation1.getStudent();
student.setName("克隆对象-李四");
citation.show();
citation1.show();
System.out.println(student==student1);
}
}
3.工厂方法模式 (单点咖啡和甜点)
(1).概述
需求: 设计一个咖啡点餐系统。
设计1个咖啡类,并定义两个子类(美式咖啡)和拿铁咖啡;再设计一个咖啡店类,咖啡店具有点咖啡的功能。
- 反列
咖啡类
package com.jsxs.pattern.factory.before;
/**
* @Author Jsxs
* @Date 2023/4/17 12:47
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: Coffe
* @Description: TODO 咖啡类
* @Version 1.0
*/
public abstract class Coffee {
// 0. 子类必须要实现抽象方法
public abstract String getName();
// 1.加糖
public void addSuger() {
System.out.println("加糖");
}
// 2.加奶
public void addMilk() {
System.out.println("加奶");
}
}
咖啡类型
package com.jsxs.pattern.factory.before;
/**
* @Author Jsxs
* @Date 2023/4/17 12:51
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: LatteCoffe
* @Description: TODO 拿铁咖啡
* @Version 1.0
*/
public class LatteCoffee extends Coffee{
@Override
public String getName() {
return "拿铁咖啡";
}
}
package com.jsxs.pattern.factory.before;
/**
* @Author Jsxs
* @Date 2023/4/17 12:50
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: AmericanCoffee
* @Description: TODO 美式咖啡
* @Version 1.0
*/
public class AmericanCoffee extends Coffee{
@Override
public String getName() {
return "美式咖啡";
}
}
咖啡商店
package com.jsxs.pattern.factory.before;
/**
* @Author Jsxs
* @Date 2023/4/17 12:52
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: CoffeeStore
* @Description: TODO 咖啡店
* @Version 1.0
*/
public class CoffeeStore {
public Coffee orderCoffee(String type){
// /声明Coffee类型的变量,根据不同类型创建不同的coffee子类对象
Coffee coffee=null;
if ("american".equals(type)){
coffee=new AmericanCoffee(); // 1. 创建一个美食咖啡
}else if ("latte".equals(type)){
coffee=new LatteCoffee(); //2. 创建一个拿铁咖啡
}else {
throw new RuntimeException("对不起,你点的咖啡没有");
}
// 添加配料
coffee.addMilk();
coffee.addSuger();
return coffee;
}
}
package com.jsxs.pattern.factory.before;
/**
* @Author Jsxs
* @Date 2023/4/17 12:57
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
CoffeeStore coffeeStore = new CoffeeStore();
Coffee coffee = coffeeStore.orderCoffee("american");
System.out.println(coffee.getName());
}
}
假如需要新增产品,那么我们就迫不得已需要修改数据
在Java种,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重
。假如我们要更换对象,所有new对象的地方需要修改一遍,这显然违背了软件设计的开闭原则。如果使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦。如果要更换对象,直接在工厂里更换该对象即可,达到了对象解耦的目的,所以说: “工厂模式最大的优点是: 解耦”。
(2).简单工厂模式
简单工厂模式不是一种设计模式,反而更像一种编程习惯。
- 结构
简单工厂包含以下的角色
- 抽象产品: 定义了产品的规范、
描述了产品的主要特性和功能
。 - 具体产品:
实现或者继承
抽象产品的子类 - 具体工厂: 提供了创建产品的方法,调用者通过方法来获取产品。
抽象产品
package com.jsxs.pattern.factory.Single_Factory;
/**
* @Author Jsxs
* @Date 2023/4/17 13:54
* @PackageName:com.jsxs.pattern.factory.Single_Factory
* @ClassName: Coffee
* @Description: TODO
* @Version 1.0
*/
public abstract class Coffee {
// 0. 子类必须要实现抽象方法
public abstract String getName();
// 1.加糖
public void addSuger() {
System.out.println("加糖");
}
// 2.加奶
public void addMilk() {
System.out.println("加奶");
}
}
具体产品
package com.jsxs.pattern.factory.Single_Factory;
/**
* @Author Jsxs
* @Date 2023/4/17 12:51
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: LatteCoffe
* @Description: TODO 拿铁咖啡
* @Version 1.0
*/
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
package com.jsxs.pattern.factory.Single_Factory;
/**
* @Author Jsxs
* @Date 2023/4/17 12:50
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: AmericanCoffee
* @Description: TODO 美式咖啡
* @Version 1.0
*/
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
简单工厂
package com.jsxs.pattern.factory.Single_Factory;
/**
* @Author Jsxs
* @Date 2023/4/17 13:56
* @PackageName:com.jsxs.pattern.factory.Single_Factory
* @ClassName: SimleCoffeeFactory
* @Description: TODO 简单工厂
* @Version 1.0
*/
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type){
Coffee coffee=null;
if ("american".equals(type)){
coffee=new AmericanCoffee(); // 1. 创建一个美食咖啡
}else if ("latte".equals(type)){
coffee=new LatteCoffee(); //2. 创建一个拿铁咖啡
}else {
throw new RuntimeException("对不起,你点的咖啡没有");
}
return coffee;
}
}
咖啡店
package com.jsxs.pattern.factory.Single_Factory;
/**
* @Author Jsxs
* @Date 2023/4/17 13:55
* @PackageName:com.jsxs.pattern.factory.Single_Factory
* @ClassName: CoffeeStore
* @Description: TODO
* @Version 1.0
*/
public class CoffeeStore {
// 1.创建点餐业务
public Coffee orderCoffee(String type) {
// 2.具体商品我们去工厂获取
SimpleCoffeeFactory simpleCoffeeFactory = new SimpleCoffeeFactory();
Coffee coffee = simpleCoffeeFactory.createCoffee(type);
// 3.加配料
coffee.addMilk();
coffee.addSuger();
return coffee;
}
}
客户端
package com.jsxs.pattern.factory.Single_Factory;
/**
* @Author Jsxs
* @Date 2023/4/17 14:02
* @PackageName:com.jsxs.pattern.factory.Single_Factory
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
CoffeeStore coffeeStore = new CoffeeStore();
Coffee coffee = coffeeStore.orderCoffee("american");
System.out.println(coffee.getName());
}
}
简单工厂的优缺点
工厂处理创建对象的细节,一旦有了简单工厂类
、咖啡店类
中的订单方法
就变成此对象的客户,后期如果需要咖啡对象直接从该工厂种获取即可,这样也就接触了和Coffee实现类的耦合;同时又产生了新的耦合,咖啡店对象
和简单工厂类对象
的耦合,工厂对象
和商品对象
的耦合。
后期如果再加新产品的咖啡,我们势必需要简单工厂类的代码
,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只要修改该工厂类的代码,省去其他的修改操作。
优点: 封装了创建对象的过程,可以通过参数直接获取对象,把对象的创建和业务逻辑分开
,这样以后就避免了修改客户端代码,如果要实现新产品直接修改工厂类,而不要在源代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展
缺点:增加新产品的时候,依然需要修改工厂类,违反了开闭原则
- 简单工厂的扩展
静态工厂:
在开发过程中: 一部分人将工厂类创建对象的功能定义为静态的,这个就是静态工厂模式,也不是23种设计模式之一。
将这个方法更改成: 静态方法
package com.jsxs.pattern.factory.Single_Factory;
/**
* @Author Jsxs
* @Date 2023/4/17 13:56
* @PackageName:com.jsxs.pattern.factory.Single_Factory
* @ClassName: SimleCoffeeFactory
* @Description: TODO 简单工厂
* @Version 1.0
*/
public class SimpleCoffeeFactory {
public static Coffee createCoffee(String type){
Coffee coffee=null;
if ("american".equals(type)){
coffee=new AmericanCoffee(); // 1. 创建一个美食咖啡
}else if ("latte".equals(type)){
coffee=new LatteCoffee(); //2. 创建一个拿铁咖啡
}else {
throw new RuntimeException("对不起,你点的咖啡没有");
}
return coffee;
}
}
利用类名去创建对象
package com.jsxs.pattern.factory.Single_Factory;
/**
* @Author Jsxs
* @Date 2023/4/17 13:55
* @PackageName:com.jsxs.pattern.factory.Single_Factory
* @ClassName: CoffeeStore
* @Description: TODO
* @Version 1.0
*/
public class CoffeeStore {
// 1.创建点餐业务
public Coffee orderCoffee(String type) {
// 2.具体商品我们去工厂获取
Coffee coffee = SimpleCoffeeFactory.createCoffee(type);
// 3.加配料
coffee.addMilk();
coffee.addSuger();
return coffee;
}
}
(3).工厂方法模式
针对上面的优缺点: 使用工厂方法模式就可以完美的解决,完全遵循开闭原则。
定义一个用于创建对象的接口,让子类决定实列化哪个产品对象。工厂方法使一个产品类的实列化延迟到了其他工厂的子类。
- 结构
工厂方法模式的主要角色
- 抽象工厂:
提供一个创建产品的接口
,调用者通过它访问具体工厂的工厂方法来创建产品。 - 具体工厂: 主要是实现抽象工厂的抽象方法,完成具体产品的创建
- 抽象产品:
定义了产品的规范
,描述了产品的主要特性和功能。 - 具体产品:
实现了产品所定义的接口
,由具体的工厂来创建,它同具体工厂之间一一对应。
- 实现
抽象产品
package com.jsxs.pattern.factory.factory_method;
/**
* @Author Jsxs
* @Date 2023/4/17 13:54
* @PackageName:com.jsxs.pattern.factory.Single_Factory
* @ClassName: Coffee
* @Description: TODO
* @Version 1.0
*/
public abstract class Coffee {
// 0. 子类必须要实现抽象方法
public abstract String getName();
// 1.加糖
public void addSuger() {
System.out.println("加糖");
}
// 2.加奶
public void addMilk() {
System.out.println("加奶");
}
}
具体产品
package com.jsxs.pattern.factory.factory_method;
/**
* @Author Jsxs`在这里插入代码片`
* @Date 2023/4/17 12:51
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: LatteCoffe
* @Description: TODO 拿铁咖啡
* @Version 1.0
*/
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
package com.jsxs.pattern.factory.factory_method;
/**
* @Author Jsxs
* @Date 2023/4/17 12:50
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: AmericanCoffee
* @Description: TODO 美式咖啡
* @Version 1.0
*/
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
抽象工厂
package com.jsxs.pattern.factory.factory_method;
/**
* @Author Jsxs
* @Date 2023/4/17 14:52
* @PackageName:com.jsxs.pattern.factory.factory_method
* @ClassName: CoffeeFactory
* @Description: TODO 工厂接口
* @Version 1.0
*/
public interface CoffeeFactory {
Coffee createCoffee();
}
具体工厂
package com.jsxs.pattern.factory.factory_method;
/**
* @Author Jsxs
* @Date 2023/4/17 14:53
* @PackageName:com.jsxs.pattern.factory.factory_method
* @ClassName: AmericanCoffeeFactoryImp
* @Description: TODO 实现美式咖啡
* @Version 1.0
*/
public class AmericanCoffeeFactoryImp implements CoffeeFactory{
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
package com.jsxs.pattern.factory.factory_method;
/**
* @Author Jsxs
* @Date 2023/4/17 14:54
* @PackageName:com.jsxs.pattern.factory.factory_method
* @ClassName: LatteCoffeeFactoryImp
* @Description: TODO 拿铁咖啡实现
* @Version 1.0
*/
public class LatteCoffeeFactoryImp implements CoffeeFactory{
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
}
咖啡商店
package com.jsxs.pattern.factory.factory_method;
/**
* @Author Jsxs
* @Date 2023/4/17 14:50
* @PackageName:com.jsxs.pattern.factory.factory_method
* @ClassName: CoffeeStore
* @Description: TODO 咖啡店
* @Version 1.0
*/
public class CoffeeStore {
private CoffeeFactory coffeeFactory;
public void setCoffeeFactory(CoffeeFactory coffeeFactory) {
this.coffeeFactory = coffeeFactory;
}
// 点咖啡的功能
public Coffee orderCoffee() {
Coffee coffee = coffeeFactory.createCoffee();
coffee.addMilk();
coffee.addSuger();
return coffee;
}
}
测试类
package com.jsxs.pattern.factory.factory_method;
/**
* @Author Jsxs
* @Date 2023/4/17 14:57
* @PackageName:com.jsxs.pattern.factory.factory_method
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
CoffeeStore coffeeStore = new CoffeeStore();
// 点一杯美式咖啡
coffeeStore.setCoffeeFactory(new AmericanCoffeeFactoryImp());
// 订餐...
Coffee coffee = coffeeStore.orderCoffee();
System.out.println(coffee.getName());
}
}
- 优缺点
优点:
- 用户只需要知道具体工厂的名称就可以得到所要的产品,无须知道产品的具体创建过程。
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂,无需对原工厂进行任何修改,满足开闭原则。
缺点:
- 每增加一个产品就需要
增加一个具体产品类和一个对应的具体工厂
,增加了系统的复杂性。
4.抽象工厂模式 (咖啡-套餐)
前面介绍的工厂方法模式考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视。
这些工厂只生产同种类产品,同种类产品称为同等级产品.也就是说:“工厂方法模式只考虑生产同等级的产品,凡是现实生活中许多工厂是综合性的工厂,能生产多等级(种类)的产品,如电器厂及生产电视又生产洗衣机或空调,大学既有软件专业也有生物专业等。”
本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族。下图所示X轴是产品等级,也就是同一类产品(华为手机 苹果手机)。Y轴是产品族,也就是同一品牌的不同产品(华为手机 华为电脑),同一品牌的产品产自同一个工厂。
(1).概述
抽象工厂模式:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所需产品的具体类
就能得到同族的不同等级的产品模式结构
。
抽象工厂模式是 工厂方法模式的升级版本,工厂方法模式只生产同一个等级的产品,而抽象工厂模式可以生产多个等级的产品。
(2).结构
抽象工厂模式的主要角色:
- 抽象工厂:
提供一个创建产品的接口
,它包含了多个创建产品的方法,可以创建多个不同等级的产品 - 具体工厂: 主要是
实现抽象工厂中的多个抽象方法
,完成具体产品的创建 - 抽象产品:
定义了产品的规范
,描述了产品的主要特性和功能。 - 具体产品:
实现了产品所定义的接口
,由具体的工厂来创建,它同具体工厂之间一一对应。
(3).实现
现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯
等,钥匙按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸。其中拿铁咖啡、没事咖啡是一个产品等级
,都是咖啡;提拉米苏、抹茶慕斯
是一个产品等级;拿铁咖啡和提拉米苏
是同一产族(都是意大利风格),美式咖啡和抹茶慕斯
是同一产族(美式风格)。
抽象咖啡
package com.jsxs.pattern.factory.abstract_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 12:47
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: Coffe
* @Description: TODO 咖啡类
* @Version 1.0
*/
public abstract class Coffee {
// 0. 子类必须要实现抽象方法
public abstract String getName();
// 1.加糖
public void addSuger() {
System.out.println("加糖");
}
// 2.加奶
public void addMilk() {
System.out.println("加奶");
}
}
抽象甜品
package com.jsxs.pattern.factory.abstract_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 16:20
* @PackageName:com.jsxs.pattern.factory.abstract_factory
* @ClassName: Dessert
* @Description: TODO 甜品-抽象类
* @Version 1.0
*/
public abstract class Dessert {
public abstract void show();
}
具体咖啡
package com.jsxs.pattern.factory.abstract_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 12:51
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: LatteCoffe
* @Description: TODO 拿铁咖啡
* @Version 1.0
*/
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
package com.jsxs.pattern.factory.abstract_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 12:50
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: AmericanCoffee
* @Description: TODO 美式咖啡
* @Version 1.0
*/
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
具体甜品
package com.jsxs.pattern.factory.abstract_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 16:21
* @PackageName:com.jsxs.pattern.factory.abstract_factory
* @ClassName: Trmisu
* @Description: TODO 提拉米苏类
* @Version 1.0
*/
public class Tiramisu extends Dessert{
@Override
public void show() {
System.out.println("提拉米苏-意大利风格");
}
}
package com.jsxs.pattern.factory.abstract_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 16:23
* @PackageName:com.jsxs.pattern.factory.abstract_factory
* @ClassName: MatchaMousee
* @Description: TODO 抹茶慕斯类
* @Version 1.0
*/
public class MatchaMousee extends Dessert{
@Override
public void show() {
System.out.println("抹茶慕斯-美式风格");
}
}
抽象大工厂
package com.jsxs.pattern.factory.abstract_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 16:24
* @PackageName:com.jsxs.pattern.factory.abstract_factory
* @ClassName: DessertFactory
* @Description: TODO
* @Version 1.0
*/
public interface DessertFactory {
// 1.生产咖啡
Coffee createCoffee();
// 2.生产甜品
Dessert createDessert();
}
美式套餐
package com.jsxs.pattern.factory.abstract_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 16:25
* @PackageName:com.jsxs.pattern.factory.abstract_factory
* @ClassName: AmericanFactoryImp
* @Description: TODO 美式风格工厂
* @Version 1.0
*/
public class AmericanFactoryImp implements DessertFactory{
@Override
public Coffee createCoffee() {
return new AmericanCoffee(); // 美式咖啡
}
@Override
public Dessert createDessert() {
return new MatchaMousee(); //抹茶慕斯
}
}
意大利套餐
package com.jsxs.pattern.factory.abstract_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 16:27
* @PackageName:com.jsxs.pattern.factory.abstract_factory
* @ClassName: LatteFactoryImp
* @Description: TODO
* @Version 1.0
*/
public class LatteFactoryImp implements DessertFactory{
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
@Override
public Dessert createDessert() {
return new Tiramisu(); //提拉米苏
}
}
咖啡店
package com.jsxs.pattern.factory.abstract_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 16:28
* @PackageName:com.jsxs.pattern.factory.abstract_factory
* @ClassName: CofeStore
* @Description: TODO
* @Version 1.0
*/
public class CoffeeStore {
private DessertFactory dessertFactory;
public void setDessertFactory(DessertFactory dessertFactory) {
this.dessertFactory = dessertFactory;
}
public void Order(){
Coffee coffee = dessertFactory.createCoffee();
Dessert dessert = dessertFactory.createDessert();
System.out.println(coffee.getName());
coffee.addMilk();
coffee.addSuger();
dessert.show();
}
}
客户
package com.jsxs.pattern.factory.abstract_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 16:28
* @PackageName:com.jsxs.pattern.factory.abstract_factory
* @ClassName: CofeStore
* @Description: TODO
* @Version 1.0
*/
public class CoffeeStore {
private DessertFactory dessertFactory;
public void setDessertFactory(DessertFactory dessertFactory) {
this.dessertFactory = dessertFactory;
}
public void Order(){
Coffee coffee = dessertFactory.createCoffee();
Dessert dessert = dessertFactory.createDessert();
System.out.println(coffee.getName());
coffee.addMilk();
coffee.addSuger();
dessert.show();
}
}
如果我们需要再添加套餐(产品族)的话,只需要再加一个对应的工厂类即可,不需要再修改其他的类。
(4).优缺点
优点:
当一个产品族(套餐)中的多个对象被设计成一起工作的时候,它能保证客户端始终只使用同一个产品族中的对象 (套餐的对象必定一定)。
缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改
。eg:美式套餐中我们赠送一个意大利风格的甜品。(不能赠送或新增单个产品,可以新增一整个套餐)
(5).使用场景
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视、洗衣机、空调等。
- 系统中有多个产品族。但每次只使用其中的某一族产品。如有人只喜欢穿一个指定的套餐(固定的产品族)
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实列的创建细节和内部结构。如: 输入法换皮肤,一整套一整套的换。生成不同的操作系统程序。
(6).JDK源码解析- Collection.iterator方法
使用迭代器遍历集合,获取集合中的元素。而单列集合获取迭代器的方法就使用到了工厂方法模式。
5.工厂模式的扩展💔 -(这个不属于23种)
简单工厂+配置文件解除耦合
可以通过工厂模式+配置文件
的方式解除工厂对象和产品对象
的耦合。在工厂中加载配置文件中的全类名,并创建对象和存储,客户端如果需要对象对象,直接进行获取即可。
beans.properties: 配置文件
american=com.jsxs.pattern.factory.config_factory.AmericanCoffee
latte=com.jsxs.pattern.factory.config_factory.LatteCoffee
咖啡抽象类
package com.jsxs.pattern.factory.config_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 13:54
* @PackageName:com.jsxs.pattern.factory.Single_Factory
* @ClassName: Coffee
* @Description: TODO
* @Version 1.0
*/
public abstract class Coffee {
// 0. 子类必须要实现抽象方法
public abstract String getName();
// 1.加糖
public void addSuger() {
System.out.println("加糖");
}
// 2.加奶
public void addMilk() {
System.out.println("加奶");
}
}
具体实现
package com.jsxs.pattern.factory.config_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 12:50
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: AmericanCoffee
* @Description: TODO 美式咖啡
* @Version 1.0
*/
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "美式咖啡";
}
}
package com.jsxs.pattern.factory.config_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 12:51
* @PackageName:com.jsxs.pattern.factory.before
* @ClassName: LatteCoffe
* @Description: TODO 拿铁咖啡
* @Version 1.0
*/
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "拿铁咖啡";
}
}
咖啡工厂类
package com.jsxs.pattern.factory.config_factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;
/**
* @Author Jsxs
* @Date 2023/4/17 17:50
* @PackageName:com.jsxs.pattern.factory.config_factory
* @ClassName: CoffeeFactory
* @Description: TODO 咖啡工厂类
* @Version 1.0
*/
public class CoffeeFactory {
// 1. 加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储
//2.定义容器对象存储咖啡对象
private static HashMap<String,Coffee> map=new HashMap<String,Coffee>();
//3.加载配置文件,只需要加载一次
static {
//3.1创建Properties对象
Properties properties = new Properties();
//3.2调用load方法进行加载
InputStream in = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties"); //获取输入流、读取哪个文件
try {
properties.load(in); //这个参数是: 输入流。加载完毕之后,这个properties就已经存在数据了。
//3.3从properties集合中获取全类名创建对象
Set<Object> keys = properties.keySet(); // 获取配置文件中全部的键
for (Object key : keys) {
String ClassName = properties.getProperty((String) key); // 通过获取到配置文件中的键,我们得到值。
//3.4 通过反射去创建对象
Class aClass = Class.forName(ClassName); // 通过加载类全民获取字节码对象
Coffee coffee = (Coffee) aClass.newInstance(); //通过无参构造创建对象
// 将名称和对象那个存储到我们自定义的容器中
map.put((String) key,coffee);
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 根据名称获取对象
public static Coffee createCoffee(String type){
return map.get(type);
}
}
package com.jsxs.pattern.factory.config_factory;
/**
* @Author Jsxs
* @Date 2023/4/17 18:09
* @PackageName:com.jsxs.pattern.factory.config_factory
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
Coffee coffee = CoffeeFactory.createCoffee("latte");
System.out.println(coffee.getName());
}
}
静态成员变量用来存储创建的对象(键存储的是名称、值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需执行一次
。
6.建造者模式
(1).概述
将一个复杂对象的构建
与便是分离
,使得同样的构建过程可以创建不同的表示。
将主机拆开就是分离,将分离的主机合起来就是构建。在一样的构建方法中我们更换掉不同品牌的相同组件就是创建了不同的主机
- 分离了部件的构造(由Builder来负责)和装配(由Director负责)。从而可以构建出复杂的对象。这个模式适用于:
某个对象的构建过程复杂的情况
。 - 由于实现了构建和装配的解耦。
不同的构建器,相同的装配,也可以做出不同的对象
;相同的构建器,不同的装配顺序也可以做出不同的对象
。也就是实现了构建算法。装配算法的解耦,实现了更好的复用。 建造者模式可以将部件和其组装过程分开,一步步创建一个复杂的对象
。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
(2).结构
建造者(Builder)模式包含如下角色
- 抽象建造者类(Builder): 这个接口要实现复杂对象的那些部分的创建,并不涉及具体的对象部件的创建。
- 具体建造者类(ConCreteBuilder): 实现Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实列。
- 产品类(Product): 要创建的复杂对象。
- 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在知道者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
(3).实列
创建共享单车
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。
这里的Bike是产品,包含车架,车座等组件;Builder是抽象建造者,摩拜单车公司和ofo公司是具体的创建者;Direct是指挥者。
protected:访问权限->本类以及本类的子类。创建对象的i两种方式->有参创造和无参创造。无参创造会回溯到祖宗类Object类,所以不管用哪种方式->我们尽量带上无参构造
- 指挥者和抽象类分离
产品
package com.jsxs.pattern.build.demo1;
/**
* @Author Jsxs
* @Date 2023/4/18 15:40
* @PackageName:com.jsxs.pattern.build.demo1
* @ClassName: Bike
* @Description: TODO 具体的产品对象
* @Version 1.0
*/
public class Bike {
private String frame; //车架
private String seat; //车座
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
}
建造者接口
package com.jsxs.pattern.build.demo1;
/**
* @Author Jsxs
* @Date 2023/4/18 15:42
* @PackageName:com.jsxs.pattern.build.demo1
* @ClassName: Builder
* @Description: TODO
* @Version 1.0
*/
public abstract class Builder {
// 1.声明Bike类型的变量
protected Bike bike=new Bike(); // 设计图或者概念-还没组装
public abstract void buildFame(); //车架
public abstract void buildSeat(); //车座
public abstract Bike createBike(); //构建具体自行车的方法
}
具体建造者
package com.jsxs.pattern.build.demo1;
/**
* @Author Jsxs
* @Date 2023/4/18 15:50
* @PackageName:com.jsxs.pattern.build.demo1
* @ClassName: MobileCompany
* @Description: TODO 摩拜公司
* @Version 1.0
*/
public class MobileCompany extends Builder{
@Override
public void buildFame() {
bike.setFrame("摩拜-车架");
}
@Override
public void buildSeat() {
bike.setFrame("摩拜-车座");
}
@Override
public Bike createBike() {
return bike;
}
}
package com.jsxs.pattern.build.demo1;
/**
* @Author Jsxs
* @Date 2023/4/18 15:52
* @PackageName:com.jsxs.pattern.build.demo1
* @ClassName: OfoCompany
* @Description: TODO
* @Version 1.0
*/
public class OfoCompany extends Builder{
@Override
public void buildFame() {
bike.setFrame("off-车架");
}
@Override
public void buildSeat() {
bike.setSeat("off-车座");
}
@Override
public Bike createBike() {
return bike;
}
}
指挥者
package com.jsxs.pattern.build.demo1;
/**
* @Author Jsxs
* @Date 2023/4/18 15:55
* @PackageName:com.jsxs.pattern.build.demo1
* @ClassName: Director
* @Description: TODO 总工程师-指挥者
* @Version 1.0
*/
public class Director {
// 声明Builder类型的变量
private Builder builder;
// 声明有参构造创建对象
public Director(Builder builder) {
this.builder = builder;
}
// 组装自行车的功能
public Bike construct(){ //构造自行车
this.builder.buildFame();
this.builder.buildSeat();
return builder.createBike();
}
}
客户端
package com.jsxs.pattern.build.demo1;
/**
* @Author Jsxs
* @Date 2023/4/18 16:32
* @PackageName:com.jsxs.pattern.build.demo1
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
Director director = new Director(new MobileCompany());
// 指挥者组装对象
Bike bike = director.construct();
System.out.println(bike.getFrame());
}
}
指挥者类Director在建造者模式中很重要,它用于知道具体构建这如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有一些情况需要简化系统结构,可以把指挥者类和抽象类建造者进行结合
- 抽象类和建造者不分离
自行车产品
package com.jsxs.pattern.build.demo1;
/**
* @Author Jsxs
* @Date 2023/4/18 15:40
* @PackageName:com.jsxs.pattern.build.demo1
* @ClassName: Bike
* @Description: TODO 具体的产品对象
* @Version 1.0
*/
public class Bike {
private String frame; //车架
private String seat; //车座
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
}
抽象创建者--- 结合
package com.jsxs.pattern.build.demo1;
/**
* @Author Jsxs
* @Date 2023/4/18 15:42
* @PackageName:com.jsxs.pattern.build.demo1
* @ClassName: Builder
* @Description: TODO
* @Version 1.0
*/
public abstract class Builder {
// 1.声明Bike类型的变量
protected Bike bike=new Bike(); // 设计图或者概念-还没组装
public abstract void buildFame(); //车架
public abstract void buildSeat(); //车座
public abstract Bike createBike(); //构建具体自行车的方法
// 组装自行车的功能
public Bike construct(){ //构造自行车
this.buildFame();
this.buildSeat();
return this.createBike();
}
}
具体建造者
package com.jsxs.pattern.build.demo1;
/**
* @Author Jsxs
* @Date 2023/4/18 15:50
* @PackageName:com.jsxs.pattern.build.demo1
* @ClassName: MobileCompany
* @Description: TODO 摩拜公司
* @Version 1.0
*/
public class MobileCompany extends Builder{
@Override
public void buildFame() {
bike.setFrame("摩拜-车架");
}
@Override
public void buildSeat() {
bike.setFrame("摩拜-车座");
}
@Override
public Bike createBike() {
return bike;
}
@Override
public Bike construct() {
return super.construct();
}
}
package com.jsxs.pattern.build.demo1;
/**
* @Author Jsxs
* @Date 2023/4/18 15:52
* @PackageName:com.jsxs.pattern.build.demo1
* @ClassName: OfoCompany
* @Description: TODO
* @Version 1.0
*/
public class OfoCompany extends Builder{
@Override
public void buildFame() {
bike.setFrame("off-车架");
}
@Override
public void buildSeat() {
bike.setSeat("off-车座");
}
@Override
public Bike createBike() {
return bike;
}
@Override
public Bike construct() {
return super.construct();
}
}
客户端
package com.jsxs.pattern.build.demo1;
/**
* @Author Jsxs
* @Date 2023/4/18 16:32
* @PackageName:com.jsxs.pattern.build.demo1
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
MobileCompany mobileCompany = new MobileCompany();
Bike bike = mobileCompany.construct();
System.out.println(bike.getFrame());
}
}
说明: 这样做确实简化了系统结构,让生产者来构建自行车,但同时也加重了抽象建造者类的职责,也不是太符合单一职责的原则。如果构建自行车contrstuct()这个方法过于复杂,建议还是封装到指挥者中。
(4).优缺点
优点:
-
建造者模式的封装性很好
。使用建造者模式可以有效地封装变化,在使用建造者模式的场景中,一般产品类和建造这类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。 -
在建造者模式中,客户端不必知道产品内部组成的细节,
将产品与产品的创建过程解耦
,使得相同的创建过程可以创建不同的产品对象。 -
可以更加精密地控制产品地创建过程
。将复杂产品地创建步骤分解在不同地方法中,使得创建过程更加清晰,也是更方便使用程序来控制创建过程。 -
建造者模式很容易进行扩展,通过一个新的建造者类就可以完成,基本上不用修改之前已经测试通过地代码,因此也就不会对原有功能引入风险,符合开闭原则。
缺点:
建造者模式所创建地产品一般具有较多地共同点,其组成部分很相似,如果产品之间地差异性很大,则不适用建造者模式
,因此其实用范围收到一定限制。
(5).使用场景
建造者模式创建地是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在一下场合使用:
- 创建的对象比较复杂,有多个部件构成,
各部件面临着复杂的变化,但构件间的建造顺序是稳定的
。 - 常见复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
(6).模式扩展
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实列,代码可读性会非常差,而且很容易引入错误
,此时就应该利用建造者模式进行重构。
package com.jsxs.pattern.build.demo2;
/**
* @Author Jsxs
* @Date 2023/4/18 17:18
* @PackageName:com.jsxs.pattern.build.demo2
* @ClassName: Phone
* @Description: TODO
* @Version 1.0
*/
public class Phone {
private String cpu;
private String Screen;
private String money;
private String mainBoard;
// 私有构造方法
private Phone(Builder builder) {
this.cpu = builder.cpu;
this.Screen = builder.Screen;
this.money = builder.money;
this.mainBoard = builder.mainBoard;
}
// 静态内部类
public static final class Builder{
private String cpu;
private String Screen;
private String money;
private String mainBoard;
public Builder cpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder screen(String screen) {
this.Screen = screen;
return this;
}
public Builder money(String money) {
this.money = money;
return this;
}
public Builder mainBoard(String mainBoard) {
this.mainBoard = mainBoard;
return this;
}
public Phone build(){
return new Phone(this); // 传递这个内部类
}
@Override
public String toString() {
return "Builder{" +
"cpu='" + cpu + '\'' +
", Screen='" + Screen + '\'' +
", money='" + money + '\'' +
", mainBoard='" + mainBoard + '\'' +
'}';
}
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", Screen='" + Screen + '\'' +
", money='" + money + '\'' +
", mainBoard='" + mainBoard + '\'' +
'}';
}
}
package com.jsxs.pattern.build.demo2;
/**
* @Author Jsxs
* @Date 2023/4/18 17:26
* @PackageName:com.jsxs.pattern.build.demo2
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 利用建造者创建对象 ---- 因为这些方法的返回值对象都是当前对象,所以可以使用链式编程
Phone.Builder builder = new Phone.Builder()
.cpu("inter")
.screen("三星")
.mainBoard("苹果")
.money("1200");
Phone phone = builder.build();
System.out.println(phone);
}
}
重构后的代码在使用起来更方便,某种程度上可以提高开发效率。从软件设计,对程序员的要求比较高。
7.创建者模式对比
(1).工厂方法模式VS建造者模式
工厂方法模式注重的是整体对象的创建方式(结果)
;而建造者模式注重的是部件构建的过程(过程)
,意在通过一步步地精确构造出一个复杂地对象。
我们举个简单的列子来说明两者地差异: 如要制造一个超人,如果使用工厂方法模式,直接生产出来地就是一个力大无穷、能够飞翔、内裤外穿地超人;而如果使用建造者模式,则需要组装收、头、脚、驱赶,然后再把内裤外穿,于是一个超人就诞生了。
(2).抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族地创建
,一个产品家族是这样地一系列产品;具有不同分类欸都地产品组合,采用抽象工厂模式是不需要关心构建过程,只关心什么产品由什么工厂生产
即可。
建造者模式则是要求按照指定地蓝图建造产品,他的主要目的是通过组件装零配件而生产地一个新产品
。
如果将抽象工厂模式看成汽车配件生产工厂
,生产一个产品族地产品.那么建造者模式就是一个汽车组装工厂
,通过对部件地组装可以返回一量完整的汽车。
(三)、结构性模式(7种)
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构性模式
和对象结构型模式
,前者采用继承机制来组织接口和类,后者采用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系的耦合度低,满足合成复用原则,所以对象结构型模式
比类结构型模式
具有更大的灵活性。
1.代理模式
(1).概述
由于某些原因需要给某个对象(结婚对象)提供一个代理(婚介)以控制对该对象的访问(结婚仪式)。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介
。
eg: 结婚: 找婚介。
Java中的代理按照代理类生成时机不同分为静态代理
和动态代理
。静态代理类在编译期就生成
,而动态代理类则是在Java运行时动态生成
。动态代理又有JDK和GCLib代理两种。
(2).结构
代理模式分为三种角色:
- 抽象主题类: 通过
接口或抽象类
声明真实主题和代理对象实现的业务方法(结婚)。 - 真实主题类:实现了抽象主题的
具体业务
,是代理对象所代表的真实对象,是最终要引入的对象(新人)。 - 代理类: 提供了与真实主题相同的接口,其内部含有对真实主题的引用,他可以访问、控制或扩展真实主题的功能(婚介所)。
(3).静态代理
我们通过案列来感觉一下静态代理。
eg: 火车站买票
如果要买火车票的话,我们需要去火车站买票,排队等一系列的操作,显然比较麻烦。而火车站多个地方都有代售点,我们去代售点买火车票就方便很多了。这个列子其实就是典型的代理模式,火车站是目标对象
,代售点是代理对象
。
抽象主题类
package com.jsxs.structure.proxy.static_proxy;
/**
* @Author Jsxs
* @Date 2023/4/19 13:40
* @PackageName:com.jsxs.structure.proxy.static_proxy
* @ClassName: SellTickets
* @Description: TODO 卖火车票的接口
* @Version 1.0
*/
public interface SellTickets {
void sell();
}
真实主题类
package com.jsxs.structure.proxy.static_proxy;
/**
* @Author Jsxs
* @Date 2023/4/19 13:41
* @PackageName:com.jsxs.structure.proxy.static_proxy
* @ClassName: TrainStation
* @Description: TODO 火车站类
* @Version 1.0
*/
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("在火车站进行卖票业务");
}
}
代理类
package com.jsxs.structure.proxy.static_proxy;
/**
* @Author Jsxs
* @Date 2023/4/19 13:42
* @PackageName:com.jsxs.structure.proxy.static_proxy
* @ClassName: ProxyPoint
* @Description: TODO 代售点
* @Version 1.0
*/
public class ProxyPoint implements SellTickets{
private TrainStation trainStation=new TrainStation();
@Override
public void sell() {
System.out.println("代售点收取服务费帮火车站卖票,票的来源是火车站");
trainStation.sell();
}
}
从上面的代码中我们可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取一些服务费用)
(4).JDK动态代理
我们使用上面的案例进行动态代理,先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy
,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法来获取对象
。
抽象主题类
package com.jsxs.structure.proxy.jdk_proxy;
/**
* @Author Jsxs
* @Date 2023/4/19 13:40
* @PackageName:com.jsxs.structure.proxy.static_proxy
* @ClassName: SellTickets
* @Description: TODO 卖火车票的接口
* @Version 1.0
*/
public interface SellTickets {
void sell();
}
真实主题类
package com.jsxs.structure.proxy.jdk_proxy;
/**
* @Author Jsxs
* @Date 2023/4/19 13:41
* @PackageName:com.jsxs.structure.proxy.static_proxy
* @ClassName: TrainStation
* @Description: TODO 火车站类
* @Version 1.0
*/
public class TrainStation implements SellTickets {
@Override
public void sell() {
System.out.println("在火车站进行卖票业务");
}
}
JDK动态代理类
package com.jsxs.structure.proxy.jdk_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Author Jsxs
* @Date 2023/4/19 14:04
* @PackageName:com.jsxs.structure.proxy.jdk_proxy
* @ClassName: ProxyFactory
* @Description: TODO 获取代理对象的工厂类
* @Version 1.0
*/
public class ProxyFactory {
// 1.声明目标对象
private TrainStation trainStation=new TrainStation();
// 2.获取代理对象的方法
public SellTickets getProxyObject(){
// 返回代理对象即可
/**
* 1.类加载器: 用于加载代理类。可以通过目标对象获取类加载器
* 2.代理类实现的接口的字节码对象
* 3. 代理对象的调用处理程序:
*/
SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(trainStation.getClass().getClassLoader(), trainStation.getClass().getInterfaces(), new InvocationHandler() {
/**
*
* @param proxy 代理对象: 和proxyObject对象是同一个对象,在invoke方法中基本不用
* @param method 对接口中的方法进行封装的method对象
* @param args 调用方法的参数
* @return 方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代售点收取一定的服务费用(JDK动态代理)...");
Object invoke = method.invoke(trainStation, args);
return invoke;
}
});
return proxyObject;
}
}
客户端
package com.jsxs.structure.proxy.jdk_proxy;
/**
* @Author Jsxs
* @Date 2023/4/19 14:18
* @PackageName:com.jsxs.structure.proxy.jdk_proxy
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 1.创建代理类工厂
ProxyFactory proxyFactory = new ProxyFactory();
// 2.获取代理对象
SellTickets proxyObject = proxyFactory.getProxyObject();
// 3.调用售卖的方法
proxyObject.sell();
}
}
使用动态代理,我们思考下面问题
- Proxy是代理类嘛?
ProxyFactory不是代理模式中所说的代理类
,而代理类是程序在运行过程中动态的在内存中生成的类。通过阿里巴巴开源的Java诊断工具(Arthas)查看代理类的结构。
(5).GCLIB动态代理
同样是上面的案列,我们再次使用GCLIB代理实现。
如果没有定义SellTickets接口,只定义了TrainStation(火车站)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。
GCLIB是一个功能强大,高i性能的代码生成包。它没有实现接口的类提供代理类,为JDK的动态代理做了很好的补充。
GCLIB是第三方提供的包,所以需要引入jar包的坐标。
<!-- https://mvnrepository/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
火车站售票业务
package com.jsxs.structure.proxy.cglib_proxy;
/**
* @Author Jsxs
* @Date 2023/4/19 13:41
* @PackageName:com.jsxs.structure.proxy.static_proxy
* @ClassName: TrainStation
* @Description: TODO 火车站类
* @Version 1.0
*/
public class TrainStation {
public void sell() {
System.out.println("在火车站进行卖票业务");
}
}
代理工厂
package com.jsxs.structure.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;
/**
* @Author Jsxs
* @Date 2023/4/19 15:08
* @PackageName:com.jsxs.structure.proxy.cglib_proxy
* @ClassName: ProxyFactory
* @Description: TODO
* @Version 1.0
*/
public class ProxyFactory implements MethodInterceptor {
// 1.声明目标对象
private TrainStation trainStation= new TrainStation();
// 2.获取代理类
public TrainStation getTrainStation(){
// 创建Enhancer对象,雷士与JDK中的Proxy类
Enhancer enhancer = new Enhancer();
// 设置父类的字节码对象
enhancer.setSuperclass(trainStation.getClass());
// 设置回调函数
enhancer.setCallback(this); //MethodInterceptor父类的子类也就是 ProxyFactory;
// 创建代理对象
TrainStation proxyObject =(TrainStation) enhancer.create();
return proxyObject;
}
/**
*
* @param o
* @param method 方法
* @param objects 参数
* @param methodProxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代售点收取费用");
Object invoke = method.invoke(trainStation, objects);
return invoke;
}
}
客户端
package com.jsxs.structure.proxy.cglib_proxy;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Author Jsxs
* @Date 2023/4/19 15:15
* @PackageName:com.jsxs.structure.proxy.cglib_proxy
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
// 获取代理对象
TrainStation proxyObject = proxyFactory.getTrainStation();
// 对象调离对象中的卖票方法
proxyObject.sell();
}
}
(6).三种代理的对比
- JDK代理和GCLIB代理
使用GCLIB实现动态代理,GCLIB底层采用ASM字节码生成框架
,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,GCLIB不能对声明finnal的类或者方法进行动态代理,因为GCLIB原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于GCLIB代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比GCLIB代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于GCLIB代理,所以如果有接口使用JDK动态代理,如果没有接口使用GCLIB代理
。
- 动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转义到调用处理器一个几种的方法中处理。这样,在接口方法数量比较多的时候,我们可以更加灵活处理,而不需要像静态代理那样每一个方法都需要中转,
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现这个问题。
(7).代理模式的优缺点
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用。
- 代理对象可以扩展目标对象的功能
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。
缺点:
- 增加了系统的复杂度
(8).使用场景
- 远程代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务的功能,而不必过多关系通信的细节。 - 防护墙代理(VPN)
当你将浏览器配置成功使用代理功能时,防火墙就将我们的浏览器的请求转发给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。 - 保护代理
控制对一个对象的访问,如果需要可以给不同的用户提供不同级别的使用权限。
2.适配器模式
(1).概述
如果去欧洲国家去旅游的话,它们的插座如下图最左边,是欧洲的标准。为我们使用的插头如下图最右边的。因此我们的笔记本电脑,收集在当地不能直接充电的,所以需要一个插座转换器,转换器第一面插入地面的插座,第二面供我们充电,这样使得我们的插头就能在当地使用。生活中这样的列子很多,收集充电器(将220v转换为5v的电压),读卡器等,其实就是使用到了适配器模式。
定义:将一个类的接口转换为客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能够一起工作。适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解 现有组件库中的相关组件的内部结构,所以应用相对较少。
(2).结构
适配器模式包含以下主要角色:
目标接口
: 当前系统业务所期待的接口,它可以是抽象类或者接口(中国接口
)适配者类
: 它是被访问和适配的现存组件库中的组件接口(欧洲接口
)适配器类
: 他是一个转换器,通过继承或引用适配者的对象,把适配者接口转换为目标接口,让客户按照目标接口的格式访问适配者。(转换器
)
(3).类适配器模式实现
实现方式: 定义一个适配器类(转换器)来实现当前系统的业务接口(中国),同时又继承现有组件库中已经存在的组件(欧洲)
。
eg: 读卡器
现在已经有一台电脑只能读取SD卡(目标接口),而要读取TE卡中的内容就需要使用到适配器模式。创建一个读卡器,将TE卡中的内容读取出来。
目标接口(中国)
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:47
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: SDScard
* @Description: TODO 目标接口 (SD卡)
* @Version 1.0
*/
public interface SDCard {
// 从SD卡中读取数据
String readSD();
//往SD卡中写数据
void writeSD(String msg);
}
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:49
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: SDCardIml
* @Description: TODO
* @Version 1.0
*/
public class SDCardIml implements SDCard{
@Override
public String readSD() {
String msg="SDCard red msg : hello word";
return msg;
}
@Override
public void writeSD(String msg) {
System.out.println("SDCard write msg:"+msg);
}
}
适配者类()欧洲
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:42
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: TFCard
* @Description: TODO 适配者类的接口
* @Version 1.0
*/
public interface TFCard {
// 从TF卡中读取数据
String readTF();
// 网TF卡中写数据
void writeTF(String msg);
}
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:44
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: TFCardImi
* @Description: TODO 适配者类
* @Version 1.0
*/
public class TFCardImi implements TFCard{
@Override
public String readTF() {
String msg="TFCARD READ MSG: HELLO WORLD";
return msg;
}
@Override
public void writeTF(String msg) {
System.out.println("TFCARD WRITE MSG:"+msg);
}
}
计算机类
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:54
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: Computer
* @Description: TODO 计算机类
* @Version 1.0
*/
public class Computer {
// 从SD卡中读取数据
public String readSD(SDCard sdCard){
if (sdCard==null){
throw new RuntimeException("SDCard is not null");
}
return sdCard.readSD();
}
}
适配器类()转换器
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:59
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: SDAdapterTF
* @Description: TODO 适配器类 (转换器)
* @Version 1.0
*/
public class SDAdapterTF extends TFCardImi implements SDCard{ //实现当前业务的接口、同时继承已经存在的的组件。
@Override
public String readSD() {
return this.readTF();
}
@Override
public void writeSD(String msg) {
this.writeTF(msg);
}
}
测试类
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:56
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建计算机对象
Computer computer = new Computer();
//读取SD卡中的数据
String s = computer.readSD(new SDCardIml());
System.out.println(s);
System.out.println("--------------------------");
// 使用该计算机读取TF卡中的数据
// 定义适配器类
SDAdapterTF sdAdapterTF = new SDAdapterTF();
// 适配器可以访问TF卡
String s1 = sdAdapterTF.readTF();
System.out.println(s1);
}
}
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可以,反之不可
(4).对象适配器模式
实现方式: 对象适配器模式可采用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
目标接口(中国)
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:47
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: SDScard
* @Description: TODO 目标接口 (SD卡)
* @Version 1.0
*/
public interface SDCard {
// 从SD卡中读取数据
String readSD();
//往SD卡中写数据
void writeSD(String msg);
}
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:49
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: SDCardIml
* @Description: TODO
* @Version 1.0
*/
public class SDCardIml implements SDCard{
@Override
public String readSD() {
String msg="SDCard red msg : hello word";
return msg;
}
@Override
public void writeSD(String msg) {
System.out.println("SDCard write msg:"+msg);
}
}
适配者类()欧洲
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:42
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: TFCard
* @Description: TODO 适配者类的接口
* @Version 1.0
*/
public interface TFCard {
// 从TF卡中读取数据
String readTF();
// 网TF卡中写数据
void writeTF(String msg);
}
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:44
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: TFCardImi
* @Description: TODO 适配者类
* @Version 1.0
*/
public class TFCardImi implements TFCard{
@Override
public String readTF() {
String msg="TFCARD READ MSG: HELLO WORLD";
return msg;
}
@Override
public void writeTF(String msg) {
System.out.println("TFCARD WRITE MSG:"+msg);
}
}
计算机类
package com.jsxs.structure.adapter.class_adapter;
/**
* @Author Jsxs
* @Date 2023/4/20 18:54
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: Computer
* @Description: TODO 计算机类
* @Version 1.0
*/
public class Computer {
// 从SD卡中读取数据
public String readSD(SDCard sdCard){
if (sdCard==null){
throw new RuntimeException("SDCard is not null");
}
return sdCard.readSD();
}
}
这里修改成聚合
package com.jsxs.structure.adapter.object_adpater;
/**
* @Author Jsxs
* @Date 2023/4/20 18:59
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: SDAdapterTF
* @Description: TODO 适配器类 (转换器)
* @Version 1.0
*/
public class SDAdapterTF implements SDCard { //实现当前业务的接口、同时继承已经存在的的组件。
// 聚合适配者类
private TFCard tfCard;
// 设置有参构造方法,目的是使用哪个实现类
public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}
@Override
public String readSD() {
return tfCard.readTF();
}
@Override
public void writeSD(String msg) {
tfCard.writeTF(msg);
}
}
package com.jsxs.structure.adapter.object_adpater;
/**
* @Author Jsxs
* @Date 2023/4/20 18:56
* @PackageName:com.jsxs.structure.adapter.class_adapter
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建计算机对象
Computer computer = new Computer();
//读取SD卡中的数据
String s = computer.readSD(new SDCardIml());
System.out.println(s);
System.out.println("--------------------------");
// 使用该计算机读取TF卡中的数据
// 定义适配器类
SDAdapterTF sdAdapterTF = new SDAdapterTF(new TFCardImi());
String s1 = sdAdapterTF.readSD();
System.out.println(s1);
}
}
以上的方法实现了接口复用。聚合
注意: 还有一个适配器模式是接口适配器模式。当不希望实现一个接口中的所有方法,可以抽像一个Adapter,实现所有方法。而我们只需要继承该抽象类即可。
(5).应用场景
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同
(6).JDK源码解析
Reader
(字符流)、InputStream
(字节流)的适配使用的是InputStreamReader。
InputStreamReader继承自Java.io包中的Reader,对其中的抽象方法给出了实践。
3.桥接模式
(1).概述
现在有一个需求,需要创建不同的图形,并且每个图形都有可能会有不同的颜色。我们可以利用继承的方式来设计类的关系。
我们可以发现有很多的类,假如我们再增加一种颜色或一个形状,就需要创建更多的类。
试想,在一个多种可能会变化的维度的系统中,用继承方式会造成类爆炸
,扩展起来不灵活。每次在一个维度上新增一个具体实现都要增加多个子类,为了更加灵活的设计系统,我们此时可以考虑使用桥接模式。
定义
:
将抽象与实现分离,使他们可以独立变化。他是用组合关系代理继承关系来实现,从而降低了抽象和实现这两个可唯独的耦合度
。
(2).结构
桥接模式包含以下角色:
抽象化角色
: 定义抽象类,并包含一个对实现话对象的引用扩展抽象化角色
: 是抽象化角色的子类,实现父类
中的业务方法,并通过组合关系调用实现化角色中的业务方法。实现化角色
: 定义实现化角色的接口,供扩展抽象化角色调用具体实现化角色
: 给出实现化角色接口的具体实现。
(3).案列
eg: 视频播放器
需要开发一个跨平台视频播放器,可以在不同操作系统平台(如windows,mac,Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含两个维度,适合使用桥接模式。
实现化角色
package com.jsxs.structure.Bride;
/**
* @Author Jsxs
* @Date 2023/4/21 20:43
* @PackageName:com.jsxs.structure
* @ClassName: Bride
* @Description: TODO 实现化角色
* @Version 1.0
*/
public interface VideoFile {
// 解码
void decode(String fileName);
}
具体的实现化角色
package com.jsxs.structure.Bride;
/**
* @Author Jsxs
* @Date 2023/4/21 20:44
* @PackageName:com.jsxs.structure.Bride
* @ClassName: AVI
* @Description: TODO 具体的实现化角色
* @Version 1.0
*/
public class AVI implements VideoFile{
@Override
public void decode(String fileName) {
System.out.println("AVI视频文件:"+fileName);
}
}
package com.jsxs.structure.Bride;
/**
* @Author Jsxs
* @Date 2023/4/21 20:45
* @PackageName:com.jsxs.structure.Bride
* @ClassName: MP4
* @Description: TODO 具体的实现化角色
* @Version 1.0
*/
public class MP4 implements VideoFile{
@Override
public void decode(String fileName) {
System.out.println("MP4:"+fileName);
}
}
抽象化角色
package com.jsxs.structure.Bride;
/**
* @Author Jsxs
* @Date 2023/4/21 20:46
* @PackageName:com.jsxs.structure.Bride
* @ClassName: OS
* @Description: TODO 抽象化角色
* @Version 1.0
*/
public abstract class OS {
// 聚合: 设置的类型为保护类型目的是为了让子类访问。
protected VideoFile videoFile;
// 因为我们设置有参构造函数,所以子类必须要赋值
public OS(VideoFile videoFile) {
this.videoFile = videoFile;
}
public abstract void play(String fileName);
}
扩展抽象化角色
package com.jsxs.structure.Bride;
/**
* @Author Jsxs
* @Date 2023/4/21 21:00
* @PackageName:com.jsxs.structure.Bride
* @ClassName: Windows
* @Description: TODO 扩展抽象化角色
* @Version 1.0
*/
public class Windows extends OS{
public Windows(VideoFile videoFile) {
super(videoFile);
}
@Override
public void play(String fileName) {
videoFile.decode(fileName);
}
}
package com.jsxs.structure.Bride;
/**
* @Author Jsxs
* @Date 2023/4/21 21:03
* @PackageName:com.jsxs.structure.Bride
* @ClassName: Mac
* @Description: TODO 扩展抽象化角色
* @Version 1.0
*/
public class Mac extends OS{
public Mac(VideoFile videoFile) {
super(videoFile);
}
@Override
public void play(String fileName) {
videoFile.decode(fileName);
}
}
package com.jsxs.structure.Bride;
/**
* @Author Jsxs
* @Date 2023/4/21 21:04
* @PackageName:com.jsxs.structure.Bride
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建Mac系统对象
OS mac = new Mac(new AVI());
// 使用操作系统播放文件
mac.play("战狼4");
}
}
好处:
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要改变原有维度。如:“如果现在有一种视频wmv,我们只需要再定以一个类实现VideoFile接口即可,其他类不需要在变化”
- 实现细节对客户透明
(4).使用场景
- 当一个类存在两个独立的维度的时候,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次导致系统类的个数急剧增加。
- 当一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性时,避免在两个层次之间建立静态的继承连接,通过桥接模式可以使他门在抽象层建立一个关联关系。
4.装饰者模式
(1).概述
我们先来看一个快餐店的例子。
快餐店有炒面、炒饭这些快餐。可以额外的附加加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外的加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。
使用继承的方式去
使用继承的方式存在的问题:
- 扩展性不好
如果要再一种配料(培根),我们就会发现需要给炒米和炒面条分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更更多的子类。 - 产生过多的子类
(2).结构
定义: 旨在不该现有对象结构的情况下,动态的给该对象增加一些职责(即增加其他额外的功能)的模式。
装饰模式的角色:
- 抽象构建角色: 定义一个
抽象接口
以规范准备接受附加责任的对象。 - 具体构建角色:
实现抽象构建
,通过装饰角色为其添加一些职责。 - 抽象装饰角色:
继承或实现抽象构建
,并包含具体构建的实列,可以通过其子类扩展具体构建的功能。 - 具体装饰角色:
实现抽象装饰
的相关方法,并给具体构建对象添加附加的责任。
(3).案列
我们使用装饰者模式对快餐店案列进行改进,体会装饰者模式的精髓。
类图如下:
抽象构建角色
package com.jsxs.structure.decorator;
/**
* @Author Jsxs
* @Date 2023/4/21 17:52
* @PackageName:com.jsxs.structure.decorator
* @ClassName: FastFood
* @Description: TODO 抽象构建角色
* @Version 1.0
*/
public abstract class FastFood {
private float price;
private String desc;
public FastFood(float price, String desc) {
this.price = price;
this.desc = desc;
}
public FastFood() {
}
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();
}
具体构建角色
package com.jsxs.structure.decorator;
/**
* @Author Jsxs
* @Date 2023/4/21 17:57
* @PackageName:com.jsxs.structure.decorator
* @ClassName: rice
* @Description: TODO 炒饭具体构建
* @Version 1.0
*/
public class rice extends FastFood{
public rice(){ //无参创建对象
super(10,"炒饭"); // 设定继承父类的参数
}
@Override
public float cost() {
return this.getPrice();
}
}
package com.jsxs.structure.decorator;
/**
* @Author Jsxs
* @Date 2023/4/21 18:01
* @PackageName:com.jsxs.structure.decorator
* @ClassName: noddles
* @Description: TODO
* @Version 1.0
*/
public class noddles extends FastFood{
public noddles(){
super(20,"面条");
}
@Override
public float cost() {
return this.getPrice();
}
}
抽象装饰者类
package com.jsxs.structure.decorator;
/**
* @Author Jsxs
* @Date 2023/4/21 18:02
* @PackageName:com.jsxs.structure.decorator
* @ClassName: Granish
* @Description: TODO 抽象装饰者类
* @Version 1.0
*/
public abstract class Granish extends FastFood{
// 聚合快餐
private FastFood fastFood;
public Granish(float price, String desc, FastFood fastFood) {
super(price, desc);
this.fastFood = fastFood;
}
public FastFood getFastFood() {
return fastFood;
}
public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}
}
具体装饰角色
package com.jsxs.structure.decorator;
/**
* @Author Jsxs
* @Date 2023/4/21 18:06
* @PackageName:com.jsxs.structure.decorator
* @ClassName: egg
* @Description: TODO
* @Version 1.0
*/
public class egg extends Granish{
public egg(FastFood fastFood){
super(1,"鸡蛋",fastFood); //定义鸡蛋
}
@Override
public float cost() {
return this.getPrice()+this.getFastFood().cost(); //鸡蛋价格+快餐价格
}
@Override
public String getDesc() {
return super.getDesc()+getFastFood().getDesc(); // 鸡蛋描述+米饭描述
}
}
package com.jsxs.structure.decorator;
/**
* @Author Jsxs
* @Date 2023/4/21 18:20
* @PackageName:com.jsxs.structure.decorator
* @ClassName: Bacon
* @Description: TODO
* @Version 1.0
*/
public class Bacon extends Granish{
public Bacon(FastFood fastFood){
super(1,"培根",fastFood); //定义鸡蛋
}
@Override
public float cost() {
return this.getPrice()+this.getFastFood().cost(); //鸡蛋价格+快餐价格
}
@Override
public String getDesc() {
return super.getDesc()+getFastFood().getDesc(); // 鸡蛋描述+米饭描述
}
}
客户端
package com.jsxs.structure.decorator;
/**
* @Author Jsxs
* @Date 2023/4/21 18:22
* @PackageName:com.jsxs.structure.decorator
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 点一份炒饭
FastFood rice = new rice();
System.out.println(rice.getDesc()+rice.getPrice());
// 炒饭中加一个鸡蛋
rice = new egg(rice);
System.out.println(rice.getDesc()+" "+rice.cost());
rice=new Bacon(rice);
System.out.println(rice.getDesc()+" "+rice.cost());
}
}
好处:
- 装饰者模式可以带来比较更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取不同行为状态的多样化的结构。装饰者模式比继承更具备灵活的扩展性,
完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任
。 装饰类和被装饰类可以独立发展,不会相互耦合
,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
(4).使用场景
- 当
不能采用继承的方式
对系统进行扩充或者采用继承不利于系统扩展和维护时。 -
- 不能次啊用继承的情况主要有两类:
-(1).第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性
增长。
-(2).第二类是因为类定义不能继承(如 final)
- 不能次啊用继承的情况主要有两类:
- 在不影响其他对象的情况下,以
动态、透明的方式给单个对象添加职责
- 当对象的功能要求可以
动态地添加
,也可以再动态的撤销
时。
(5).JDK源码解析
IO流中的包装类使用到了装饰模式。BufferedInputStream
,BufferedOutputStream
,BufferedReader
,BufferedWriter
。
(6).代理和装饰者的区别
静态代理和装饰者模式的区别:
- 相同点:
-
- 都要实现与目标类相同的业务接口
-
- 在两个类中都要声明目标对象
-
- 都可以在不修改目标类的前提下增强目标方法
- 不同点
-
- 目的不同: “装饰者是为了增强目标对象,静态代理是为了保护和隐藏目标对象”
-
- 获取目标对象构建的地方不同:“装饰者是由
外界
传递进来的,可以通过构造方法传递,静态代理时在代理内部
创建,以此来隐藏目标对象”
- 获取目标对象构建的地方不同:“装饰者是由
5.外观模式
(1).概述
有些人可能超过股票,但是其实大部分人都不懂,这种没有足够了解证券知识的情况下做股票是很容易亏钱的,刚开始炒股肯定都会想,如果有个懂行的帮帮手,支付宝里面有很多基金,它将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债卷、外汇等领域,而基金投资的收益归持有者所有,管理机构收取一定比列的托管管理费用
。
定义:
又名门面模式
,是一种通过多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一的接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性
。
迪米特法则的典型应用
(2).结构
外感模式包含以下主要角色:
- 外观角色: 为多个子系统对外提供一个共同的接口。
- 子系统角色: 实现系统的部分功能,客户可以通过外观角色访问它。
(3).案例
eg:智能家电控制
小明的爷爷已经60岁了,一个人在家生活;每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语言直接控制这些智能家电的开启和关闭。类图如下:
子类系统角色
package com.jsxs.structure.facade;
/**
* @Author Jsxs
* @Date 2023/4/22 9:58
* @PackageName:com.jsxs.structure.facade
* @ClassName: Light
* @Description: TODO 子系统角色
* @Version 1.0
*/
public class Light {
public void on(){
System.out.println("开灯");
}
public void off(){
System.out.println("关灯");
}
}
package com.jsxs.structure.facade;
/**
* @Author Jsxs
* @Date 2023/4/22 9:59
* @PackageName:com.jsxs.structure.facade
* @ClassName: TV
* @Description: TODO 子角色
* @Version 1.0
*/
public class TV {
public void on(){
System.out.println("开电视");
}
public void off(){
System.out.println("关电视");
}
}
package com.jsxs.structure.facade;
/**
* @Author Jsxs
* @Date 2023/4/22 9:59
* @PackageName:com.jsxs.structure.facade
* @ClassName: airContition
* @Description: TODO 子类角色
* @Version 1.0
*/
public class airContition {
public void on(){
System.out.println("开灯空调");
}
public void off(){
System.out.println("关空调");
}
}
外观角色
package com.jsxs.structure.facade;
/**
* @Author Jsxs
* @Date 2023/4/22 10:02
* @PackageName:com.jsxs.structure.facade
* @ClassName: SmartFacade
* @Description: TODO 外观角色
* @Version 1.0
*/
public class SmartFacade {
// 聚合
private Light light;
private TV tv;
private airContition airContition;
// 实列化
public SmartFacade() {
light=new Light();
tv=new TV();
airContition=new airContition();
}
// 语音控制
public void say(String message){
if (message.contains("打开")){
on();
}else if (message.contains("关闭")){
off();
}else {
System.out.println("我不知道你在说什么...");
}
}
// 一键打开
public void on(){
light.on();
tv.on();
airContition.on();
}
// 一键关闭
public void off(){
light.off();
tv.off();
airContition.off();
}
}
测试
package com.jsxs.structure.facade;
/**
* @Author Jsxs
* @Date 2023/4/22 10:16
* @PackageName:com.jsxs.structure.facade
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
SmartFacade smartFacade = new SmartFacade();
smartFacade.say("关闭");
smartFacade.say("打开");
}
}
好处:
降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类
。- 对客户屏蔽了子系统的组件,减少了客户处理的对象数目,
并使得子系统使用起来更加容易
。
缺点:
- 不符合
开闭原则
,修改很麻烦。
(4).使用场景
- 对分层结构系统构建时,使用外观模式定义子系统中每层的入口可以简化子系统之间的依赖关系。
当一个复杂系统的子系统很多时,外观模式可以位系统设计一个简单的接口供外界访问
。当客户端与多个子系统之间存在很大的联系时
,引入外观模式可以将他们分离,从而提高自行的独立性和可移植性
。
(5).源码分析
使用tomcat作为web容器时,接受浏览器发送过来的而请求,tomcat会将请求信息封装成ServletRequest对象,但是大家想想ServletRequest是一个接口,它还有一个子接口HttpServletRequest,而我们都知道request对象肯定是一个ServletRequest对象的子实现类对象,到底是哪个类的对象呢,可以通过暑促和request对象,我们就会发现是一个名为RequestFacade的类的对象。
public class Personnel_visit_informationServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
为什么在这里使用外观模式?
定义RequestFacade
类,分别实现ServletRequest
,同时定义私有成员变量Request,
并且方法的实现调用Request
的实现。然后将RequestFacade上传位ServletRequest传给Servlet的service方法,这样即使在servlet中被下转为RequestFacade,也不能访问保护成员变量对象中的方法
。即用了Request,又能放置其中方法不合理的访问。
6.享元模式
(1).概述
定义:
运用共享技术
来有效的支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率(共享单车)。
(2).结构
享元模式中存在以下两种状态:
- 内部状态: 既不会随着环境的改变而改变的可共享部分,
- 外部状态: 指随环境改变而改变的不可以共享的部分。享元模式实现就是区分应用中的这两种状态,并将外部状态外部化。
享元模式的主要角色:
- 抽象享元角色: 通常是一个
接口或者抽象类
,在抽象享元类中声明了具体享元对象的内部数据(内部状态
),同时也可以通过这些方法来设置外部数据(外部状态
) - 具体享元角色:
它实现了抽象享元类,称为享元对象
;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单列模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。 - 非享元对象角色: 并不是所有的抽象享元类的子类都需要被共享,
不能被共享的子类可以设计为非共享具体享元类
;当需要一个非共享享元类的对象时可以直接通过实列化创建。 - 享元工厂角色:
负责创建和管理享元角色
。当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象
。
(3).享元模式案列
eg: 俄罗斯方块
下面的图片就是总所周知的俄罗斯方块,如果在俄罗斯方块这个游戏中,每个不同的方块就是一个实列对象,这些对象就要占用很多的内存空间,下面利用享元模式进行实现。
类图:
代码如下:
俄罗斯方块有不同的形状,我们可以对这些形状向上抽象出AbstractBox,用来定义共性的属性和行为。
抽象享元角色
package com.jsxs.structure.flyweight;
/**
* @Author Jsxs
* @Date 2023/4/22 14:47
* @PackageName:com.jsxs.structure.flyweight
* @ClassName: AbstractBox
* @Description: TODO 抽象享元角色
* @Version 1.0
*/
public abstract class AbstractBox {
// 获取图形的方法
public abstract String getShape();
// 显示图形及颜色
public void display(String color){
System.out.println("方块形状是: "+getShape()+" 颜色: "+color);
}
}
具体享元角色
package com.jsxs.structure.flyweight;
/**
* @Author Jsxs
* @Date 2023/4/22 14:49
* @PackageName:com.jsxs.structure.flyweight
* @ClassName: IBox
* @Description: TODO 具体享元角色
* @Version 1.0
*/
public class IBox extends AbstractBox{
@Override
public String getShape() {
return "I";
}
}
package com.jsxs.structure.flyweight;
/**
* @Author Jsxs
* @Date 2023/4/22 14:50
* @PackageName:com.jsxs.structure.flyweight
* @ClassName: RBox
* @Description: TODO
* @Version 1.0
*/
public class RBox extends AbstractBox{
@Override
public String getShape() {
return "R";
}
}
package com.jsxs.structure.flyweight;
/**
* @Author Jsxs
* @Date 2023/4/22 14:50
* @PackageName:com.jsxs.structure.flyweight
* @ClassName: OBox
* @Description: TODO
* @Version 1.0
*/
public class OBox extends AbstractBox{
@Override
public String getShape() {
return "O";
}
}
享元工厂
package com.jsxs.structure.flyweight;
import java.util.HashMap;
/**
* @Author Jsxs
* @Date 2023/4/22 14:51
* @PackageName:com.jsxs.structure.flyweight
* @ClassName: BoxFactory
* @Description: TODO 享元工厂模式
* @Version 1.0
*/
public class BoxFactory {
private HashMap<String,AbstractBox> map;
// 构造函数初始化且设置单列模式
private BoxFactory(){
map=new HashMap<String,AbstractBox>();
// 添加图像
map.put("I",new IBox());
map.put("R",new RBox());
map.put("O",new OBox());
}
// 饿汉模式创建
private static BoxFactory factory=new BoxFactory();
// 提供一个方法获取该工厂类对象
public static BoxFactory getInstance(){
return factory;
}
// 根据名称获取图像对象
public AbstractBox getShape(String name){
return map.get(name);
}
}
测试
package com.jsxs.structure.flyweight;
/**
* @Author Jsxs
* @Date 2023/4/22 15:00
* @PackageName:com.jsxs.structure.flyweight
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 获取工厂对象
BoxFactory factory = BoxFactory.getInstance();
// 获取指定图形
AbstractBox i = factory.getShape("I");
// 打印指定颜色图形
i.display("红色");
// 获取工厂对象
BoxFactory factory1 = BoxFactory.getInstance();
// 获取指定图形
AbstractBox i1 = factory1.getShape("I");
// 打印指定颜色图形
i1.display("蓝色");
System.out.println(i==i1);
}
}
(6).优缺点和使用场景
优点:
极大的减少内存中像是或相同对象数量
,节约系统资源,提供系统性能。享元模式中的外部状态相对独立,且不影响内部状态
。
缺点:
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,是程序逻辑复杂。
使用场景:
- 一个系统有大量相同或者相似的对象,造成内存的大量耗费
- 对象的大部分状态可以外部化,可以将这些外部状态传入对象中。
- 在使用享元模式时需要维护一个存储享元对象的享元池(HashMap),而这些需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
7.组合模式
(1).概述
上面是一个文件系统,也就是目录结构。对于这种的结构我们称之为数型构
。在树结构中可以通过调用某一个方法来遍历整个树,当我们找到某一个叶子节点后,就可以对叶子节点进行相关操作。可以将这课树比喻成一个大的容器,容器里面包含很多的成员对象,这些成员对象既可以是容器对象也可以是叶子对象
。但是由于容器和叶子对象在功能上面的区别,使得我们在使用的过程中必须要区分容器对象
和叶子对象
,但是这样就会给客户带来不必要的麻烦,作为客户而已,它始终希望能够一致的对待容器对象(文件夹)
和叶子对象(文件)
。
定义:
又名部分整体模式
,是用于把一组相似的对象当作一个的单一的对象,组合模式依据属性结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,他创建了对象组的树形结构
。
(2).结构
组合模式主要包含三种角色:
- 抽象根节点: 定义系统各层次对象的公有方法和属性,可以预先定义一些默认行为和属性
- 树枝结点(文件夹): 定义树枝节点的行为,存储子节点,组合树子结点形成一个树形结构
- 叶子节点(文件): 叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
(3).案列实现
eg: 软件菜单
我们在访问别的一些管理系统时,经常可以看到类似的菜单,一个菜单可以包含菜单项
(菜单项是指不在包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单
,因此使用组合模式描述菜单就很恰当,当我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称。
不管是菜单还是菜单项,都应该继承自统一的接口,这里姑且将这个统一的接口称为菜单组件。
抽象根节点
package com.jsxs.structure.combination;
/**
* @Author Jsxs
* @Date 2023/4/22 12:56
* @PackageName:com.jsxs.structurebination
* @ClassName: MenuComponent
* @Description: TODO 抽象根节点
* @Version 1.0
*/
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 index){
throw new UnsupportedOperationException();
}
// 获取菜单或者菜单项的名称
public String getName(){
return name;
}
// 打印菜单名称的方法
public abstract void print();
}
树枝节点
package com.jsxs.structure.combination;
import java.util.ArrayList;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/4/22 13:17
* @PackageName:com.jsxs.structurebination
* @ClassName: Menu
* @Description: TODO 菜单栏、属于树子结点
* @Version 1.0
*/
public class Menu extends MenuComponent{
// 菜单可以有多个子菜单或者子菜单项
private List<MenuComponent> menuComponentList=new ArrayList<>();
// 构造方法
public Menu(String name,int leve){
this.name=name;
this.level=leve;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}
@Override
public MenuComponent getChild(int index) {
return menuComponentList.get(index);
}
@Override
public void print() {
// 打印菜单名称
for (int i = 0; i < level; i++) {
System.out.print("-");
}
System.out.println(name);
// 打印子菜单名称
for (MenuComponent menuComponent : menuComponentList) {
menuComponent.print();
}
}
}
叶子节点
package com.jsxs.structure.combination;
/**
* @Author Jsxs
* @Date 2023/4/22 13:24
* @PackageName:com.jsxs.structurebination
* @ClassName: MenuUtem 菜单项: 叶子节点
* @Description: TODO
* @Version 1.0
*/
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);
}
}
测试:
package com.jsxs.structure.combination;
/**
* @Author Jsxs
* @Date 2023/4/22 13:26
* @PackageName:com.jsxs.structurebination
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建菜单树
MenuComponent menu1 = new Menu("菜单管理", 2);
menu1.add(new MenuItem("页面访问",3));
menu1.add(new MenuItem("展开菜单",3));
menu1.add(new MenuItem("编辑菜单",3));
menu1.add(new MenuItem("删除菜单",3));
menu1.add(new MenuItem("新增菜单",3));
MenuComponent menu2 = new Menu("权限管理", 2);
menu2.add(new MenuItem("提交保存",3));
menu2.add(new MenuItem("展开菜单",3));
MenuComponent menu3 = new Menu("角色管理", 2);
menu3.add(new MenuItem("页面访问",3));
menu3.add(new MenuItem("新增角色",3));
// 创建一级菜单
MenuComponent RootMenu = new Menu("windows系统",1);
// 将二级系统添加到一级菜单中
RootMenu.add(menu1);
RootMenu.add(menu2);
RootMenu.add(menu3);
// 打印菜单名称(如果有子菜单一起打印)
RootMenu.print();
}
}
(4).组合模式的分类
在使用组合模式时,根据抽象构建类的定义形式,我们可以将组合模式分为透明组合和安全组合模式两种形式。
透明组合模式
:
透明组和模式中,抽象根节点角色声明了所有用于管理成员对象的方法,比如在实列中:MenuComponent
声明了add、remove、getChild
方法,这样做的好处是确保所有的构建类都有相同的接口
。透明组合模式也是组合模式的标准形式。
透明组合模式的缺点是不够安全,因为叶子对象和容器对象不可能有下一个层次的对象,既不可能包含成员对象,因此为其提高add()、remove()、等方法时没有意义的,这在编译阶段不会出错,但在运行阶段,如果调用这些方法可能会出错。安全组合模式
在安全组合模式中,在抽象构建角色中没有声明任何用于管理成员对象的方法,而是树枝节点Menu
类中声明并实现这些方法。安全组合模式的缺点是不够透明
,因为叶子构建和容器构建具有不同的放啊,且容器构件中那些用于管理成员对象饿方法没有在抽象构建类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构建和容器构建。
(5).使用场景
优点:
- 组合模式可以清除地定义分层次地复杂对象,表示对象地全部或部分层次,
它让客户端忽略了层次地差异,方便对整个层次结构进行控制
- 客户端可以一致地使用一个组合结构或其中单个对象,不必关于处理地是单个对象还是整个组合结构,
简化客户端代码
。 - 在组合模式中增加新地树枝节点和叶子节点都很方便,无需对现有类库进行任何修改,
符合"开闭原则"
。 - 组合模式为
树形结构地面向对象实现
提供了一种灵活地解决方案,通过叶子节点和树枝节点地递归组合,可以形成复杂地树形结构,但对属性结构地控制却非常简单。
组合模式正是因为树形结构而生,所以组合模式地使用场景就是出现属性结构的地方。比如: 文件目录显示,多级目录呈现等树形结构数据的操作。
(7).JDK源码分析
Integer类使用了享元模式。
package com.jsxs.structure.flyweight;
/**
* @Author Jsxs
* @Date 2023/4/22 15:00
* @PackageName:com.jsxs.structure.flyweight
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
Integer i1=127;
Integer i2=127;
System.out.println(i1==i2);
Integer i3=128;
Integer i4=128;
System.out.println(i3==i4);
}
}
结论: -128~127 是一个对象。否则创建一个新的对象。
(四)、行为型模式(11种)
行为型模式用于描述程序在运行时复杂的流程控制
,即描述多个类或对象之间怎样相互协助共同完成单个对象都无法独特完成的任务,它设计算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制
来在类
间分配行为,后者采用组合或聚合
对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足"合成复用原则",所以对象行为模式比类行为模式具有更强大的灵活性
。
除了"模板方法模式"和解释器模式是类行为模式,其他的全部属于对象行为型模式。
1.模板方法模式
(1).概述
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需要的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
列如: 去银行办理业务一般要经历四个流程: 区号、排队、办理具体业务、对银行工作人员进行分析等,其中取号、排队和对银行共工作人员进行评分的业务对每个客户都是一样的
,可以在父类中实现,但是办理具体业务
却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
定义:
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法得某些特定步骤
。
(2).结构
模板方法模式包含以下主要角色:
- 抽象类:负责给出一个算法的轮廓和骨架。它有一个
模板方法和若干个基本方法
构成。 -
模板方法
: 定义了算法的骨架,按某种算法调用其包含的基本方法
。
-
基本方法
: 是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种
-
-
- (1).
抽象方法(办理具体业务)
: 一个抽象方法由抽象类声明、由其具体子类实现
- (1).
-
-
-
- (2).
具体方法(取号、排队、评分)
:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承
- (2).
-
-
-
- (3).
钩子方法
: 在抽象类中以及该实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为Boolean类型。
- (3).
-
- 具体子类: 实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
(3).案列实现
eg: 炒菜
炒菜的步骤是固定的,分为倒油、热油、到蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。
抽象类
package com.jsxs.behavioralModel.template;
/**
* @Author Jsxs
* @Date 2023/4/22 20:32
* @PackageName:com.jsxs.behavioralModel.template
* @ClassName: AbstractClass
* @Description: TODO 抽象类(模板方法和基本方法)
* @Version 1.0
*/
public abstract class AbstractClass {
// 模板方法定义 (算法结构不能被子类改变)
public final void cookProcess(){
pourOil();
heatOil();
pourVegetable();
pourSauce();
}
// 倒油
public void pourOil() {
System.out.println("倒油");
}
// 热油
public void heatOil() {
System.out.println("热油");
}
// 到蔬菜
public abstract void pourVegetable();
// 到调味品
public abstract void pourSauce();
// 翻炒
public void fly() {
System.out.println("翻炒....");
}
}
具体子类
package com.jsxs.behavioralModel.template;
/**
* @Author Jsxs
* @Date 2023/4/22 20:44
* @PackageName:com.jsxs.behavioralModel.template
* @ClassName: Baicai
* @Description: TODO 具体子类
* @Version 1.0
*/
public class Baicai extends AbstractClass{
@Override
public void pourVegetable() {
System.out.println("白菜");
}
@Override
public void pourSauce() {
System.out.println("醋和垃圾");
}
}
package com.jsxs.behavioralModel.template;
/**
* @Author Jsxs
* @Date 2023/4/22 20:45
* @PackageName:com.jsxs.behavioralModel.template
* @ClassName: Baocai
* @Description: TODO 具体子类
* @Version 1.0
*/
public class Baocai extends AbstractClass{
@Override
public void pourVegetable() {
System.out.println("包菜");
}
@Override
public void pourSauce() {
System.out.println("蒜蓉");
}
}
测试
package com.jsxs.behavioralModel.template;
/**
* @Author Jsxs
* @Date 2023/4/22 20:46
* @PackageName:com.jsxs.behavioralModel.template
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
Baocai baocai = new Baocai();
baocai.cookProcess();
}
}
(4).优缺点
优点:
提高代码复用性
: 将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。实现了反向控制
: 通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了方向控制,并符合"开闭原则";
缺点:
- 对每一个不同的实现都需要定义一个子类,这会导致
类的个数增加
,系统更加庞大,设计也更加抽象。 - 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结果,
它提高了代码阅读的难度
。
(4).使用场景
- 算法的整体步骤很固定,但其中个别部分容易变化的时候,这时候可以使用模板方法模式,将容易变得部分抽象出来,供子类实现。
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类得反向控制。
(6).JDK源码分析
InputStream类就是用了模板方法模式,在InputStream类中定义了多个Read()方法。重载…
2.策略模式
(1).概述
我们出去旅游可供选择的方式有很多种,可以骑自行车、可以坐汽车、可以坐火车。
作为一个程序员,开发需要选择一款开发工具,当然可以使用Idea进行代码开发,也可以使用Eclips。
定义: 该模式定义了一些列算法,并将每个算法都封装起来。使他们可以相互替换,且算法的变化并不会影响使用算法的客户(都会达到目的地)
,策略模式属于对象行为模式,它通过对算法进行描述,把使用算法的责任
和算法的实现分割开
来,并委派给不同的对象对这些算法进行管理。
(2).结构
- 抽象策略类: 这是一个抽象角色,通常由一个
接口或抽象类
实现。此角色给出所有的具体策略类所需的接口。 - 具体策略类:
实现了抽象策略定义的接口
,提供具体的算法实现或行为。 - 环境类: 持有一个
策略类的引用
,最终给客户端调用。
(3).案列实现
eg: 促销活动
一家百货公司在顶年度的促销活动,针对不同的节日(春节、中秋节、圣诞节)退出不同的活动,由促销员将不同的活动展示给客户。
抽象策略
package com.jsxs.behavioralModel.strategy;
/**
* @Author Jsxs
* @Date 2023/4/23 9:05
* @PackageName:com.jsxs.behavioralModel.strategy
* @ClassName: Strategy
* @Description: TODO 抽象策略类
* @Version 1.0
*/
public interface Strategy {
void show();
}
具体策略
package com.jsxs.behavioralModel.strategy;
/**
* @Author Jsxs
* @Date 2023/4/23 9:07
* @PackageName:com.jsxs.behavioralModel.strategy
* @ClassName: StrategyImlA
* @Description: TODO 具体策略类
* @Version 1.0
*/
public class StrategyImlA implements Strategy{
@Override
public void show() {
System.out.println("A");
}
}
package com.jsxs.behavioralModel.strategy;
/**
* @Author Jsxs
* @Date 2023/4/23 9:07
* @PackageName:com.jsxs.behavioralModel.strategy
* @ClassName: StrategyImlB
* @Description: TODO
* @Version 1.0
*/
public class StrategyImlB implements Strategy{
@Override
public void show() {
System.out.println("B");
}
}
package com.jsxs.behavioralModel.strategy;
/**
* @Author Jsxs
* @Date 2023/4/23 9:08
* @PackageName:com.jsxs.behavioralModel.strategy
* @ClassName: StrategyImlC
* @Description: TODO
* @Version 1.0
*/
public class StrategyImlC implements Strategy{
@Override
public void show() {
System.out.println("C");
}
}
环境类
package com.jsxs.behavioralModel.strategy;
/**
* @Author Jsxs
* @Date 2023/4/23 9:10
* @PackageName:com.jsxs.behavioralModel.strategy
* @ClassName: ContextEnv
* @Description: TODO 环境类: 销售员
* @Version 1.0
*/
public class ContextEnv {
// 聚合
private Strategy strategy;
// 有参构造赋值
public ContextEnv(Strategy strategy) {
this.strategy = strategy;
}
// 促销活动展示出来
public void salesShow(){
strategy.show();
}
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
}
package com.jsxs.behavioralModel.strategy;
/**
* @Author Jsxs
* @Date 2023/4/23 9:13
* @PackageName:com.jsxs.behavioralModel.strategy
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
ContextEnv contextEnv = new ContextEnv(new StrategyImlA());
contextEnv.salesShow();
// ----- 复用这个对象
contextEnv.setStrategy(new StrategyImlB());
contextEnv.salesShow();
}
}
(4).使用场景
优点:
策略类之间可以自由切换
: 由于策略类都是先同一个接口,所以可以自由的切换。易于扩展
: 增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开闭原则避免使用多重条件选择语句(if else)
,充分体现了面向对象的思想
缺点:
客户端必须知道所有的策略类
,并且自行决定使用哪一个策略- 策略模式将造成产生很多策略类,可以通过享元模式在一定程度上减少对象的创建。
使用场景:
- 一个系统需要动态的在
几种算法中选择一种
时,可将算法封装到策略类中 - 一个类定义了多种行为,并且这些行为在这个类中的操作以多个条件语句的形式出现,
可将每个条件分支一如他们各自的策略类中以代替这些条件语句
。 - 系统中各个
算法彼此完全独立
,且要求对用户隐藏具体算法的实现细节时。 - 系统要求使用算法的客户不应该知道其操作的数据时,可使用
策略模式来隐藏与算法相关的数据结构
。 - 多个类只区别在表现型为不同,可以使用策略模式,在运行时动态选择具体执行的行为。
(5).JDK源码解析
Comparator
中的策略模式在Arrays
类中有一个Sort()
方法。
1. sort()方法时排序的方法
2. 每一个排序的方法后面都对应着不同的算法,升序和降序算法。
3.命令模式
(1).命令模式
日常生活中,我们出去吃饭会遇到如下的情况。
顾客(点餐)->女服务员(接受订单)->女服务员转给厨师(厨师佐餐)
定义:
将一个请求封装为对象,使发出请求的责任(服务员)
和执行请求的责任(厨师)
分隔开。这样两者之间通过命令对象进行沟通,这样方便命令对象进行存储、传递、调用、增加与管理。
(2).结构
命令模式包含以下主要角色:
- 抽象命令角色: 定义命令的
接口
,声明执行的方法。 - 具体命令角色: 具体的命令,
实现命令接口
;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。 - 实现者/接收者角色: 接收者,
真正执行命令的对象(厨师)
。任何类都可以成为一个接收者,只要它能够实现命令要求实现的响应功能。 - 调用者/请求者角色:
要求命令对象执行请求(服务员)
,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行响应的操作的地方,也就是说相当于使用命令对象的入口。
(3).案列实现
将上面的案列用代码实现,那我们就需要分析命令模式的角色在该案列中由谁来充当。
服务员
: 调用者角色。
资深大厨
: 结接收者角色。
订单
: 命令中包含订单。
订单
package com.jsxs.behavioralModel.command;
import java.util.HashMap;
import java.util.Map;
/**
* @Author Jsxs
* @Date 2023/4/23 13:08
* @PackageName:com.jsxs.behavioralModelmand
* @ClassName: Order
* @Description: TODO 订单类
* @Version 1.0
*/
public class Order {
// 餐桌号码
private int diningTable;
// 所属的餐品及分数
private Map<String,Integer> foodDir=new HashMap<String,Integer>();
public int getDiningTable() {
return diningTable;
}
public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}
public Map<String, Integer> getFoodDir() {
return foodDir;
}
// 餐品的名称和数量
public void setFood(String name,int num) {
foodDir.put(name,num);
}
}
接收者
package com.jsxs.behavioralModel.command;
/**
* @Author Jsxs
* @Date 2023/4/23 13:15
* @PackageName:com.jsxs.behavioralModelmand
* @ClassName: SeniorChef
* @Description: TODO 厨师
* @Version 1.0
*/
public class SeniorChef {
// 做餐
public void makeFood(String name,int num){
System.out.println(num+" 份 "+name);
}
}
抽象命令
package com.jsxs.behavioralModel.command;
/**
* @Author Jsxs
* @Date 2023/4/23 13:16
* @PackageName:com.jsxs.behavioralModelmand
* @ClassName: Command
* @Description: TODO 抽象命令类
* @Version 1.0
*/
public interface Command {
void execute(); //执行命令
}
具体命令
package com.jsxs.behavioralModel.command;
import java.util.Map;
import java.util.Set;
/**
* @Author Jsxs
* @Date 2023/4/23 13:17
* @PackageName:com.jsxs.behavioralModelmand
* @ClassName: OrderCommand
* @Description: TODO
* @Version 1.0
*/
public class OrderCommand implements Command{
// 持有接收者对象
private SeniorChef seniorChef;
// 持有订单对象
private Order order;
// 有参构造方法进行赋值的操作
public OrderCommand(SeniorChef seniorChef, Order order) {
this.seniorChef = seniorChef;
this.order = order;
}
//
@Override
public void execute() {
System.out.println(order.getDiningTable()+"号桌的订单是:");
// 这里存放的是我们要做的全部餐品
Map<String, Integer> foodDir = order.getFoodDir();
// 遍历map集合
Set<String> strings = foodDir.keySet(); //获取所有键的集合
// 做餐
for (String foodName : strings) {
seniorChef.makeFood(foodName,foodDir.get(foodName)); // 餐名和数量
}
System.out.println(order.getDiningTable()+"号桌子的饭做好了");
}
}
请求者
package com.jsxs.behavioralModel.command;
import java.util.ArrayList;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/4/23 13:27
* @PackageName:com.jsxs.behavioralModelmand
* @ClassName: Waitor
* @Description: TODO 服务员 (请求者角色)
* @Version 1.0
*/
public class Waitor {
// 持有多个命令对象
private List<Command> commands = new ArrayList<>();
// 将单个命令存放到集合中
public void setCommand(Command command) {
commands.add(command);
}
// 发起命令的功能
public void orderUp(){
System.out.println("美女服务员说: 来订单了...");
// 因为命令在集合中
for (Command command : commands) {
if (command!=null){
command.execute(); // 执行
}
}
}
}
测试类
package com.jsxs.behavioralModel.command;
/**
* @Author Jsxs
* @Date 2023/4/23 13:32
* @PackageName:com.jsxs.behavioralModelmand
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 订单对象
Order order1 = new Order();
order1.setDiningTable(1);
order1.setFood("西红柿鸡蛋面",1);
order1.setFood("可乐",2);
Order order2 = new Order();
order2.setDiningTable(2);
order2.setFood("炒饭",1);
order2.setFood("雪碧",2);
// 创建厨师对象
SeniorChef seniorChef = new SeniorChef();
// 创建命令对象
OrderCommand orderCommand = new OrderCommand(seniorChef, order1);
OrderCommand orderCommand2 = new OrderCommand(seniorChef, order2);
//创建调用者也就是服务员
Waitor waitor = new Waitor();
waitor.setCommand(orderCommand);
waitor.setCommand(orderCommand2);
waitor.orderUp();
}
}
(4).使用场景
优点:
- 降低了系统的耦合度,命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便。采用命令模式增加或删除命令不会影响其他类,它满足"开闭原",对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo(命令撤销) 和 Redo(命令恢复) 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。
- 系统结构更加复杂。
使用场景:
- 系统需要将请求调用者和请求接受者解耦,使得调用者和接收者部直接交互。
- 系统需要在不同的事件指定请求、将
请求排队(List)
和执行请求。 - 系统需要支持命令的撤销操作(Undo)和恢复操作(Redo).
(5).JDK源码解析
Runable
是一个典型的命令模式,Runnable
担当命令的角色,Thread(服务员)
充当的是调用者,start
方法就是执行方法。
4.职责链模式
(1).概述
在现实生活中,常常会出现这样的案列: 一个请求由多个对象处理,但每一个对象的处理条件的权限不同。列如: 公司员工请假,可皮甲的领导有不么负责人、总经理、副经理等,但每个领导能批准的天数不同,员工必须根据自己请假的天数去找不同的领导签名,也即是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的列子还有很多,如找领导出差报销、生活中的"击鼓传花"游戏等。
定义:
又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有的请求的处理者通过前以对象记住下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
(2).结构
职责链模式主要包含以下角色:
- 抽象处理者角色: 定义一个处理请求的
接口
,包含抽象处理方法和一个后继连接。 - 具体处理者角色:
实现抽象处理者的处理方法
,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后续者。 - 客户类角色: 创建处理链,并相信链头的具体处理者对象提交请求,
它不关心处理细节和请求的传递过程
。
(3).案列实现
现需要开发一个请假流程控制系统。请假一天以上的假只需要小组长同意即可;请假一天到三天的假还需要部门经理同意;请求3天到7天的还需要总经理同意才行。
类图如下:
1、如果父类没有定义有参构造函数(或者存在有参构造方法,但是无参构造方法也有),
子类则不需要显式定义构造方法
。2、如果父类有显式定义有参构造方法且没有显示无参构造方法,则子类必须显式定义有参构造方法,
并且在构造方法中使用super(XX)显式调用父类的有参构造方法
请假条
package com.jsxs.behavioralModel.responsibility;
/**
* @Author Jsxs
* @Date 2023/4/23 15:08
* @PackageName:com.jsxs.behavioralModel.responsibility
* @ClassName: LeaveRequest
* @Description: TODO 请假类
* @Version 1.0
*/
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 void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
抽象处理者角色
package com.jsxs.behavioralModel.responsibility;
/**
* @Author Jsxs
* @Date 2023/4/23 15:10
* @PackageName:com.jsxs.behavioralModel.responsibility
* @ClassName: Handler
* @Description: TODO 抽象处理者
* @Version 1.0
*/
public abstract class Handler {
// 定义常量
protected final static int NUM_ONE=1;
protected final static int NUM_TWO=3;
protected final static int NUM_THREE=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;
}
// 各级领导处理请求条的放啊
protected abstract void handlerLeave(LeaveRequest leaveRequest);
// 提交请假条
public final void submit(LeaveRequest leaveRequest){
// 该领导进行审批
this.handlerLeave(leaveRequest);
if (this.nextHandler!=null&&leaveRequest.getNum()>this.numEnd){ //假如有后继领导,且请求的天数大于自己的最大天数,那么就交给下一任领导处理
this.nextHandler.submit(leaveRequest); //把请假条给下一任
}else {
System.out.println("流程结束....");
}
}
}
具体处理者
package com.jsxs.behavioralModel.responsibility;
/**
* @Author Jsxs
* @Date 2023/4/23 15:23
* @PackageName:com.jsxs.behavioralModel.responsibility
* @ClassName: GroupLeader
* @Description: TODO 小组长
* @Version 1.0
*/
public class GroupLeader extends Handler{
public GroupLeader(){
super(0,NUM_ONE);
}
@Override
protected void handlerLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName()+"请假 "+leaveRequest.getNum()+" 天"+leaveRequest.getContent());
System.out.println("小组长审批: 同意");
}
}
package com.jsxs.behavioralModel.responsibility;
/**
* @Author Jsxs
* @Date 2023/4/23 15:27
* @PackageName:com.jsxs.behavioralModel.responsibility
* @ClassName: Manger
* @Description: TODO 经理
* @Version 1.0
*/
public class Manger extends Handler{
public Manger(){
super(NUM_ONE,NUM_TWO);
}
@Override
protected void handlerLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName()+"请假 "+leaveRequest.getNum()+" 天"+leaveRequest.getContent());
System.out.println("经理审批: 同意");
}
}
package com.jsxs.behavioralModel.responsibility;
/**
* @Author Jsxs
* @Date 2023/4/23 15:28
* @PackageName:com.jsxs.behavioralModel.responsibility
* @ClassName: BigLeader
* @Description: TODO
* @Version 1.0
*/
public class BigLeader extends Handler{
public BigLeader(){
super(NUM_TWO,NUM_THREE);
}
@Override
protected void handlerLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName()+"请假 "+leaveRequest.getNum()+" 天"+leaveRequest.getContent());
System.out.println("总经理审批: 同意");
}
}
客户类角色
package com.jsxs.behavioralModel.responsibility;
/**
* @Author Jsxs
* @Date 2023/4/23 15:41
* @PackageName:com.jsxs.behavioralModel.responsibility
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建一个请假条
LeaveRequest leaveRequest = new LeaveRequest("jsxs", 5, "身体不适");
// 创建各级领导对象
GroupLeader groupLeader = new GroupLeader();
Manger manger = new Manger();
BigLeader bigLeader = new BigLeader();
// 添加上级领导
groupLeader.setNextHandler(manger);
manger.setNextHandler(bigLeader);
// bigLeader.setNextHandler(null);
// jsxs提交请假申请
groupLeader.submit(leaveRequest);
}
}
(4).优缺点
优点:
- 降低了对象间的耦合度: 该模式降低了请求发送者和接收者的耦合度
- 增加了系统的可扩展性: 可以根据需要增加新的请求处理类,满足
开闭原则
。 - 增强了给对象指派职责的灵活性: 当工作流程发生变化,可以动态的改变链内的成员或者修改它们的次序,也可以动态的新增或者删除责任。
- 责任链简化了对象之间的连接: 一个对象只需保持指向其后继者的引用,不需要保持其他处理者的引用,这避免了使用总舵的if或者if .else语句。
- 责任分担: 每个类只需处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点:
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理(
请假七天以上
),该请求可能一致传到链的末端都得不到处理。 - 对比较长的职责链,请求的处理可能设计多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
(5).JDK源码分析
在JavaWeb应用开发中,FilterChain是职责链(过滤器)模式的典型应用,以下是Filter的模拟实现分析。
5.状态模式
(1).概述
通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态停止状态,运行状态
。每一种状态改变,都有可能要根据其他状态来更新处理。列如: 如果电梯门现在处于运行时状态,就不能进行开门
的操作,而如果电梯门是停止状态,就可以执行开门操作
。
- 没有使用状态模式前
接口: 在接口中直接定义变量是 :全局变量
package com.jsxs.behavioralModel.state.before;
/**
* @Author Jsxs
* @Date 2023/4/23 16:10
* @PackageName:com.jsxs.behavioralModel.state.before
* @ClassName: ILift
* @Description: TODO 电梯接口
* @Version 1.0
*/
public interface ILift {
// 定义四个电梯状态的常量
int OPENING_STATE=1;
int CLOSING_STATE=2;
int RUNNING_STATE=3;
int STOPPING_STATE=4;
// 设置电梯状态的功能
void setState(int state);
// 设置电梯操作的功能
void open();
void close();
void ran();
void stop();
}
实现类
package com.jsxs.behavioralModel.state.before;
/**
* @Author Jsxs
* @Date 2023/4/23 16:14
* @PackageName:com.jsxs.behavioralModel.state.before
* @ClassName: Lift
* @Description: TODO 电梯类 (子类)
* @Version 1.0
*/
public class Lift implements ILift{
// 声明一个记录当前电梯状态的变量。
private int state;
@Override
public void setState(int state) {
this.state=state; // 赋值
}
@Override
public void open() {
switch (state){ //当前电梯状态
case OPENING_STATE:
// 什么事情都不做
break;
case CLOSING_STATE:
System.out.println("电梯打开了");
setState(OPENING_STATE);
break;
case RUNNING_STATE:
// 什么事情都不做
break;
case STOPPING_STATE:
System.out.println("电梯打开了");
setState(OPENING_STATE);
break;
}
}
@Override
public void close() {
switch (state){ //当前电梯状态
case OPENING_STATE:
System.out.println("电梯关闭了");
setState(CLOSING_STATE);
break;
case CLOSING_STATE:
break;
case RUNNING_STATE:
// 什么事情都不做
break;
case STOPPING_STATE:
break;
}
}
@Override
public void ran() {
switch (state){ //当前电梯状态
case OPENING_STATE:
// 什么事情都不做
break;
case CLOSING_STATE:
System.out.println("电梯开始运行了..");
setState(RUNNING_STATE);
break;
case RUNNING_STATE:
// 什么事情都不做
break;
case STOPPING_STATE:
System.out.println("电梯开始运行了..");
setState(RUNNING_STATE);
break;
}
}
@Override
public void stop() {
switch (state){ //当前电梯状态
case OPENING_STATE:
// 什么事情都不做
break;
case CLOSING_STATE:
System.out.println("电梯停止了");
setState(STOPPING_STATE);
break;
case RUNNING_STATE:
System.out.println("电梯停止了");
setState(STOPPING_STATE);
break;
case STOPPING_STATE:
break;
}
}
}
测试
package com.jsxs.behavioralModel.state.before;
/**
* @Author Jsxs
* @Date 2023/4/23 16:25
* @PackageName:com.jsxs.behavioralModel.state.before
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
Lift lift = new Lift();
// 设置当前电梯的状态
lift.setState(ILift.OPENING_STATE);
//开门
lift.open();
lift.close();
lift.ran();
lift.stop();
}
}
问题分析:
- 使用了大量的switch…case这样的判断(if…else也是一样),实用程序的可阅读性变差。
- 扩展性很差。如果加了断电的状态,我们需要修改上面判断逻辑。
定义:
对有状态的对象,把复杂的"判断逻辑"提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
(2).结构
状态模式包含以下主要角色:
- 环境模式: 也称上下文,也定义了客户程序需要的接口,维护了一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
- 抽象状态角色: 定义一个
接口
,用以封装对象中的特定状态所对应的行为。 - 具体状态角色:
实现抽象状态
所对应的行为。
(3).案列实现
对上诉电梯的案列使用状态模式进行改进。类图如下:
抽象状态类
package com.jsxs.behavioralModel.state.after;
/**
* @Author Jsxs
* @Date 2023/4/23 17:13
* @PackageName:com.jsxs.behavioralModel.state.after
* @ClassName: LiftState
* @Description: TODO 抽象状态类
* @Version 1.0
*/
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 ran();
// 电梯停止操作
public abstract void stop();
}
具体状态角色
package com.jsxs.behavioralModel.state.after;
/**
* @Author Jsxs
* @Date 2023/4/23 17:16
* @PackageName:com.jsxs.behavioralModel.state.after
* @ClassName: OpeningState
* @Description: TODO 电梯开启状态类
* @Version 1.0
*/
public class OpeningState extends LiftState{
@Override
public void open() {
System.out.println("电梯开启..");
}
@Override
public void close() {
// 修改状态
super.context.setLiftState(Context.CLOSING_STATE);
// 调用当前状态中的context中的close方法
super.context.close();
}
@Override
public void ran() {
// 什么都不做
}
@Override
public void stop() {
// 什么都不做
}
}
package com.jsxs.behavioralModel.state.after;
/**
* @Author Jsxs
* @Date 2023/4/23 17:18
* @PackageName:com.jsxs.behavioralModel.state.after
* @ClassName: ClosingState
* @Description: TODO 电梯关闭状态
* @Version 1.0
*/
public class ClosingState extends LiftState{
@Override
public void open() {
// 修改状态
super.context.setLiftState(Context.OPENING_STATE);
// 调用当前状态中的context中的close方法
super.context.open();
}
@Override
public void close() {
System.out.println("电梯关闭..");
}
@Override
public void ran() {
// 修改状态
super.context.setLiftState(Context.RUNNING_STATE);
// 调用当前状态中的context中的close方法
super.context.ran();
}
@Override
public void stop() {
// 修改状态
super.context.setLiftState(Context.STOPPING_STATE);
// 调用当前状态中的context中的close方法
super.context.stop();
}
}
package com.jsxs.behavioralModel.state.after;
/**
* @Author Jsxs
* @Date 2023/4/23 17:19
* @PackageName:com.jsxs.behavioralModel.state.after
* @ClassName: RunningState
* @Description: TODO
* @Version 1.0
*/
public class RunningState extends LiftState{
@Override
public void open() {
}
@Override
public void close() {
}
@Override
public void ran() {
System.out.println("电梯运行...");
}
@Override
public void stop() {
// 修改状态
super.context.setLiftState(Context.STOPPING_STATE);
// 调用当前状态中的context中的close方法
super.context.stop();
}
}
package com.jsxs.behavioralModel.state.after;
/**
* @Author Jsxs
* @Date 2023/4/23 17:19
* @PackageName:com.jsxs.behavioralModel.state.after
* @ClassName: StoppingState
* @Description: TODO
* @Version 1.0
*/
public class StoppingState extends LiftState{
@Override
public void open() {
// 修改状态
super.context.setLiftState(Context.OPENING_STATE);
// 调用当前状态中的context中的close方法
super.context.getLiftState().close();
}
@Override
public void close() {
// 修改状态
super.context.setLiftState(Context.CLOSING_STATE);
// 调用当前状态中的context中的close方法
super.context.getLiftState().close();
}
@Override
public void ran() {
// 修改状态
super.context.setLiftState(Context.RUNNING_STATE);
// 调用当前状态中的context中的close方法
super.context.getLiftState().ran();
}
@Override
public void stop() {
System.out.println("电梯停止了...");
}
}
环境角色
package com.jsxs.behavioralModel.state.after;
/**
* @Author Jsxs
* @Date 2023/4/23 17:14
* @PackageName:com.jsxs.behavioralModel.state.after
* @ClassName: Context
* @Description: TODO 环境角色类
* @Version 1.0
*/
public class Context {
// 定义对应状态对象的常量
public final static OpeningState OPENING_STATE = new OpeningState();
public final static ClosingState CLOSING_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 liftState;
}
// 设置当前状态对象
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
// 设置当前状态对象中的Context对象
this.liftState.setContext(this);
}
// 电梯操作的方法
public void open() {
this.liftState.open();
}
public void close() {
this.liftState.close();
}
public void ran() {
this.liftState.ran();
}
public void stop() {
this.liftState.stop();
}
}
测试
package com.jsxs.behavioralModel.state.after;
/**
* @Author Jsxs
* @Date 2023/4/23 17:37
* @PackageName:com.jsxs.behavioralModel.state.after
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建环境角色
Context context = new Context();
context.setLiftState(new RunningState());
context.open();
context.close();
context.ran();
context.stop();
}
}
(4).使用场景
优点:
- 将所有与某个状态有关的行为放到一个类中,并且可以方便的增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
缺点:
- 状态模式的使用必然会
增加系统类和对象的个数
。 - 状态模式的结构与实现都较为复杂,
如果使用不当将导致程序结构和代码的混乱
。 - 状态模式对"
开闭原则
"的支持不太友好。
使用场景:
当一个对象的行为取决于它的状态,并且他必须在运行时根据状态改变它的行为时
,就可以考虑使用状态模式。- 一个操作中含有庞大的分支结构,并且这些
分支决定对象的状态
。
6.观察者模式
(1).概述
定义:
又被称为发布-订阅模式,它定义了一种一对多
的依赖关系,让多个观察者对象同时监听某一个主题对象
。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
(2).结构
在观察者模式中有如下角色:
- Subject:
抽象主题(抽象被观察者)
,抽象主题角色把所有观察者对象保存一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口
,可以增加和删除观察者对象。 - ConcreateSubject::
具体主题
(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。 - Observer:
抽象观察者
,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知更新自己。 - ConcrereObserver:
具体观察者
,实现抽象观察者定义的更新接口,以便在得到主题更改通知更新自身的状态。
(3).实列实现
eg: 微信公众号
在使用微信公众号时,大家都会有这样的体验。当你关注的公众号中新内容更新的话,它就会推送给关注公众号的微信客户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者
,微信公众号时被观察者
。
抽象主题
package com.jsxs.behavioralModel.observe;
/**
* @Author Jsxs
* @Date 2023/4/23 19:34
* @PackageName:com.jsxs.behavioralModel.observe
* @ClassName: Subject
* @Description: TODO 抽象主题角色类
* @Version 1.0
*/
public interface Subject {
// 添加订阅者/观察者对象
void add(Observer observer);
// 删除订阅者
void detach(Observer observer);
// 通知订阅者更新信息
void notify(String messsage);
}
抽象观察者类
package com.jsxs.behavioralModel.observe;
/**
* @Author Jsxs
* @Date 2023/4/23 19:35
* @PackageName:com.jsxs.behavioralModel.observe
* @ClassName: Observer
* @Description: TODO 抽象观察者类
* @Version 1.0
*/
public interface Observer {
// 公众号推送的内容
void update(String message);
}
具体被观察者
package com.jsxs.behavioralModel.observe;
import java.util.ArrayList;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/4/23 19:37
* @PackageName:com.jsxs.behavioralModel.observe
* @ClassName: SubscriptionSubject
* @Description: TODO 具体主题角色类
* @Version 1.0
*/
public class SubscriptionSubject implements Subject{
// 定义以恶搞集合,用来存储多个观察者对象
private List<Observer> weiXinUserList=new ArrayList<>();
@Override
public void add(Observer observer) {
weiXinUserList.add(observer);
}
@Override
public void detach(Observer observer) {
weiXinUserList.remove(observer);
}
@Override
public void notify(String messsage) {
// 遍历集合
for (Observer observer : weiXinUserList) {
// 调用观察者对象中的update方法
observer.update(messsage);
}
}
}
具体观察者类
package com.jsxs.behavioralModel.observe;
/**
* @Author Jsxs
* @Date 2023/4/23 19:44
* @PackageName:com.jsxs.behavioralModel.observe
* @ClassName: WeiXinUser
* @Description: TODO 具体观察者角色类
* @Version 1.0
*/
public class WeiXinUser implements Observer{
private String name;
public WeiXinUser(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name+" 接收到了 "+message);
}
}
测试
package com.jsxs.behavioralModel.observe;
/**
* @Author Jsxs
* @Date 2023/4/23 19:48
* @PackageName:com.jsxs.behavioralModel.observe
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建公众号对象
SubscriptionSubject subscriptionSubject = new SubscriptionSubject();
// 订阅公众号
subscriptionSubject.add(new WeiXinUser("孙维看"));
subscriptionSubject.add(new WeiXinUser("JSXS"));
subscriptionSubject.add(new WeiXinUser("CKQN"));
//3.公众号更新文章(发布)
subscriptionSubject.notify("你好啊,俺是微信呀");
}
}
(4).使用场景
优点:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 被观察者发送通知,所有驻车的观察者都会收到信息【发布-订阅】
缺点:
- 如果观察者非常多的话,那么所有的观察者受到的被观察者发送的通知会耗时。
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
使用场景:
- 对象间存在一对多的关系,一个对象的状态发生改变会影响其他的对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时。
(5).JDK中提供的实现
在Java中,通过java.util.Observable
和 java.ytil.Observer
接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实列。
7.中介者模式
(1).概述
一般来说,同事之间的关系是比较复杂的,多个同事类之间相互关联时,它们之间的关系会呈现复杂的网状结构,这是一种过渡耦合架构.即不利于类的复用,也不稳定
。列如在图中,有6个对象同事,假如对象1发生变化,那么将会有4个对象发生变化。如果对象2发生变化,那么将会有5个对象发生变化,也就是说: 同事类之间直接关联的设计是不好的
。
如果引入中介者模式
的话,那么同事类之间的关系将变为星型关系,任何一个类的变化只能够影响到本类以及中介者类,这样就降低了系统的耦合
。一个好的设计,必定不会把所有的对象关系处理逻辑分装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。
代理对旧功能的增强、中介直接添加新功能
(2).结构
中介模式包含以下主要角色:
- 抽象中介者角色: 它是
中介者的接口
,提供了同事对象注册与转发同事对象信息的抽象方法。 - 具体中介者角色:
实现中介者接口
,定义一个List
来管理同时对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。 - 抽象同事类角色(记住中介):
定义同事类的接口
,保存中介者对象
,提供同事对象交互的抽象方法,实现所有相互影响的同时类的公共功能。 - 具体同事类角色(记住中介): 是
抽象同事类的实现者
,当需要与其他同时对象交互时,由中介者对象负责后续的交互。
(3).案列实现
eg : 租房
现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。
抽象者中介者
package com.jsxs.behavioralModel.mediator;
/**
* @Author Jsxs
* @Date 2023/4/23 21:31
* @PackageName:com.jsxs.behavioralModel.mediator
* @ClassName: Mediator
* @Description: TODO 抽象中介者
* @Version 1.0
*/
public abstract class Mediator {
public abstract void constact(String message,Person person);
}
抽象同事类
package com.jsxs.behavioralModel.mediator;
/**
* @Author Jsxs
* @Date 2023/4/23 21:33
* @PackageName:com.jsxs.behavioralModel.mediator
* @ClassName: Person
* @Description: TODO 抽象同事类
* @Version 1.0
*/
public abstract class Person {
// 同事姓名
protected String name;
// 聚合中介
protected Mediator mediator;
public Person(String name, Mediator mediator) {
this.name = name;
this.mediator = mediator;
}
}
具体同事类
package com.jsxs.behavioralModel.mediator;
/**
* @Author Jsxs
* @Date 2023/4/23 21:34
* @PackageName:com.jsxs.behavioralModel.mediator
* @ClassName: Tenant
* @Description: TODO 租房者(具体同事类)
* @Version 1.0
*/
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);
}
}
package com.jsxs.behavioralModel.mediator;
/**
* @Author Jsxs
* @Date 2023/4/23 21:39
* @PackageName:com.jsxs.behavioralModel.mediator
* @ClassName: HouseOwner
* @Description: TODO 房东(具体同事)
* @Version 1.0
*/
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);
}
}
中介者模式
package com.jsxs.behavioralModel.mediator;
/**
* @Author Jsxs
* @Date 2023/4/23 21:42
* @PackageName:com.jsxs.behavioralModel.mediator
* @ClassName: MediatorStructure
* @Description: TODO 具体的中介者角色类
* @Version 1.0
*/
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);
}
}
}
测试:
package com.jsxs.behavioralModel.mediator;
/**
* @Author Jsxs
* @Date 2023/4/23 21:46
* @PackageName:com.jsxs.behavioralModel.mediator
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建中介者
MediatorStructure mediatorStructure = new MediatorStructure();
// 创建租房者对象
Tenant tenant = new Tenant("顾客: JSXS", mediatorStructure);
// 创建房主
HouseOwner houseOwner = new HouseOwner("房主: SKQN", mediatorStructure);
// 中介者要知道具体欸都房主和租房者
mediatorStructure.setHouseOwner(houseOwner);
mediatorStructure.setTenant(tenant);
// 交流
tenant.constact("你那里有房子嘛?");
houseOwner.constact("我这里有房子。");
}
}
(4).优缺点
优点:
- 松散耦合: 中介者模式通过把多个同事对象那个之间的交互封装加粗样式到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样依赖,同事对象就可可以独立地变化和复用,而
不再像以前那样"牵移除而动全身了
"。 - 集中控制交互: 多个同事对象地交互,被封装在中介者对象里面几种管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要修改。
-一对多关联转换变为一对一的关联: 没有使用中介者模式的时候,同事对象之间的关系是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变为双向的一对一
,这会让对象的关系更容易理解和实现。
缺点:
- 当同事类太多时,中介者的职责将会很大,他会
变得复杂而庞大,以至于系统难以维护
。
使用场景:
系统中对象之间存在复杂的引用关系
,系统结构混乱且难以理解。- 当想创建一个运行于
多个类之间的对象
,又不想生成新的子类时。
8.迭代器模式
(1).概述
定义:
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示(遍历)。
(2).结构
迭代器模式主要包含以下角色:
- 抽象聚合角色: 定义储存、添加,删除聚合元素以及创建迭代器对象的
接口
。 - 具体聚合角色: 实现抽象聚合类,返回一个具体的迭代器的实列。
- 抽象迭代器角色: 定义
访问和遍历聚合元素的接口
,通常包含 hasNext()、next()等方法。 - 具体迭代器角色:
实现抽象迭代器接口
中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
(3).案列实现:
eg: 定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现,设计到的类如:
Student
package com.jsxs.behavioralModel.iterator;
/**
* @Author Jsxs
* @Date 2023/4/24 12:02
* @PackageName:com.jsxs.behavioralModel.iterator
* @ClassName: Student
* @Description: TODO
* @Version 1.0
*/
public class Student {
private String name;
private String number;
public String getName() {
return name;
}
public Student(String name, String number) {
this.name = name;
this.number = number;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
'}';
}
}
抽象迭代器接口
package com.jsxs.behavioralModel.iterator;
/**
* @Author Jsxs
* @Date 2023/4/24 12:03
* @PackageName:com.jsxs.behavioralModel.iterator
* @ClassName: StudentIterator
* @Description: TODO 抽象迭代器接口
* @Version 1.0
*/
public interface StudentIterator {
// 判断是否还有元素
boolean hasNext();
// 获取下一个元素
Student next();
}
具体迭代器
package com.jsxs.behavioralModel.iterator;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/4/24 12:05
* @PackageName:com.jsxs.behavioralModel.iterator
* @ClassName: StudentInteratorIml
* @Description: TODO 具体迭代器
* @Version 1.0
*/
public class StudentInteratorIml implements StudentIterator{
private List<Student> list;
public StudentInteratorIml(List<Student> list) {
this.list = list;
}
// 记录位置
private int position=0;
@Override
public boolean hasNext() {
return position<list.size(); //判断list集合中是否有元素
}
@Override
public Student next() {
// 从集合中获取指定位置的元素
Student student = list.get(position);
position++;
return student;
}
}
抽象聚合角色
package com.jsxs.behavioralModel.iterator;
/**
* @Author Jsxs
* @Date 2023/4/24 12:16
* @PackageName:com.jsxs.behavioralModel.iterator
* @ClassName: StudentAggregate
* @Description: TODO 抽象聚合角色
* @Version 1.0
*/
public interface StudentAggregate {
// 添加学生的功能
void addStudent(Student student);
//删除学生的功能
void deleteStudent(Student student);
// 获取迭代器的功能
StudentIterator getStudentIterator();
}
具体聚合角色
package com.jsxs.behavioralModel.iterator;
import java.util.ArrayList;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/4/24 12:20
* @PackageName:com.jsxs.behavioralModel.iterator
* @ClassName: StuedntAggergateImi
* @Description: TODO 具体聚合角色
* @Version 1.0
*/
public class StuedntAggergateImi implements StudentAggregate{
private List<Student> list=new ArrayList<Student>();
@Override
public void addStudent(Student student) {
list.add(student);
}
@Override
public void deleteStudent(Student student) {
list.remove(student);
}
@Override
public StudentIterator getStudentIterator() {
return new StudentInteratorIml(list);
}
}
测试
package com.jsxs.behavioralModel.iterator;
/**
* @Author Jsxs
* @Date 2023/4/24 12:34
* @PackageName:com.jsxs.behavioralModel.iterator
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建聚合对象
StuedntAggergateImi stuedntAggergateImi = new StuedntAggergateImi();
// 添加元素
stuedntAggergateImi.addStudent(new Student("JSXS","12122"));
stuedntAggergateImi.addStudent(new Student("JSXS","12122"));
stuedntAggergateImi.addStudent(new Student("JSXS","12122"));
stuedntAggergateImi.addStudent(new Student("JSXS","12122"));
// 遍历聚合对象
// (1).获取迭代器对象
StudentIterator studentIterator = stuedntAggergateImi.getStudentIterator();
// (2).遍历
while (studentIterator.hasNext()){
System.out.println(studentIterator.next().toString());
}
}
}
(4).优缺点
优点:
- 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
- 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
- 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无需修改原有代码,满足 “
开闭原则
” 的要求。
缺点:
增加了类的个数,这在一定程度增加了系统的复杂性。
使用场景:
- 当需要为聚合对象提供多种遍历方式时。
- 当需要遍历不同的聚合结构提供一个统一的接口时。
- 当访问一个聚合对象的内容而无需暴露其内部细节的表示时。
(5).JDK源码解析
迭代器模式在Java的很多集合类被广泛应用。
List<String> list=new ArrayList<>();
Iterator<String> iterator=list.iiterator();
while(iterator.hasNext(){
System.out.print(iterator.next())
}
- List: 抽象聚合类
- ArrayList: 具体的聚合类
- Iterator: 抽象迭代器
- list.iterator(): 返回的实现了
Iterator
接口的具体迭代器对象。
9.访问者模式
(1).概述
定义:
封装一些作用于某种数据结构中的个元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
(2).结构
访问者模式包含以下主要角色:
- 抽象访问者角色: 定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,
他的方法个数理论上来讲与元素个数的实现个数(Element的实现个数)是一样的
,从这点不难看出,访问者模式要求元素类的个数不能改变。 - 具体访问者角色: 给出对每一个元素访问时所产生的
具体行为
。 - 抽象元素角色: 定义了一个
接受访问者的方法
(accept),其意义是指,每一个元素都要可以被访问者访问。 - 具体元素角色:
提供接受访问方法的具体实现
,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。 - 对象结构角色:
定义当中所提到的对象结构
,对象结构是一个抽象描述,具体点可以理解为一个具有容器性质或者符合对象特性的类,它会含有一组元素,并且可以迭代这些元素,供访问者访问。
(3).案列实现
给宠物喂食物:
现在养宠物的人特别多,我们就以这个为列子,当然宠物还分为狗、猫等,要给宠物喂食物的话,主任可以喂食物,其他人也可以喂食物。
- 访问者角色: 给宠物喂食物的人
- 具体访问者角色: 主人、其他人。
- 抽象元素角色: 动物抽象类。
- 具体元素角色: 宠物狗、宠物猫。
- 结构对象角色: 主人家。
抽象访问者角色
package com.jsxs.behavioralModel.vistor;
/**
* @Author Jsxs
* @Date 2023/4/24 13:29
* @PackageName:com.jsxs.behavioralModel.vistor
* @ClassName: Person
* @Description: TODO 抽象访问者角色
* @Version 1.0
*/
public interface Person {
// 喂狗
void feed(Cat cat);
//喂猫
void feed(Dog dog);
}
抽象元素角色
package com.jsxs.behavioralModel.vistor;
/**
* @Author Jsxs
* @Date 2023/4/24 13:30
* @PackageName:com.jsxs.behavioralModel.vistor
* @ClassName: Animal
* @Description: TODO 抽象元素角色
* @Version 1.0
*/
public interface Animal {
// 接受访问者访问的功能
void accept(Person person);
}
具体元素角色
package com.jsxs.behavioralModel.vistor;
/**
* @Author Jsxs
* @Date 2023/4/24 13:30
* @PackageName:com.jsxs.behavioralModel.vistor
* @ClassName: Cat
* @Description: TODO 具体元素角色
* @Version 1.0
*/
public class Cat implements Animal{
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃饭 猫咪");
}
}
package com.jsxs.behavioralModel.vistor;
/**
* @Author Jsxs
* @Date 2023/4/24 13:30
* @PackageName:com.jsxs.behavioralModel.vistor
* @ClassName: Dog
* @Description: TODO 具体元素角色
* @Version 1.0
*/
public class Dog implements Animal{
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃饭 狗");
}
}
具体访问者角色
package com.jsxs.behavioralModel.vistor;
/**
* @Author Jsxs
* @Date 2023/4/24 13:35
* @PackageName:com.jsxs.behavioralModel.vistor
* @ClassName: Owner
* @Description: TODO 具体访问角色
* @Version 1.0
*/
public class Owner implements Person{
@Override
public void feed(Cat cat) {
System.out.println("主人喂猫");
}
@Override
public void feed(Dog dog) {
System.out.println("主人喂狗");
}
}
package com.jsxs.behavioralModel.vistor;
/**
* @Author Jsxs
* @Date 2023/4/24 13:36
* @PackageName:com.jsxs.behavioralModel.vistor
* @ClassName: SomeOne
* @Description: TODO
* @Version 1.0
*/
public class SomeOne implements Person{
@Override
public void feed(Cat cat) {
System.out.println("非主人喂猫");
}
@Override
public void feed(Dog dog) {
System.out.println("非主人喂狗");
}
}
对象结构
package com.jsxs.behavioralModel.vistor;
import java.util.ArrayList;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/4/24 13:38
* @PackageName:com.jsxs.behavioralModel.vistor
* @ClassName: Home
* @Description: TODO 对象结构类
* @Version 1.0
*/
public class Home {
// 声明一个独享
private List<Animal> animals= new ArrayList<Animal>();
// 添加元素的功能
public void add(Animal animal){
animals.add(animal);
}
//
public void action(Person person){
// 遍历集合获取每一个元素
for (Animal animal : animals) {
animal.accept(person);
}
}
}
测试
package com.jsxs.behavioralModel.vistor;
/**
* @Author Jsxs
* @Date 2023/4/24 13:42
* @PackageName:com.jsxs.behavioralModel.vistor
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
Home home = new Home();
home.add(new Cat());
home.add(new Dog());
home.action(new Owner());
}
}
(4).使用场景
优点:
- 扩展性好: 在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好: 通过访问者来定义整个对象结构通用的功能,提高了复用程度。
- 分离无关行为: 通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能比较单一。
缺点:
- 对象结构变化很困难: 在访问这模式中,每增加一个新的元素类,都需要在每一个具体的访问者类总增加响应的具体操作,违背了"开闭原则"
- 违反可依赖导致原则: 访问者模式以来了具体类: 而没有依赖抽象类。
使用场景:
- 对象结构相对稳定,但操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
(6).扩展
访问者模式用到了一种双分派技术。
- 分派:
变量被声明时的类型叫做变量的静态类型
,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型
。比如:Map map=new HashMa,map
变量的静态类型时:map
,实际类型时:hashMap
。根据对象的类型而对方法进行的选择就是分派、分派分为两者: 静态分派和动态分派。 - 静态分派:
发生在编译期间
,分派根据静态类型信息发生。静态分派对于我门来说并不陌生,方法重载就是静态分派
。 - 动态分派:
发生在运动期间
,动态分派的置换某个方法。Java通过方法的重写支持动态分派
。
编译看左边,执行看右边
- 动态分派
package com.tian;
public class Test {
public static void main(String[] args) {
Animal a = new Dog();
a.execute();
Animal a1 = new Cat();
a1.execute();
}
}
class Animal {
public void execute() {
System.out.println("Animal");
}
}
class Dog extends Animal {
@Override
public void execute() {
System.out.println("dog");
}
}
class Cat extends Animal {
@Override
public void execute() {
System.out.println("cat");
}
}
Java编译器在编译时期并不是总是知道那些代码会被执行,因为编译器仅仅知道对象的静态的类型,而不知道对象的真实类型
;而方法的调用则是根据对象的真实类型,而不是静态类型
。
- 静态分派
package com.tian;
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
class Execute {
public void execute(Animal a) {
System.out.println("Animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Test {
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);
}
}
这个结果可能出乎一些人的意料了,为什么呢?
重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。
- 双分派
所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。
package com.tian;
class Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
class Dog extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
class Cat extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
class Execute {
public void execute(Animal a) {
System.out.println("animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Test {
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对象做为参数传递给Anmal类型的变量调用的方法,这里完成第次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也 将自己this作为参数传递进去,这里就完成了第二次分派,这里的Execute类中有多个重载的方法,而传递进行的是this就是具体的实际类型的对象
说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢? 就是可以实现方法的动态绑定我们可以对上面的程序进行修改。
双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。
10.备忘录模式
(1).概述
备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便的找回到一个特定的历史步骤,当新的状态无效或者存在问题的时候,可以使用暂时存储起来的备忘录状态进行恢复,很多软件都提供了撤销操作,如word、记事本,Photoshop、IDEA等软件在编辑时按下 CTRL+Z组合键能够撤销当前操作,使文档恢复之前的状态;还有浏览器的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这一类。
定义:
又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。
(2).结构
备忘录模式的主要角色如下:
- 发起人角色: 记录当前时刻的内部状态信息,提供
创建备忘录
和恢复备忘录数据
的功能,实现其他业务功能,它可以访问备忘录里的所有信息。 - 备忘录角色: 负责人
存储发起人的内部状态
,在需要的时候提供这些内部状态给发起人。 - 管理者角色: 对备忘录进行管理,提供
保存与获取
备忘录的功能,但其不能对备忘录的内容进行访问与修改
。
备忘录有两个等效的接口:
- 窄接口:
管理者对象
(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口
,这个窄接口只允许它把备忘录对象传给其他的对象。 - 宽接口: 与管理者看到的窄接口相反,
发起人
对象可以看到一个宽接口
,这个宽接口允许它读取所有的数据,一边根据这些数据恢复这个发起人对象的内部状态。
(3).案列实现
游戏挑战BOSS
游戏中的某个场景,游戏角色有生命力、攻击力、防御力等数据,在打BOSS前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想(分低)可以让游戏恢复到决斗之前的状态。
要实现上述案列,有两种方式:
- “白箱备忘录模式”
- “黑箱备忘录模式”
- 白箱备忘录模式
备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对多有对象公开。
发起人角色
package com.jsxs.behavioralModel.memento.white_box;
/**
* @Author Jsxs
* @Date 2023/4/24 19:39
* @PackageName:com.jsxs.behavioralModel.memento.white_box
* @ClassName: GanmeRole
* @Description: TODO 游戏角色类 (发起人角色)
* @Version 1.0
*/
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.atk=roleStateMemento.getAtk();
this.vit=roleStateMemento.getVit();
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;
}
}
备忘录角色
package com.jsxs.behavioralModel.memento.white_box;
/**
* @Author Jsxs
* @Date 2023/4/24 19:43
* @PackageName:com.jsxs.behavioralModel.memento.white_box
* @ClassName: RoleStateMemento
* @Description: TODO 备忘录角色
* @Version 1.0
*/
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 RoleStateMemento() {
}
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;
}
}
备忘管理角色
package com.jsxs.behavioralModel.memento.white_box;
/**
* @Author Jsxs
* @Date 2023/4/24 19:50
* @PackageName:com.jsxs.behavioralModel.memento.white_box
* @ClassName: RoleStateCaretaker
* @Description: TODO 备忘录管理对象
* @Version 1.0
*/
public class RoleStateCaretaker {
// 声明备忘录角色
private RoleStateMemento roleStateMemento;
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
}
}
测试类
package com.jsxs.behavioralModel.memento.white_box;
/**
* @Author Jsxs
* @Date 2023/4/24 19:51
* @PackageName:com.jsxs.behavioralModel.memento.white_box
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
System.out.println("----------大战boss前------------");
//创建游戏角色
GameRole gameRole = new GameRole();
//初始化游戏角色
gameRole.initState();
//展示初始化后的游戏状态
gameRole.stateDisplay();
// 将该游戏角色内部状态进行备份
//创建管理者对象
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
System.out.println("----------大战boss后------------");
// 损耗严重
gameRole.fight();
// 大战后状态演示
gameRole.stateDisplay();
// 恢复之前的状态
gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
// 恢复之后展示
gameRole.stateDisplay();
}
}
- 黑箱模式
备忘录角色对发起人对象提供一个宽接口
,而为其它对象提供一个提供一个窄接口
。在Java语言中,实现双重接口的办法就是将备忘录类
设计成为发起人类的内部成员类。
将RoleStateMemento
设为GameRole
的内部类,从而将RoleStateMento
对象封装在GameRloe
里面;在外面提供一个标识接口Memento
给RoleStateCaretaker
及其他对象使用。这样GameRole
类看到的是RoleStateMemento
所有的接口,而RoleStateCaretaker
及其他对象看到的仅仅是标识接口Memento
所有暴露出来的接口,从而维护了封装型。
备忘录接口
package com.jsxs.behavioralModel.memento.black_box;
/**
* @Author Jsxs
* @Date 2023/4/24 20:32
* @PackageName:com.jsxs.behavioralModel.memento.black_box
* @ClassName: Menento
* @Description: TODO 备忘录接口,对外提供窄接口
* @Version 1.0
*/
public interface Menento {
}
package com.jsxs.behavioralModel.memento.black_box;
/**
* @Author Jsxs
* @Date 2023/4/24 19:39
* @PackageName:com.jsxs.behavioralModel.memento.white_box
* @ClassName: GanmeRole
* @Description: TODO 游戏角色类 (发起人角色)
* @Version 1.0
*/
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 Menento saveState(){
return new RoleStateMemento(vit,atk,def);
}
// 恢复角色状态功能
public void recoverState(Menento memento){
RoleStateMemento roleStateMemento=(RoleStateMemento) memento;
// 将备忘录中存储的状态赋值给当前对象的成员
this.atk=roleStateMemento.getAtk();
this.vit=roleStateMemento.getVit();
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;
}
private class RoleStateMemento implements Menento{
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 RoleStateMemento() {
}
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;
}
}
}
测试
package com.jsxs.behavioralModel.memento.black_box;
/**
* @Author Jsxs
* @Date 2023/4/24 19:51
* @PackageName:com.jsxs.behavioralModel.memento.white_box
* @ClassName: Client
* @Description: TODO
* @Version 1.0
*/
public class Client {
public static void main(String[] args) {
System.out.println("----------大战boss前------------");
//创建游戏角色
GameRole gameRole = new GameRole();
//初始化游戏角色
gameRole.initState();
//展示初始化后的游戏状态
gameRole.stateDisplay();
// 将该游戏角色内部状态进行备份
//创建管理者对象
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setMemento(gameRole.saveState());
System.out.println("----------大战boss后------------");
// 损耗严重
gameRole.fight();
// 大战后状态演示
gameRole.stateDisplay();
// 恢复之前的状态
gameRole.recoverState(roleStateCaretaker.getMemento());
// 恢复之后展示
gameRole.stateDisplay();
}
}
(4).优缺点
优点:
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史地状态。
- 实现了内部状态地封装。除了创建它地发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类,发起人不需要管理和把偶你其他内部状态信息过多或者特别频繁,将会占比比较大地内存资源。
缺点:
- 资源消耗大。如果要保存地内部状态信息过多或者特别频繁,将会占用比较大地内存资源。
使用场景:
- 需要保存与恢复数据地场景,如玩游戏时地中间结果地存放功能。
- 需要提供一个可回滚地场景,如 word、记事本、等软件地ctrl+z
11.解释器模式
(1).概述
设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应地加法和减法地工具方法
定义: 给定一个语言,它定义地文法标识,并定义一个解释想起,这个解释器使用该标识来解释语言中地句子
。
(2).结构
- 抽象表达式角色: 定义
解释器地接口
,约定解释器地解释操作,主要包含解释方法 interpret(), - 终结符表达角色: 是抽象表达式地子类,用来实现文法中与中介符相关地操作,文法中地每一个终结符都有一个具体中介表达式与之相对应。
- 非总结符表达式角色:也是抽象表达式地子类,用来实现文法中与非终结符相关地操作,文法中地每条规则都对应一个非终结符表达式。
- 环境角色:通常包含各个解释器需要地数据或是公共地功能,一般用俩传递被所有解释器共享地数据,后面地解释器可以从这里获取这些值。
- 客户端: 主要任务是将需要分析地句子或表达式转换成使用解释器对象描述地抽象语法树,然后调用解释器地解释方法,当然也可以通过环境角色简介访问解释器地解释方法。
(3).优缺点
优点:
- 易于改变和扩展文法:
- 实现文法较为容易: 代码编写不会特别复杂
- 增加新地解释表达式较为方便: 符合开闭原则
缺点:
- 对于复杂文法难以维护
- 执行效率较低
使用场景:
- 当语言的文法较为简单,且自信效率不是关键问题
- 当问题重复出现,且可以用一种简单的语言来进行表达时
- 当一个语言需要解释执行,并且语言中的句子可以标识为一个抽象语法树的时候。
版权声明:本文标题:110.【十万字带你深入学习23种设计模式】 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1715924200h657995.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论