JAVA设计模式
1.设计模式相关内容介绍
本篇笔记对于JAVA23中设计模式,记录下来了21种,剩下的两种个人认为耦合度很高,看起来就很不舒服就没在本篇本章中做出记录
1.1设计模式概述
- 设计模式,是一套被反复使用,多数人知晓的,经过分给编目的,代码设计经验的总结,它描述了软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案,也就是说,它是解决特定问题的一系列套路,具有一定的普遍性,可以反复使用。
- 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性,继承性,和多态性以及类的关联关系和组合关系的充分理解。
掌握设计模式可以提高程序员的思维能力,编程能力和设计能力
使程序更加标准化,代码编制更加工业化,使软件开发效率大大提高,继承和多态性以及类的关联关系和组合关系的充分理解
1.1.1 设计模式的分类
- 创建型模式
用于描述怎样创建对象,它的主要特征是将对象的创建与使用分离,GoF(四人组)书中提供了单例,原型,工厂方法,抽象工厂,建造者等5种创建型模式 - 结构型模式
用于描述如何将类或者对象按某种布局组成更大的结构,GoF(四人组)中提供了代理,适配器,桥接,外观,享元,组合等7中结构型模式 - 行为型模式
用于描述类或者对象之间怎样相互协作,共同完成单个对象无法独立完成的任务,以及怎样分配职责GoF(四人组)书中提供了磨板方法,策略,命令,职责链,状态,观察者,中介者,迭代器,访问者,备忘录,解释器等11中行为型模式
1.2类图
在软件工程中,类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化人们对系统的理解
类图是系统分析和设计阶段的产物,是系统测试和编码的重要模型
1.1.1 类的表示方式
类使用包含类名,属性,和方法,且带有分割线的矩形来表示,如图下的Employee类,它包含name,age和address这三个属性以及worf()方法
属性/方法名称前加的加号和减号表示了这个属性/方法的可见性,表示可见性的符号有三种
+:表示public
-:表示private
#:表示protected
属性的完整表示方式是:可见性 名称 :类型 [ =缺省值]
方法的完整表示方式是: 可见性 名称(参数列表)返回值类型
1.2.2 类和类之间的表达方式
1.2.2.1 关联关系
关联关系是对象常用的一种引用关系,用于表示一类对象和另一类对象的联系,如:学生和老师,师傅和徒弟,妻子和丈夫等,关联关系是类与类中最常见的一种关系
- 1.单向关联
如图,单向关联用一个箭头表示,上图中每一个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现 - 2.双向关联
双向关联是各吃持有对象的变量为双向关联
双向关联用一个不带箭头的直线表示,上图中在Customer类中维护了一个List< Produce> ,表示一个顾客可以购买多个商品,在Product中维护了一个顾客,双方都是互相关联的 - 自关联
- Node类包含了自己,链表类型就是用这种表示的
1.2.2.2 聚合关系
- 聚合关系是关联关系的一种,是强关联关系,是整体与部分之间关联
- 聚合关系也是通过成员变量来实现的,其中成员变量是整体对象的一部分,但是成员对象可以脱离整体而独立存在,例如学校和老师,学校包含老师,如果学校停办了老师依然存在,
- 聚合关系使用空心菱形实现来表示,菱形指向整体
1.2.2.3 组合关系
- 组合表示类之间的整体与部分之间的关系,但它是一种跟强烈的聚合关系
- 在组合对象中整体对象可以控制部分对象的生命周期,一旦整体对象不在了,部分对象也将不在,部分对象不能脱离整体对象而存在,头和嘴的关系,头没有了,嘴也不复存在
1.2.2.4 依赖关系
- 依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性关联,在代码中某个类的方法通过局部变量,方法的参数或者对静态方法的调用来访问另一个类中某些方法来完成一个职责
- 依赖关系使用带箭头的虚线表示,箭头从使用类指向被依赖的类。引用关系
1.2.2.5 继承关系
- 继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类的一种关系是一种继承关系
- 泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类,代码实现时,使用面向对象的继承机制来实现泛化关系,例如Student和Teacher都是Persion的子类
1.2.2.6 实现关系
- 实现关系是接口与实现类之间的关系,在这种关系中类实现了接口,类中的操作实现了接口中所声明的所有抽象操作。
- 使用空心三角虚线来表示,箭头从实现类指向了接口,例如汽车和船实现了交通工具
1.3软件设计原则
- 在软件开发中,为了提高软件系统的可维护性和复用性,增加软件和可扩展和灵活性,程序员要尽量根据6条原则来开发程序,从而提高软件开发效率,节约软件开发成本
1.3.1 开闭原则
对扩展开放,对修改关闭
在程序需要进行扩展的时候,不能去修改原有的代码,实现一个热插拔的效果,简言之,是为了使程序的扩展性好,易于维护和升级- 想要达到这样的效果我们需要使用接口和抽象类
- 因为抽象灵活性好,适应性广,只要抽象的合理,介意基本保持软件架构的稳定,从软件中易变的细节可以从抽象派生来的实现类进行扩展,当软件需要发生改变时,只需要重新派生一个实现类来扩展就好了
通过车案例说明
- 一个抽象类,车,aodi,benchi,baoma,实现了车重写了价格方法,客户client想买车,只需要去4s店里面告诉他你想要的车型号,他就会帮你查出价格,除了这三辆车,想要在添加也不需要修改源代码,只需要在添加一个实现类即可
1.3.2 里式代换原则
- 里式代换原则是面向对象设计的基本原则之一
- 里式代还原则:任何基类可以出现的地方,子类一定可以出现,通俗理解:子类可以扩展父类的功能,但不改变父类原有的功能,换句话说,除添加新的方法完成新增功能外,尽量不要重写父类的方法
- 例:正方形不是长方形
- 在数学领域中,正方是是长方形,因为正方形的四个边都相等,所以正方形是长方形的一种特殊情况。
- 以下是一个例子,需要做一个正方形扩容的方法,判断宽<长的话宽+1,传参使用长方形,虽说正方形也是长方形,但是正方形四条边相等,是无法进入到这个while循环的
1.3.3 依赖倒转原则
高层模块不应该依赖底层模块,两者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象,简单说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块的耦和
- 通过案例演示:
- 现在要组装电脑,需要配件cpu,内存条,硬盘只有这些配件都有了,计算机才能正常的运行,选择cpu有很多的选择,如Inter,ADM等,硬盘可以选择希捷,西数等内存条可以选择金士顿,海盗船等。
类图如下 - 电脑中对以下指定配件进行引用,编写getset方法,用户在测试类中实例化配件,实例化电脑,将配件传给电脑就可以运行了
- 上述关系图是可以实现功能的,但是组装电脑貌似只能用InterCpu,呢欧村条只能用金士顿的,硬盘只能用希捷的,这对用户不友好,用户肯定是想自己组装电脑的时候用上自己喜欢的配件,但是用喜欢的配件就得需要更改代码了,违反了开闭原则,于是就衍生下面这张图,定义接口,用户想用什么cpu就新开一个类去继承接口。
1.3.4 接口隔离原则
- 客户端不应该被迫依赖于它不使用的方法,一个类对于另一个类的依赖应该建立在最小的接口上
- 在例:
- 改进如下,我现在想实现哪种就实现哪种再也不用担心,他有3个功能,我就想实现1个还要被迫的实现另外两个
1.3.5 合成复用原则
合成复用原则指的是:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
通常类的复用分为继承复用和合成复用两种
继承复用虽然有简单易实现的优点,但是它也存在以下缺点
- 继承复用破坏了类的封装性,因此继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又被称为白箱复用
- 子类和父类的耦合度高,父类实现的热河改变都会导致子类的实现发生变化,这不利于维护类的扩展和维护
- 它限制了复用的灵活性,从父类继承而来的实现是静态的,在编译时候就已定义,不会发生改变
- 举例说明
汽车按照动力源可以分为:汽油汽车,电动汽车,等,按颜色划分可以分为白色汽车,红色汽车,黑色汽车等,如果同时考虑这两种就会产生如下:
- 可以更改为颜色接口,有对应的颜色实现类,定义一个Car类,引颜色接口,并且Car类有两个实现类
2.创建者模式
- 创建者模式主要关注的是怎样创建对象,它的主要特点是将对象的创建和使用分离
- 创建型模式分为:
- 单例模式
- 工厂方法模式
- 抽象工厂模式
- 原型模式
- 建造者模式
2.1单例模式
- 单例模式,是java中最简单的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式
- 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保有单个对象被创建,这个类提供了唯一一的对象方式可以直接实例化对象,不需要被创建
- 单例模式主要有以下角色:
- 单例类:只能创建一个对象的实例
- 访问类:使用单例类
2.1.1 单例模式的实现
单例模式分为懒汉式和饿汉式
饿汉式:类加载就会导致该单例对象被创建
懒汉式:类加载不会导致该单例对象被创建,而是首次使用对象才会被创建
- 饿汉式1:静态变量方式
/**
* @author LXY
* @desc 饿汉式,类加载的时候就会实例化
* @time 2023--12--22--9:54
*/
public class Singletons {
//私有构造方法,这样外部就无法通过构造方法来实例化对象
private Singletons(){}
//在本类中创建对象
private static Singletons singletons=new Singletons();
//外部可以通过对象.方法的方式拿到创建的实例对象
public static Singletons getInstance(){
return singletons;
}
}
public class Client {
public static void main(String[] args) {
Singletons instance = Singletons.getInstance();
Singletons instance2 = Singletons.getInstance();
System.out.println(instance==instance2); //结果为true证明是一个对象
}
}
- 饿汉式2:静态代码块方式
/**
* @author LXY
* @desc 饿汉式通过静态代码块
* @time 2023--12--22--10:02
*/
public class Singgletons2 {
//私有化
private Singgletons2(){}
private static Singgletons2 singgletons2=null;
static {
singgletons2=new Singgletons2();
}
//外部可以通过对象.方法的方式拿到创建的实例对象
public static Singgletons2 getInstance(){
return singgletons2;
}
}
ps:静态变量会随着类的加载而创建,这样操作如果加载了一直不用势必会造成内存的浪费
- 单例模式
/**
* @author LXY
* @desc 单例模式
* @time 2023--12--22--10:14
*/
public class Singletons3 {
//私有化构造器
private Singletons3(){}
//定义静态变量,并且使用volatile,volatile可以保证在多线程环境下的可见性和有序性
private static volatile Singletons3 Instance=null;
//初始化方法
public static Singletons3 getInstance() {
//双重检查的方式,既有了加锁安全的方式又不会造成因为每个线程都上锁的堵塞
if (Instance==null){
//对Instance是null的情况进行加锁,是写操作保证线程的安全
synchronized (Singletons3.class){
if (Instance==null){
Instance= new Singletons3();
}
}
}
return Instance;
}
}
- 单例模式静态内部类的实现
/**
* @author LXY
* @desc 懒汉式,静态内部类实现
* @time 2023--12--22--10:24
*/
public class Singletons4 {
private Singletons4(){}
private static class SingletonsHead{
private final static Singletons4 SINGLETONS=new Singletons4();
}
public static Singletons4 getInstance(){
return SingletonsHead.SINGLETONS;
}
}
2.1.2 序列化和反射会破坏单例模式
- 以下通过读文件流,写文件流,最后读的两个对象地址值是不一样的
/**
* @author LXY
* @desc
* @time 2023--12--22--10:42
*/
public class Demo5 {
public static void main(String[] args) throws Exception{
// writeSingletons();
readSingletons();
readSingletons();
}
//读文件流
private static void readSingletons() throws Exception {
final ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("C:\\Users\\20505\\Desktop\\ctg\\5.txt"));
Singletons4 instance = (Singletons4) objectInputStream.readObject();
System.out.println(instance);
objectInputStream.close();
}
//写文件流
private static void writeSingletons() throws Exception {
final Singletons4 instance = Singletons4.getInstance();
final ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C:\\Users\\20505\\Desktop\\ctg\\5.txt"));
//写入对象
objectOutputStream.writeObject(instance);
//释放资源
objectOutputStream.close();
}
}
-
理由:writeObject是没有问题的,readObject也可以成功的读取到对象,但是
-
readObject底层他读出对象之后会判断对象中又没有readResolve()方法,如果有这个方法就返回里面的值,如果没有就返回一个新的对象,这就是为什么两次读出来的数据是不一样的
-
代码改正
/**
* @author LXY
* @desc 懒汉式,静态内部类实现
* @time 2023--12--22--10:24
*/
public class Singletons4 implements Serializable {
private Singletons4(){}
private static class SingletonsHead{
private final static Singletons4 SINGLETONS=new Singletons4();
}
public static Singletons4 getInstance(){
return SingletonsHead.SINGLETONS;
}
public Object readResolve(){
return SingletonsHead.SINGLETONS;
}
}
- 最后的结果是正确的
1.2.3 Runtime饿汉式
- Runtime是java.long包下的一个类,是饿汉式的
2.3工厂方法模式
- 点咖啡案例
设计一个咖啡店(Coffee)并定义两个子类,(美式咖啡(AmericanCoffee)和拿铁咖啡(LatteCoffee));在设计一个咖啡店(CoffeeStore),咖啡店具有点咖啡的功能
- 代码实现
/**
* @author LXY
* @desc 咖啡的抽象类
* @time 2023--12--22--15:33
*/
public abstract class Coffee {
//咖啡名字
abstract String getName();
public void addMilk(){
System.out.println("加牛奶");
}
public void addsugar(){
System.out.println("加糖");
}
}
/**
* @author LXY
* @desc 美式咖啡
* @time 2023--12--22--15:35
*/
public class AmericanCoffee extends Coffee{
@Override
String getName() {
return "我是美式咖啡";
}
}
/**
* @author LXY
* @desc拿铁咖啡
* @time 2023--12--22--15:36
*/
public class LatteCoffee extends Coffee{
@Override
String getName() {
return "我是拿铁咖啡";
}
}
/**
* @author LXY
* @desc 咖啡店
* @time 2023--12--22--15:36
*/
public class CoffeeStore {
Coffee coffee=null;
public Coffee orderCoffee(String type){
if (type.equals("american")){
coffee=new AmericanCoffee();
}else if (type.equals("latte")){
coffee=new LatteCoffee();
}else {
throw new RuntimeException("对不起咖啡点完了");
}
//加糖,加牛奶
coffee.addsugar();
coffee.addMilk();
return coffee;
}
}
/**
* @author LXY
* @desc 测试类
* @time 2023--12--22--15:39
*/
public class Client {
public static void main(String[] args) {
//实例化咖啡店
CoffeeStore coffeeStore=new CoffeeStore();
//向咖啡店要一杯美式咖啡
final Coffee coffee = coffeeStore.orderCoffee("american");
System.out.println(coffee.getName());
}
}
- 在java中万物皆对象这些对象都需要创建,如果创建的时候直接new对象,会让对象的耦合严重,假如我们要更换对象,所有new对象的地方都要修改一遍,这显然违背了开闭原则,如果我们使用工厂来生产对象,沃恩就之和工厂就可以了,彻底和对象解耦,如果要更换对象,直接在工厂历更换对象即可,达到了对象的解耦目的,所哟说工厂模式最大的优点就是解耦
2.3.1简单工厂模式
- 简单工厂其实更像是一种习惯并不属于规范的工厂模式,有些人为了方便会这样书写,
- 代码如下,对咖啡店进行代码修改(CoffeeStore ),新增咖啡工厂类SimpleCoffeeFactory ,简单工厂实现了咖啡店的解耦,但是并没有咖啡工厂类还继续存在耦合,还需要继续修改,
/**
* @author LXY
* @desc 咖啡店
* @time 2023--12--22--15:36
*/
public class CoffeeStore {
Coffee coffee=null;
public Coffee orderCoffee(String type){
//调用咖啡简单工厂模式,
coffee=SimpleCoffeeFactory.getinterFactor(type);
//加糖,加牛奶
coffee.addsugar();
coffee.addMilk();
return coffee;
}
}
/**
* @author LXY
* @desc 咖啡工厂类,
* @time 2023--12--22--15:46
*/
public class SimpleCoffeeFactory {
public static Coffee getinterFactor(String type){
if (type.equals("american")){
return new AmericanCoffee();
}else if (type.equals("latte")){
return new LatteCoffee();
}else {
throw new RuntimeException("对不起咖啡点完了");
}
}
}
- 简单工厂优缺点:
- 优点:封装了创建对象的过程,可以通过参数直接获取对象,把对象的创建和业务逻辑分开,这样就避免了直接修改客户代码,
- 缺点:违背了开闭原则,增加产品的时候还需要修改工厂的代码
2.3.2 工厂方法模式
- 针对于上面的问题可以使用工厂方法模式可以完美解决问题并且遵守开闭原则
- 定义一个用于创建对象的接口,让子类决定实例化哪个产品对象,工厂方法使一个产品类的实例化延迟到其工厂的子类
- 抽象工厂(Abstract Factory)提供了创建产品的接口,调用者可以通过它访问具体工厂的工厂方法来创建产品
- 具体工厂(ConcreleFactory)主要是实现抽象工厂中的抽象方法,完成具体产品的创建
- 抽象产品(Product)定义了产品的规范,描述了产品的主要特性和习惯
- 具体产品(ConCreteProduct)实现了抽象产品角色所定义的接口,由具体工厂来创建,它与具体工厂一 一对应
/**
* @author LXY
* @desc 咖啡的抽象类
* @time 2023--12--22--15:33
*/
public abstract class Coffee {
//咖啡名字
abstract String getName();
public void addMilk(){
System.out.println("加牛奶");
}
public void addsugar(){
System.out.println("加糖");
}
}
/**
* @author LXY
* @desc 美式咖啡
* @time 2023--12--22--15:35
*/
public class AmericanCoffee extends Coffee{
@Override
String getName() {
return "我是美式咖啡";
}
}
/**
* @author LXY
* @desc拿铁咖啡
* @time 2023--12--22--15:36
*/
public class LatteCoffee extends Coffee{
@Override
String getName() {
return "我是拿铁咖啡";
}
}
/**
* @author LXY
* @desc 接口
* @time 2023--12--22--16:02
*/
public interface CoffeeFactory {
Coffee createCoffeeFactory();
}
/**
* @author LXY
* @desc
* @time 2023--12--22--16:03
*/
public class AmericanCoffeeFactory implements CoffeeFactory{
@Override
public Coffee createCoffeeFactory() {
return new AmericanCoffee();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--22--16:04
*/
public class LatteCoffeeCoffeeFactory implements CoffeeFactory{
@Override
public Coffee createCoffeeFactory() {
return new LatteCoffee();
}
}
/**
* @author LXY
* @desc 咖啡店
* @time 2023--12--22--15:36
*/
public class CoffeeStore {
CoffeeFactory coffeeFactory=null;
public void setCoffee( CoffeeFactory coffeeFactory) {
this.coffeeFactory = coffeeFactory;
}
public Coffee orderCoffee(){
Coffee coffee = this.coffeeFactory.createCoffeeFactory();
//加糖,加牛奶
coffee.addsugar();
coffee.addMilk();
return coffee;
}
}
/**
* @author LXY
* @desc 测试类
* @time 2023--12--22--15:39
*/
public class Client {
public static void main(String[] args) {
//实例化咖啡店
CoffeeStore coffeeStore=new CoffeeStore();
//向咖啡店要一杯美式咖啡
coffeeStore.setCoffee(new AmericanCoffeeFactory());
final Coffee coffee = coffeeStore.orderCoffee();
System.out.println(coffee.getName());
}
}
2.3.3 抽象工厂模式
- 前面的介绍的工厂模式只适用于一系列产品的生产,比如畜牧业只养牲畜,电视机厂家只生产电视机,笔记本厂家只能生产笔记本 等等,
- 这些工厂只生产同类的产品,同种类产品称之为同级产品,也就是说:工厂方法模式只考虑同等级的产品,但是在现实生活中许多工厂是综合性工厂,能生产多等级(种类)的产品,如果电器成产厂家又生产洗衣机或者空调,大学既有计算机专业还有其他专业等,如果不同等级的产品还用工行模式来生产会产生类爆炸,即创建的类特别多
- 是一种类访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无需指定所要产品的具体类就能得到同族的不同等级的产品模式结构
- 抽象工厂模式是工厂模式的升级版本,工厂模式只适用于生产同一级别的产品,抽象工厂模式可生产多个等级的产品
- 现咖啡店的业务发生了变化,不仅要生产咖啡还要生产甜点,如提拉米苏,抹茶慕斯等等,要是按照工厂模式需要创建提拉米斯类,莫斯蛋糕类,提拉米苏工厂,抹茶慕斯工厂,甜点工厂,很容易发生类爆炸的情况,其中拿铁卡得,美式咖啡是一个产品等级的咖啡,提拉米苏,抹茶蛋糕又是一个产品等级,所以这个案例可以这样写拿铁咖啡和提拉米苏是一个产品族,(都是意大利风味),美式咖啡和抹茶慕斯也是一个产品族(属于美式风味)
- 代码实现
/**
* @author LXY
* @desc 咖啡的抽象类
* @time 2023--12--22--15:33
*/
public abstract class Coffee {
//咖啡名字
abstract String getName();
public void addMilk(){
System.out.println("加牛奶");
}
public void addsugar(){
System.out.println("加糖");
}
}
/**
* @author LXY
* @desc 美式咖啡
* @time 2023--12--22--15:35
*/
public class AmericanCoffee extends Coffee {
@Override
String getName() {
return "我是美式咖啡";
}
}
/**
* @author LXY
* @desc拿铁咖啡
* @time 2023--12--22--15:36
*/
public class LatteCoffee extends Coffee {
@Override
String getName() {
return "我是拿铁咖啡";
}
}
/**
* @author LXY
* @desc 甜点
* @time 2023--12--22--16:47
*/
public abstract class Dessert {
abstract String getName() ;
}
/**
* @author LXY
* @desc
* @time 2023--12--22--16:48
*/
public class TimisuDessert extends Dessert{
@Override
String getName() {
return "我是提拉米苏";
}
}
/**
* @author LXY
* @desc
* @time 2023--12--22--16:49
*/
public class MoMusiDessert extends Dessert{
@Override
String getName() {
return "抹茶慕斯";
}
}
/**
* @author LXY
* @desc 风味,风味接口
* @time 2023--12--22--16:55
*/
public interface DessertFacory {
Coffee createcoffee();
Dessert createDesser();
}
/**
* @author LXY
* @desc
* @time 2023--12--22--16:57
*/
public class ItalyDessertFactory implements DessertFacory{
@Override
public Coffee createcoffee() {
return new LatteCoffee();
}
@Override
public Dessert createDesser() {
return new TimisuDessert();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--22--16:56
*/
public class AmericanDessrtFactory implements DessertFacory{
@Override
public Coffee createcoffee() {
return new AmericanCoffee();
}
@Override
public Dessert createDesser() {
return new MoMusiDessert();
}
}
/**
* @author LXY
* @desc 测试类
* @time 2023--12--22--15:39
*/
public class Client {
public static void main(String[] args) {
ItalyDessertFactory italyDessertFactory=new ItalyDessertFactory();
System.out.println("italyDessertFactory.createcoffee().getName() = " + italyDessertFactory.createcoffee().getName());
System.out.println("italyDessertFactory.createcoffee().getName() = " + italyDessertFactory.createDesser().getName());
}
}
- 优缺点:
- 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端只使用同一个产品族的对象
- 缺点:当需要增加一个新产品的时候,所有的工厂类都需要更改
2.3.4 反射+读配置文件实现解耦
可以通过工厂迷失+配置文件的方式接触工厂对象和产品对象的耦合,在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接获取即可
american=com.rj.jand.patten.propper.AmericanCoffee
latte=com.rj.jand.patten.propper.LatteCoffee
/**
* @author LXY
* @desc 咖啡店
* @time 2023--12--22--15:36
*/
public class CoffeeStore {
//存储咖啡对象
private static HashMap<String,Coffee>map=new HashMap<>();
//通过读取配置文件+反射获取
static {
//加载Properties对象
Properties p=new Properties();
InputStream is = CoffeeStore.class.getClassLoader().getResourceAsStream("bean.properties");
try {
//加载读取
p.load(is);
Set<Object> keySet = p.keySet();
for (Object o : keySet) {
//通过反射拿到自己写的类名字(String)
String ClassName = p.getProperty((String) o);
//通过反射技术获取对象
final Class<?> aClass = Class.forName(ClassName);
final Coffee coffee = (Coffee)aClass.newInstance();
map.put((String)o, coffee);
}
}catch (Exception e){
}
}
public Coffee orderCoffee(String type){
return map.get(type);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--22--17:13
*/
public class Client {
public static void main(String[] args) {
CoffeeStore coffeeStore=new CoffeeStore();
final Coffee coffee = coffeeStore.orderCoffee("american");
System.out.println(coffee.getName());
final Coffee coffeee = coffeeStore.orderCoffee("latte");
System.out.println(coffeee.getName());
}
}
2.3.5 iterator 使用工厂类
List list=new ArrayList();
list.add("1");
list.add("2");
final Iterator iterator = list.iterator();
-
使用迭代器遍历集合,获取集合中的元素而单列集合获取迭代器就使用到了工厂模式,我们看看类结构
-
Collection作为list的顶层父接口,引用了Iterator
-
我们现在是实现的是ArrayList,他的父类是List,list中又一个 Iterator iterator();的方法
-
ArrayList作为他的子类,实现了这个方法,返回了一个内部实现类,
2.4原型模式
- 用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象
- 原型对象包含如下角色:
- 抽象圆形类:规定了具体原型对象必须实现的方法clone()方法
- 具体原型类:实现抽象原型类的clone()方法,它是可被复制的对象
- 访问类:使用具体原型类中clone()方法来复制新的对象
2.4.1原型模式实现
- 原型模式分为浅克隆和深克隆
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的内存地址
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不在指向原有内存地址
Java中的object类中提供了clone()方法来实现浅克隆,cloneable接口是上面的类图中的抽象原型类,而实现了clineable接口的子实现类
就是具体的原型类,代码如下
/**
* @author LXY
* @desc
* @time 2023--12--23--14:33
*/
public class Certificate implements Cloneable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("恭喜本年度"+getName()+"荣获三好学生奖状,特发此证,以资鼓励");
}
@Override
protected Certificate clone() throws CloneNotSupportedException {
return (Certificate) super.clone();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--23--14:36
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
//第一个对象
Certificate certificate=new Certificate();
//第二个帝乡
Certificate clone = certificate.clone();
certificate.setName("张三");
clone.setName("李四");
certificate.show();
clone.show();
}
}
- 目前使用的是浅克隆,如果被克隆体里面的属性是对象的话,克隆出来的对象还是指向同一个内存地址:看代码
/**
* @author LXY
* @desc
* @time 2023--12--23--14:33
*/
public class Student implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* @author LXY
* @desc
* @time 2023--12--23--14:33
*/
public class Certificate implements Cloneable{
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public void show(){
System.out.println("恭喜本年度"+getStudent().getName()+"荣获三好学生奖状,特发此证,以资鼓励");
}
@Override
protected Certificate clone() throws CloneNotSupportedException {
return (Certificate) super.clone();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--23--14:36
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
//第一个对象
Certificate certificate=new Certificate();
Student student = new Student();
student.setName("张三");
certificate.setStudent(student);
//第二个帝乡
Certificate clone = certificate.clone();
clone.getStudent().setName("李四");
certificate.show();
clone.show();
}
}
2.4.2深克隆
通过ObjectOutputStream和ObjectInputStream对象读写流,将对象进行深克隆
package com.rj.jand.patten.prototype;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* @author LXY
* @desc
* @time 2023--12--23--14:36
*/
public class Client {
public static void main(String[] args) throws Exception{
//第一个对象
Certificate certificate=new Certificate();
Student student = new Student();
student.setName("张三");
certificate.setStudent(student);
//写对象操作
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C:\\Users\\20505\\Desktop\\ctg\\a.txt"));
objectOutputStream.writeObject(certificate);
//读对象操作
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("C:\\Users\\20505\\Desktop\\ctg\\a.txt"));
Certificate clone = (Certificate) objectInputStream.readObject();
clone.getStudent().setName("李四");
certificate.show();
clone.show();
//关流
objectOutputStream.close();
objectInputStream.close();
}
}
2.5 建造者模式
- 将一个复杂的对象的构建与表示进行分离,使得同样的构建过程可以使用创建不同的表示
- 分离了部件的构造(由Builder来负责)和装配(由Director负责),从而可以构造出复杂的对象,这个模式适用于:某个对象的构造过程和复杂的情况
- 由于实现了构建和装配的解耦,不同的构造器,相同的配置,也可以做出不同的对象,相同的构建起,不同的装配顺序也可以做出不同的对象,也就是实现了构建算法,装配算法的解耦,实现了更好的复用
- 建造者模式可以将部件和其组装过程分类,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无需知道内部的具体构造细节
- 建造者(Builder)模式包含如下角色
- 抽象建造者类(Buider):这个接口规定要实现复杂对象的那些部分的创建,而不涉及具体的对象部件的创建
- 具体建造者类(ConcreteBuilder):实现Builder接口。完成复杂产品的各个部件的具体创建方法,在构造过程玩成狗,提供产品的实例
- 产品类(Produce):要创建的复杂对象
- 指挥者类(Diector)调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分按照某种顺序创建
2.5.1 建造者模式的使用
创建共享单车
生产自行车是一个复杂的付过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质,车座又有橡胶,真皮等材质对于自行车的生产可以使用建造者模式
/**
* @author LXY
* @desc 实体
* @time 2023--12--23--15:36
*/
public class Bilke {
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;
}
@Override
public String toString() {
return "Bilke{" +
"frame='" + frame + '\'' +
", seat='" + seat + '\'' +
'}';
}
}
/**
* @author LXY
* @desc
* @time 2023--12--23--15:38
*/
public abstract class Builder {
protected Bilke bilke=new Bilke();
public abstract void builderFrame();
public abstract void builderSear();
public abstract Bilke createBilke();
}
/**
* @author LXY
* @desc 哈罗创建
* @time 2023--12--23--15:40
*/
public class MoBikeBuilder extends Builder{
@Override
public void builderFrame() {
bilke.setFrame("碳纤维");
}
@Override
public void builderSear() {
bilke.setSeat("橡胶");
}
@Override
public Bilke createBilke() {
return bilke;
}
}
/**
* @author LXY
* @desc
* @time 2023--12--23--15:42
*/
public class OfoBuilder extends Builder{
@Override
public void builderFrame() {
bilke.setFrame("铝合金");
}
@Override
public void builderSear() {
bilke.setSeat("真皮");
}
@Override
public Bilke createBilke() {
return bilke;
}
}
/**
* @author LXY
* @desc
* @time 2023--12--23--15:43
*/
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public Bilke construct(){
builder.builderFrame();
builder.builderSear();
return builder.createBilke();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--23--15:45
*/
public class Cilent {
public static void main(String[] args) {
Director director=new Director(new MoBikeBuilder());
final Bilke construct = director.construct();
System.out.println(construct);
}
}
- 缺点:
- 建造者模式的封装性很好,使用建造者模式可以有效的封装变化,在使用建造模式的场景中,一般产品类和建造类是比较稳定的,因此,将主要的业务逻辑封装在指挥类中对整体而言可以去的更好的稳定性
- 在建造者模式中,客户端不必知道产品组成的细节,将产品本身与产品的创建过程进行解耦,使得相同的创建过程可以创建不同的产品对象
- 可以更加精细的控制产品的创建过程,将复杂的产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
- 建造者模式也很容易进行扩展,如果有新的需求,通过实现一个新的建造者就可完成,基本上不用修改之前已经测试通过的代码,因此也讲究不会对原有功能引入风险,符合开闭原则
- 缺点:
建造者模式所创建的产品一般具有比较多的共同点,其在组成部分像素,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用规范受到了一定的限制
2.5.2 模式扩展(链式调用)
- 平常这样写很不美观,也很不优雅,不装b,下面使用建造者模式将其变为链式调用
/**
* @author LXY
* @desc
* @time 2023--12--23--15:54
*/
public class Phone implements Serializable {
private String cpu;
private String screen;
private String memory;
private String mainboard;
private Phone(String cpu, String screen, String memory, String mainboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainboard = mainboard;
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
public static final class Builder{
private String cpu;
private String screen;
private String memory;
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 memory(String memory){
this.memory=memory;
return this;
}
public Builder mainboard(String mainboard){
this.mainboard=mainboard;
return this;
}
public Phone bilder(){
return new Phone(this.cpu,this.screen,this.memory,this.mainboard);
}
}
}
2.5.3 创建者模式对比
- 工厂方法模式vs 建造者模式
- 工厂模式注重的是整体对象的创建方式,而建造者模式注重的是部件构造的过程,以在通过一步步的精确构造对象
- 我们举个栗子来说明两者的差异,如果要制造一个超人,如果使用工厂方法模式,直接生产出来的就是一个力大无穷,能够飞翔,内裤外穿的超人,如果使用建造者模式,则需要组装手,头,脚,躯干部分,然后把内裤外穿,于是一个超人就诞生了
- 抽象工厂模式vs建造者模式
- 抽象工厂模式实现产品家族的创建,一个产品家族就是这样的一系列产品,具有不同分为纬度的产品组合,采用抽象工厂模式则不需要关心构建过程,只关心什么产品由什么工厂生产即可
- 建造者模式则要按照要求指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
- 如果将抽象工厂模式看成汽车配件成产工厂,生产一个产品族的产品,那么建造者密室就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车
3. 结构型模式
- 结构性模式描述如何将类或者对象按某种布局组成更大的结构,它分为类结构性模式,前者采用继承机制来阻止接口和类,后者采用阻止或聚合类组合对象
- 由于组合关系或聚合关系比继承关系耦和度更低,满足“合成复用原则”,所以对象结构型模式具有更大的灵活性
- 结构型模式分为以下其中:
代理模式
适配器模式
装饰器模式
桥接模式
外观模式
组合模式
享元模式
3.1代理模式
由于某些原因需要给某对象提供一个代理以控制该对象的访问,这时访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象直接的中介
Java中的代理按照代理类生成实际不同又分为静态代理和动态代理,静态代理在编译期就产生,而动态代理类则是在Java运行时动态产生,动态代理又有JDK代理和CGLib代理两种
3.1.1 静态代理
- 火车站卖票
- 如果要买或者票的话就要去火车站卖票,坐车到火车站,排队等一系列操作,显然比较麻烦,而火车站在多个地方都有代售点,我们去代售点卖票就方便很多了
/**
* @author LXY
* @desc 火车站
* @time 2023--12--23--18:25
*/
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("火车站售票成功");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--23--18:21
*/
public interface SellTickets {
void sell();
}
/**
* @author LXY
* @desc 代理类
* @time 2023--12--23--18:27
*/
public class Procypoint implements SellTickets{
private SellTickets sellTickets=new TrainStation();
@Override
public void sell() {
sellTickets.sell();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--23--18:26
*/
public class Client {
public static void main(String[] args) {
Procypoint procypoint=new Procypoint();
procypoint.sell();
}
}
3.1.2 JDK代理
- 使用jdk代理来实现
/**
* @author LXY
* @desc
* @time 2023--12--23--18:46
*/
public class Client {
public static void main(String[] args) {
Procypoint procypoint=new Procypoint();
procypoint.getproxyObject().sell();
}
}
/**
* @author LXY
* @desc 代理类
* @time 2023--12--23--18:27
*/
public class Procypoint {
//实例化火车站
private TrainStation sellTickets=new TrainStation();
public SellTickets getproxyObject(){
/**
* ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h
*/
return (SellTickets) Proxy.newProxyInstance(
sellTickets.getClass().getClassLoader(),
sellTickets.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理点收取一定的费用");
return method.invoke(sellTickets,args);
}
});
}
}
/**
* @author LXY
* @desc
* @time 2023--12--23--18:21
*/
public interface SellTickets {
void sell();
}
/**
* @author LXY
* @desc 火车站
* @time 2023--12--23--18:25
*/
public class TrainStation implements SellTickets {
@Override
public void sell() {
System.out.println("火车站售票成功");
}
}
- jdk代理 执行流程
public class Client {
public static void main(String[] args) {
Procypoint procypoint=new Procypoint();
procypoint.getproxyObject().sell();
}
}
- 上述代码通过代理调用了sell方法,会在在内存中生成对应的代码
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/73bb8673c878443abc04f92e54662d27.png - 调用sell方法的时候调用了InvocationHandler里面的invoke方法
return (SellTickets) Proxy.newProxyInstance(
sellTickets.getClass().getClassLoader(),
sellTickets.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理点收取一定的费用");
return method.invoke(sellTickets,args);
}
});
在invoke中通过method.invoke通过反射去调用对应的sell()方法,我们可以在这个步骤进行打印log日志啊,收取费用等等操作,是做一个功能的增强
3.1.3 CGLIB动态代理
- CGLIB动态代理实现需要引起maven
/**
* @author LXY
* @desc
* @time 2023--12--23--19:22
*/
public class Client {
public static void main(String[] args) {
Procypoint procypoint=new Procypoint();
procypoint.getproxyObject().sell();
}
}
/**
* @author LXY
* @desc 代理类
* @time 2023--12--23--18:27
*/
public class Procypoint implements MethodInterceptor {
//实例化火车站
private TrainStation sellTickets=new TrainStation();
public TrainStation getproxyObject(){
//创建Enhancer对象
Enhancer enhancer = new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(TrainStation.class);
//设置回调函数,传递的应该是MethodInterceptor对象,这里实现一下,传入this即可
enhancer.setCallback(this);
//创建代理对象
return (TrainStation) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("收取代理费用了开始");
return method.invoke(sellTickets,objects);
}
}
/**
* @author LXY
* @desc 火车站
* @time 2023--12--23--18:25
*/
public class TrainStation implements SellTickets {
@Override
public void sell() {
System.out.println("火车站售票成功");
}
}
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
3.1.4 CGLIB动态代理
- jak代理和cjlib代理
- 使用CGLIB实现动态代理,CGLIB的底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用JAVA反射效率要高,唯一需要注意的是,CGLIB不能对生命为final的类或者方法进行代理,因为CGLIB原理是动态生成被代理的子类
- 在JDK1.6,JDK1.7,JDK1.8逐步对JDK动态代理优化之后,在调用次数少的情况下,JDK代理效率要高高于CGLIB代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLIB代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLIB代理,所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理
- 动态代理和静态代理
- 动态代理与静态代理想比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个几种的方法中处理,这样在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要静态代理那样每一个方法进行呢中专
- 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度,而董涛代理不会出现这个问题
3.1.5 优缺点和使用场景
- 优点:
- 代理模式在客户端与目标之间起到一个中介作用和保护目标对象的作用
- 代理对象可以扩展目标对象的功能
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
- 缺点:
- 增加了系统的复杂度
- 使用场景
- 远程代理
- 本地服务通过网络请求远程服务,为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常,为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可远程服务提供的功能,而不必过多关心通信部分的细节
- 防火墙
- 当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网,当互联网返回响应时,代理服务器再把它转给你的浏览器
- 保护代理
- 控制一个对象的访问,如果需要,可以给不同的用户使用不同级别的使用权限
3.2适配器模式
- 如果去欧洲旅游的话,他们出走如最左边是标准的欧洲插头,而我们使用的插头是下图最右边,因此我们的笔记本电脑,手机在当地不能直接使用,就需要一个转换器,转换器第一面插入当地的插座,第二面供我们充点,这样是的我们的插头在当地能使用,生活中这样的例子很多读卡器等等,转接头,其实就是使用到了适配器模式
- 适配器模式(Adapter)包含以下主要角色
- 目标(Target)接口,当前系统业务所期待的接口,它尅是抽象类或者是接口
- 适配器(Adaptee)类,它是访问和适配的显存组件库中的组件接口
- 适配器(Adapter)类,它是一个转换器,通过继承或引用适配器对象,把是适配者接口转换为目标接口,让客户按照目标接口的方式直接访问适配者
3.2.1 代码实现
实现方式:定义一个适配器类来实现当前 系统的业务接口,同时又继承现有组件库中已经存在的组件
例:【读卡器案例】
现有一台电脑只能读取SD卡,而要读取到TF卡中的数据就要用到适配器模式,创建一个读卡器,将TF中的内容读取出来
/**
* @author LXY
* @desc TF卡
* @time 2023--12--24--13:43
*/
public interface TFCard {
String readTF();
void writeTF(String msg);
}
/**
* @author LXY
* @desc
* @time 2023--12--24--13:44
*/
public class TFCardImple implements TFCard{
@Override
public String readTF() {
return "TF读取成功,内容是:Hello Word";
}
@Override
public void writeTF(String msg) {
System.out.println("TF存储成功,内容是:"+msg);
}
}
/**
* @author LXY
* @desc SD卡
* @time 2023--12--24--13:45
*/
public interface SDCard {
String readSD();
void writeSD(String msg);
}
/**
* @author LXY
* @desc
* @time 2023--12--24--13:46
*/
public class SDCardImple implements SDCard{
@Override
public String readSD() {
return "SD读取成功,内容是:Hello Word";
}
@Override
public void writeSD(String msg) {
System.out.println("SD存储成功,内容是:"+msg);
}
}
/**
* @author LXY
* @desc 电脑类,但是电脑类只能读取SD卡
* @time 2023--12--24--13:48
*/
public class Computer {
public String readSD(SDCard sdCard){
if (sdCard==null){
throw new NullPointerException("SDCard is not null");
}
return sdCard.readSD();
}
}
/**
* @author LXY
* @desc 类适配器
* @time 2023--12--24--13:50
*/
public class SDAdapterTF extends TFCardImple implements SDCard{
@Override
public String readSD() {
return super.readTF();
}
@Override
public void writeSD(String msg) {
super.writeTF(msg);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--13:52
*/
public class Client {
public static void main(String[] args) {
//实例化电脑
Computer computer=new Computer();
System.out.println("computer.readSD(new SDAdapterTF()) = " + computer.readSD(new SDAdapterTF()));
}
}
- 通过适配器模式,成功的读取到了TF中的内容
3.2.2 对象模式适配器
/**
* @author LXY
* @desc 类适配器
* @time 2023--12--24--13:50
*/
public class SDAdapterTF implements SDCard{
TFCardImple tfCardImple;
public SDAdapterTF(TFCardImple tfCardImple) {
this.tfCardImple = tfCardImple;
}
@Override
public String readSD() {
return tfCardImple.readTF();
}
@Override
public void writeSD(String msg) {
tfCardImple.writeTF(msg);
}
}
3.2.3 适配器应用场景和JDK源码解析
- 应用场景
- 以前开发的系统存在满足新系统功能需求的类,但其接口通新系统的接口不一致
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同
- JDK中的适配器
- Reader(字符流),InputStream(字节流)的适配使用的是InputStreamReader
- InputStreamReader继承自java.io包中的Reader,对他中的抽象的未实现的方法给出解释。如:
如果上图代码中sd(StreamDecoder类对象)在Sun的JDK实现中,实际的方式实现是对sun.nio.cs.StreamDecoder类的同名方法的调用封装
3.3装饰者模式
- 快餐店例子:
- 快餐店有炒面,炒饭这些套餐,可以额外父加鸡蛋,火腿,培根等配菜当然加配菜需要额外的加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦
- 在不改变现有结构的情况下,动态得给该对象增加一些职责(即增加额外的功能)
- 结构:
- 抽象构建(Component)角色,定义一个抽象接口以规范准备接受附加责任的对象
- 具体构建(Concrete Componenet)角色,实现抽象构建,通过装饰角色,为其增加一份职责
- 抽象装饰(Decorator)角色,继承或实现抽象构建,并包含具体构建的实例,可以通过其子类扩展实现具体构建的功能
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构添加附加的责任
3.3.1 代码实现
/**
* @author LXY
* @desc 快餐类
* @time 2023--12--24--14:53
*/
public abstract class FastFood {
private float price; //价格
private String desc; //描述
//返回具体的价格
public abstract float cost();
//构造器
public FastFood() {
}
public FastFood(float price, String desc) {
this.price = price;
this.desc = desc;
}
//getset方法
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;
}
}
/**
* @author LXY
* @desc 炒面
* @time 2023--12--24--14:56
*/
public class FriedNode extends FastFood{
public FriedNode() {
super(15,"炒面");
}
@Override
public float cost() {
return 15;
}
}
/**
* @author LXY
* @desc 炒饭类(10块)
* @time 2023--12--24--14:55
*/
public class FriedRice extends FastFood{
@Override
public float cost() {
return 10;
}
public FriedRice() {
super(10,"扬州炒饭");
}
}
/**
* @author LXY
* @desc 装饰者类
* @time 2023--12--24--14:57
*/
public abstract class Garnish extends FastFood{
//声明快餐变量
private FastFood fastFood;
//有参数构造器
public Garnish(float price, String desc, FastFood fastFood) {
super(price, desc);
this.fastFood = fastFood;
}
//getset
public FastFood getFastFood() {
return fastFood;
}
public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}
}
/**
* @author LXY
* @desc 鸡蛋类
* @time 2023--12--24--14:59
*/
public class Egg extends Garnish {
public Egg(FastFood fastFood) {
super(1, "鸡蛋", fastFood);
}
@Override
public float cost() {
return getFastFood().cost()+getPrice();
}
@Override
public String getDesc() {
return super.getDesc()+getFastFood().getDesc();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--15:03
*/
public class Client {
public static void main(String[] args) {
FastFood fastFood=new FriedNode();
fastFood=new Egg(fastFood);
System.out.println("您已点"+fastFood.getDesc()+",其价格是"+fastFood.cost());
}
}
- 实现原理:通过实例化炒面,定义价格是15,描述是炒面,定义Garnish 是一个抽象类,私有fastFood属性,并且继承fastFood,在构造器中传递旧的fastFood属性保存为私有对象,然后再传递新的fastFood传递给父类,鸡蛋类继承Garnish ,通过重写,就可以通过父类的私有fastFood属性拿到之前的价格,并且通过重写,将之前的价格+上自己也就是鸡蛋的价格进行赋值,运用到了数据结构,对象的地址内存不会变的特点
3.3.2 好处
- 装饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰着对象来获取具有不同行为状态多样化的结果,装置这模式比继承更具有良好的扩展性,玩么的遵循开闭原则,继承是静态的附加责任,装饰着则是动态的附加责任
- 装饰类和被装饰类可以独立发展,不会相互耦和,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能
3.3.3代理和装饰者的区别
静态代理和装饰着的区别
相同点:
- 都是实现与目标类薪享通的业务接口
- 在两个类中都要声明目标对象
- 都可以在不修改目标的前提下增强方法
不同点: - 目的不同
装饰着为了增强目标对象
静态代理是为了保护和隐目标对象 - 获取目标,对象构建的地方不同
装饰着是由于外界传递进来,可以通过构造方法传递
静态代理是在代理类内部创建,以此来隐藏目标对象
3.4桥接模式
- 将抽象与实现分离使他们可以独立变化,它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合
- 票结模式主要包含以下角色
- 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用
- 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
- 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用
- 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现
3.4.1 代码实现
开发一份跨平台视频播放器,可以在不同操作系统平台如(Windows,Mac,Linux等)上播放多种格式的视频文件,常见的视频格式包括EMVB,AVI,WMV等,该播放器包含了两个维度,适合使用桥接模式
/**
* @author LXY
* @desc
* @time 2023--12--24--16:14
*/
public class Client {
public static void main(String[] args) {
Windows win=new Windows(new RMVBFile());
win.play("战狼");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--16:06
*/
public abstract class VideoFile {
abstract void decode(String Fiename);
}
/**
* @author LXY
* @desc
* @time 2023--12--24--16:07
*/
public class RMVBFile extends VideoFile{
@Override
void decode(String Fiename) {
System.out.println("RMVBFile正在播放文件:+"+Fiename);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--16:12
*/
public class Windows extends OperatingSystem{
public Windows(VideoFile videoFile) {
super(videoFile);
}
@Override
void play(String Filename) {
videoFile.decode(Filename);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--16:09
*/
public abstract class OperatingSystem {
protected VideoFile videoFile;
public OperatingSystem(VideoFile videoFile) {
this.videoFile = videoFile;
}
abstract void play(String Filename);
}
/**
* @author LXY
* @desc
* @time 2023--12--24--16:12
*/
public class MAC extends OperatingSystem{
public MAC(VideoFile videoFile) {
super(videoFile);
}
@Override
void play(String Filename) {
videoFile.decode(Filename);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--16:07
*/
public class AVIFile extends VideoFile{
@Override
void decode(String Fiename) {
System.out.println("AVIFile正在播放文件:+"+Fiename);
}
}
3.4.2 好处和使用场景
桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有的系统
如:如果现在还有一种视频文件类型wmv,我们只需要在定义一个类实现VIDERFILE接口即可,其他类不需要发生变化
实现细节对客户透明
- 使用场景:
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时
- 当一个系统不希望使用继承或因为多层次继承导致类的个数极具增加时
- 当一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性时,避免在两个层次之间建立静态的继承练习,通过桥接模式可以使它们在抽象层建立一个关联关系
3.5外观模式
- 编写一个智能家电,通过输入开关指令做到一键关灯,关电视
/**
* @author LXY
* @desc
* @time 2023--12--24--16:45
*/
public class TV {
void on(){
System.out.println("TV on()");
}
void off(){
System.out.println("TV off()");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--16:45
*/
public class Light {
void on(){
System.out.println("Light on()");
}
void off(){
System.out.println("Light off()");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--16:45
*/
public class AirCondition {
void on(){
System.out.println("AirCondition on()");
}
void off(){
System.out.println("AirCondition off()");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--16:47
*/
public class SmartAppliancesFacade {
//做一个聚合操作
Light light;
TV tv;
AirCondition airCondition;
//构造器
public SmartAppliancesFacade() {
light=new Light();
tv=new TV();
airCondition=new AirCondition();
}
public void say(String message){
if (message.equals("关灯")){
on();
}else if (message.equals("开灯")){
off();
}
}
private void off() {
light.off();
tv.off();
airCondition.off();
}
private void on() {
light.on();
tv.on();
airCondition.on();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--16:51
*/
public class Cilent {
public static void main(String[] args) {
SmartAppliancesFacade smartAppliancesFacade=new SmartAppliancesFacade();
smartAppliancesFacade.say("开灯");
}
}
3.5.1优缺点和使用场景
- 优点:降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类
- 对客户屏蔽了子系统组件,减少了客户处理对象的数目,并使得子系统使用起来更加容易
- 缺点:不符合开闭原则,很麻烦
- 使用场景:
- 对分层系统构建时,使用外观模式定义子系统中每层的入口点可以简化组系统之间的依赖关系
- 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问
- 当客户端与多个子系统存在很大的联系时,引入外观模式可以将他们分离,从而提高子系统的独立性和可移植性
3.6 享元模式
- 运用共享技术有效的支持大量细粒度对象的复用,它通过共享已经存在的对象,避免大量相似对象的开销,从而提高系统资源的利用率
- 享元(Flyweight)模式存在以下两种状态
- 1.内部状态,即不会随着环境的改变而改变的可共享部分
- 2.外部状态,指随着环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用总的这两种状态,并将外部状态外部化
- 享元模式有以下角色:
- 抽取享元角色(Flyweight)通常是一个接口或者抽象类,在抽象享元类中声明了具体享元类公共的方法 ,这些方法可以向外界提供享元享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)
- 具体享元(Concrete Flyweight)角色:它实现了抽象享元类,称为享元对象,在具体享元类中为内部状态提供了存储空间,通常我们可以结合单例模式来设计具体享元类,为每一个具体享元提供为一个的享元对象
- 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色,当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象,如果不存在,则创建一个新的享元对象
- 非享元(Unsharable Flyweight)角色:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时,可以直接通过实例化创建
3.6.1 享元模式的应用
俄罗斯方块,如果在这里每一个方块都是一个对象就需要占据很大的空间,下面用享元模式实现
/**
* @author LXY
* @desc
* @time 2023--12--24--18:35
*/
public abstract class AbstractBox {
abstract String getshape();
public void display(String color){
System.out.println("颜色是"+color);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--18:36
*/
public class IBOX extends AbstractBox{
@Override
String getshape() {
return "IBOX";
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--18:36
*/
public class LBOX extends AbstractBox{
@Override
String getshape() {
return "LBOX";
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--18:36
*/
public class OBOX extends AbstractBox{
@Override
String getshape() {
return "OBOX";
}
}
/**
* @author LXY
* @desc 工厂
* @time 2023--12--24--18:37
*/
public class BoxFactory {
static volatile BoxFactory BOX_FACTORY=new BoxFactory();
//这里当map当做是缓存
private HashMap<String,AbstractBox>map;
//构造器
public BoxFactory() {
map=new HashMap<>();
map.put("L",new LBOX());
map.put("O",new OBOX());
map.put("I",new IBOX());
}
//单例
public static BoxFactory getInstance(){
return BOX_FACTORY;
}
public AbstractBox getBox(String key){
return map.get(key);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--24--18:41
*/
public class Client {
public static void main(String[] args) {
final AbstractBox l = BoxFactory.getInstance().getBox("L");
l.display("Red");
}
}
3.6.2 优缺点和使用场景
- 优点:极大的减少内存中像素或相同对象数量,借阅系统资源,提供稳定性能
- 享元模式中的外部状态相对独立,且不影响内部状态
- 缺点:为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
4. 行为型模式
- 行为模式用于描述程序在运行时复杂的流程控制,即描述多个类或者对象之间怎样相互协作共同完成单个对象都须发独立完成的任务,它设计算法与对象间的职责分配
- 行为型模式分为类行为模式和对象行为模式,前者采用继承机制在类间分派行动,后者采用组合或聚合在对象间分配行为,由于组合关系或聚合关系比继承关系耦合度更低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性
- 行为模式分为:
- 模板方法模式
- 策略模式
- 命令模式
- 职责链模式
- 状态模式
- 观察者模式
- 中介者模式
- 迭代器模式
- 访问者模式
- 备忘录模式
- 解释器模式
4.1模板方法模式
在面向对象程序设计过程中,程序员常常遇到这样的问题,设计一个系统知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关
例如:去银行办理业务一般要经历以下4个流程:取号,排队,办理具体业务,对银行工作人员进行评分等,其中取号,排队,和对银行工作人员进行评分云业务对每个客户是一样的,可以再父类中实现,但是办理具体业务缺因人而异,它可能是存款,取款,或者转账等,可以延迟到子类实现
-
定义:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,是的子类可以不改变算法结构的情况下重定义该算法的某些特定步骤
-
模板方法(Template Method)模式包含以下主要角色
-
抽象类(Abstract class):负责给出一个算法的轮廓和骨架,它是由一个模板方法和若干基本方法构成
-
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
-
- 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分,基本方法又可以分为三种
-
- 抽象方法(Abstract Method):一个抽象方法由抽象类声明,由其具体子类实现
-
2.具体方法(Concrete Method):一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承
-
3.钩子方法(Hook Method)在抽象类中已经实现,包括用于判断的逻辑方法和需要重写子类的空方法两种
-
一般钩子方法适用于判断的扩机方法,这类方法名一般为isXxx,返回值是boolwan类型
-
具体子类(Concrete Class):实现了抽象类中所定义的抽象方法和钩子方法,他们是一个顶级逻辑的组成步骤
4.1.1模板方法实现
- 炒菜的步骤是固定的,分为倒油,热油,倒蔬菜,倒调料品,翻炒等步骤
* @author LXY
* @desc
* @time 2023--12--25--11:26
*/
public abstract class AbstartClass {
public final void cookProcess(){
prouroi(); //倒油
heatoil(); //热油
pouvegetable(); //下菜
pourSauce(); //放调料
fry(); //炒菜
}
public void prouroi(){
System.out.println("倒油");
}
public void heatoil(){
System.out.println("热油");
}
//倒蔬菜是不一样的(一个是下包菜,一个是空心菜)
public abstract void pouvegetable();
//到的调料品是不一样的
public abstract void pourSauce();
public void fry(){
System.out.println("炒菜,炒到熟");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--11:29
*/
public class ConcerateClass_BaoCai extends AbstartClass{
@Override
public void pouvegetable() {
System.out.println("下包菜");
}
@Override
public void pourSauce() {
System.out.println("下辣椒");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--11:29
*/
public class ConcerateClass_CaiXin extends AbstartClass{
@Override
public void pouvegetable() {
System.out.println("下卷心菜");
}
@Override
public void pourSauce() {
System.out.println("下盐");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--11:30
*/
public class Cliet {
public static void main(String[] args) {
ConcerateClass_BaoCai concerateClass_baoCai=new ConcerateClass_BaoCai();
concerateClass_baoCai.cookProcess();
}
}
4.1.2 优缺点和适用场景
-优点:
- 提高代码的复用性
将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的代码放入不同的子类中 - 实现了反向控制
通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制,并符合开闭原则 - 缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度
- 适用场景:
- 算法的整体步骤很稳定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现
- 需要通过子类来决定父类中某个步骤是否执行,实现子类对父类的反向控制
4.1.3 JDK源码解析
- InputStream 中就使用了模板模式
-有三个重载的read方法 - read(byte b[])中调用的是自己的三个参数的重载方法
- 在read(byte b[], int off, int len) 中,其中调用的是read()的无法方法
- 注意这个read()方法是抽象的,需要子类去实现,最终的实现还是子类
public abstract class InputStream implements Closeable {
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
4.2 策略模式
- 概述
- 我们去旅游出行,可以选择很多种,可以骑自行车,坐汽车,坐火车,坐飞机
- 作为一个程序员,开发也要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择idea开发,也可以进行eclispse开发
- 该模式定义了一系列的算法,并将每个算法封装起来,使他们之间可以相互替换,且算法的变化不会影响使用算法的客户,策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开,并委派给不同的对象对这些算法进行管理
- 结构:
抽象策略(strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现,此角色给出所有的具体策略类所需的接口
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为
环境(Context)类:持有一个策略类的引用,最终给客户端调用
4.2.1案例实现
- 一家百货公司在年度促销活动,针对不同的(春节,中秋,圣诞)退出不同的促销活动,由促销员将促销活动展示给客户:类图如下
/**
* @author LXY
* @desc
* @time 2023--12--25--14:20
*/
public abstract class Strategy {
public abstract void show();
}
/**
* @author LXY
* @desc
* @time 2023--12--25--14:20
*/
public class StartegyA extends Strategy{
@Override
public void show() {
System.out.println("满500减200");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--14:20
*/
public class StartegyB extends Strategy{
@Override
public void show() {
System.out.println("满一送一");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--14:20
*/
public class StartegyC extends Strategy{
@Override
public void show() {
System.out.println("满500抽免单");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--14:21
*/
public class SalesMan {
Strategy strategy;
public SalesMan(Strategy strategy) {
this.strategy = strategy;
}
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void salesManshow(){
strategy.show();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--14:22
*/
public class Client {
public static void main(String[] args) {
SalesMan salesMan=new SalesMan(new StartegyA());
salesMan.salesManshow();
System.out.println("------------");
salesMan.setStrategy(new StartegyB());
salesMan.salesManshow();
System.out.println("------------");
salesMan.setStrategy(new StartegyC());
salesMan.salesManshow();
}
}
4.2.2 策略模式优缺点
- 优点
- 策略类之间可以自由切换
由于策略类都实现同一个实现,所以使他们之间可以自由切换 - 易于扩展
增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开闭原则 - 避免使用多重条件选择语句(if ese),充分体现面向对象设计思想
- 缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类
- 策略模式将造成产生很多策略类,可以使用享元模式在一定程度上减少对象的数量
- 使用场景
- 一个系统需要动态的在几种算法中选择一种,可将每个算法封装到策略类中
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时
- 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为
4.3 命令模式
- 日常出去玩都会想到这样的场景:
- 定义:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开,这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储传递,调用,增加与管理
- 结构
- 命令模式包含以下主要角色
- 抽象命令类(Command)角色:定义命令的接口,声明执行的方法
- 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接受者,并调用接受者的功能来完成命令要执行的啊哦做
- 实现者/接受者(Receiver)角色:接受者,真正执行命令的对象,任何类都可能成为一个接受者,只要它讷讷狗实现命令要求实现的相对应功能
- 调用者/请求者(Invoke)角色:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象,这个是客户端真正触发命令并要求命令执行响应操作的地方,也就是说相当于使用命令对象的入口
4.3.1案例实现
- 服务员:就是调用者角色由她来发起命令
- 资深大厨:就是接受者角色,真正命令执行的对象
- 订单:命令这种包含订单
/**
* @author LXY
* @desc 厨师
* @time 2023--12--25--15 :31
*/
public class SenoirCherf {
public void makFood(int num,String footName){
System.out.println("座位号:"+num+"正在做:"+footName);
}
}
/**
* @author LXY
* @desc 订单类
* @time 2023--12--25--15:28
*/
public class Order {
//餐桌号
int daningTable;
//点餐
Map<String,Integer> map=new HashMap<>();
public void setDaningTable(int daningTable) {
this.daningTable = daningTable;
}
public int getDaningTable() {
return daningTable;
}
public Map<String, Integer> getMap() {
return map;
}
public void setMap(Integer num, String name) {
map.put(name,num);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--15:32
*/
public interface Command {
void execute();
}
/**
* @author LXY
* @desc
* @time 2023--12--25--15:33
*/
public class OrderCommand implements Command{
//厨师
SenoirCherf senoirCherf;
Order order; //订单
public OrderCommand(SenoirCherf senoirCherf, Order order) {
this.senoirCherf = senoirCherf;
this.order = order;
}
@Override
public void execute() {
System.out.println("接受到的订单");
final Map<String, Integer> map = order.getMap(); //获取到订单
for (String str : map.keySet()) {
System.out.println("订单来了:菜品是"+str+",数量是:"+map.get(str));
senoirCherf.makFood(order.getDaningTable(),str);
}
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--15:37
*/
public class Waitor {
//多个命令类一个服务员可以发送多个命令
List<Command>Commands=new ArrayList<>();
//载入数据
public void setList(Command command) {
Commands.add(command);
}
void Orderup(){
for (Command command : Commands) {
command.execute();
}
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--15:40
*/
public class Client {
public static void main(String[] args) {
//生成两份订单
Order order1 = new Order();
order1.setDaningTable(1);
order1.setMap(5,"可乐");
order1.setMap(10,"汉堡");
Order order2 = new Order();
order2.setDaningTable(2);
order2.setMap(1,"西红柿炒鸡蛋");
order2.setMap(1,"炒河粉");
//指定厨师
SenoirCherf senoirCherf=new SenoirCherf();
Command command1=new OrderCommand(senoirCherf,order1);
Command command2=new OrderCommand(senoirCherf,order2);
Waitor waitor=new Waitor();
waitor.setList(command1);
waitor.setList(command2);
waitor.Orderup(); //开始
}
}
4.3.2 优缺点以及使用场景
- 优点
- 降低系统的耦合度,命令模式能将调用操作的对象与实现该操作的对象解耦
- 增加或删除命令非常方便,采用命令模式增加与删除命令不会影响其他类,它满足开闭原则,对扩展比较灵活
- 可以实现宏命令,命令模式可以与组合模式结合,将多个命令装配成一个组合命令,既宏命令
- 方便实现undo和Redo操作,命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复
- 缺点:使用命令模式可能会导致某些系统有过多的具体命令类
- 系统结构更加复杂
- 使用场景
- 系统需要将请求调用者和请求接受者解耦,使得调用者和接受者不直接交互
- 系统需要在不同的时间指定请求,将请求排队和执行请求
- 系统需要支持命令的核销(Undo)曹组和恢复(Redo)操作
4.4职责链模式
- 在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或者权限不同,例如:公司员工请假,可批假的有部门负责人,副总经理,总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名,电话和地址等信息,这增加了难度,这样的例子还有很多,如找领导出差报销,生活中的击鼓传花游戏等
- 定义:又名职责链模式,为了避免请求发送者与多个请求矗立着耦和在一起,将所有的请求矗立着通过前一对象记住下一对象引用链连城一条链,可将请求沿着这条链传递,直到有对象处理它为止
结构:朱泽连模式主要包含以下角色:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象出来方法和一个后继连接
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,潘丹是否处理本次请求,如果可以处理请求则处理,否则将该请求传给他的后继者
- 客户类(Client)角色:创建处理链,并将链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程
4.4.1职责链模式代码实现
- 现需开发一个请假流程控制系统,请假一天以下的假只需要小组长同意即可,请假1-3天需要部门经理统一,请假3-7天需要总经理同意
/**
* @author LXY
* @desc 请假条类
* @time 2023--12--25--16:40
*/
public class LeavRepuest {
private String name;
private Integer num;
private String content;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
/**
* @author LXY
* @desc 抽象的
* @time 2023--12--25--16:34
*/
public abstract class Hander {
//定义常量用于判断请假时间
protected final static Integer NUM_ONE=1;
protected final static Integer NUM_THREE=3;
protected final static Integer NUM_SEVEN=7;
Hander nexHand;
private Integer numStart; //能管理的开始时间
private Integer numEnd; //能管理的最终时间
//子类需要重写构造器,传递给父类自己能审批的时间
public Hander(Integer numStart, Integer numEnd) {
this.numStart = numStart;
this.numEnd = numEnd;
}
public void setNexHand(Hander nexHand) {
this.nexHand = nexHand;
}
//需要子类重写该方法,这是一个审批方法
public abstract void setNexHand(LeavRepuest leavRepuest);
//提交
public void sumbit(LeavRepuest leavRepuest){
if (leavRepuest.getNum()==null)
throw new NullPointerException("请假时间不能为空");
if ((leavRepuest.getNum()>this.numStart)&&(leavRepuest.getNum()<=this.numEnd)){
setNexHand(leavRepuest); //开始请假
}else {
this.nexHand.sumbit(leavRepuest); //如果自己没权限就去链上寻找
}
}
}
/**
* @author LXY
* @desc 总经理
* @time 2023--12--25--16:46
*/
public class GeneralManager extends Hander{
public GeneralManager() {
super(NUM_THREE, NUM_SEVEN);
}
@Override
public void setNexHand(LeavRepuest leavRepuest) {
System.out.println("总经理请假同意:请假人姓名-"+leavRepuest.getName()+",请假天数:"+leavRepuest.getNum()+",请假理由:"+leavRepuest.getContent());
}
}
/**
* @author LXY
* @desc 组长
* @time 2023--12--25--16:46
*/
public class GroupLeader extends Hander{
public GroupLeader() {
super(0, NUM_ONE);
}
@Override
public void setNexHand(LeavRepuest leavRepuest) {
System.out.println("小组长请假同意:请假人姓名-"+leavRepuest.getName()+",请假天数:"+leavRepuest.getNum()+",请假理由:"+leavRepuest.getContent());
}
}
/**
* @author LXY
* @desc 经理
* @time 2023--12--25--16:46
*/
public class Manager extends Hander{
public Manager() {
super(NUM_ONE, NUM_THREE);
}
@Override
public void setNexHand(LeavRepuest leavRepuest) {
System.out.println("经理请假同意:请假人姓名-"+leavRepuest.getName()+",请假天数:"+leavRepuest.getNum()+",请假理由:"+leavRepuest.getContent());
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--16:51
*/
public class Client {
private static Hander groupLeader = null;
static {
//组长
groupLeader =new GroupLeader();
//副总经理
Hander manager=new Manager();
//总经理
Hander generalManager=new GeneralManager();
//绑定职责链
groupLeader.setNexHand(manager);
manager.setNexHand(generalManager);
}
public static void main(String[] args) {
LeavRepuest leavRepuest = new LeavRepuest();
leavRepuest.setNum(2);
leavRepuest.setContent("支原体感染");
leavRepuest.setName("张三");
//因为员工只认识自己的上级,所以就只能找自己的上级,组长处理不了继续往上找
groupLeader.sumbit(leavRepuest);
}
}
4.4.2职责链模式优缺点
-
优点
-
降低了对象之间的耦合度
该模式降低了请求发送你这和接受者的耦合度 -
增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则 -
增强了给对象指派职责的灵活性
当工作流程发生变化,可以懂爱的改变链内的成员或者修改它们的次序,也可以动态的新增或者删除责任 -
责任链简化了对象之间的连接
一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的if或者if…else语句 -
责任分担
每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的职责范围,符合单一职责原则 -
缺点:
-
不能保证每个请求一定被处理,由于一个请求没有明确的接受者,所以不能保证它一定会被处理,该请求可能传递发哦链的末端都得不到处理
-
队比较长的则热念,请求的处理可能设计多个对象,系统性能会受到一定的影响
在JAVA中的过滤器链和设计模式的职责从某种程度上是一样的
4.5状态模式
-
概述,通过按钮来控制一个电梯的状态,一个电梯有关门状态,开门状态,停止状态,运行状态,每一种状态都可以改变,都有可能根据其他状态来更新处理,例如:如果电梯门处于运行状态,就不能进行开门操作,如果电梯门是停止状态就可以执行开门操作
-
状态模式,以下代码略微有点不好理解,可以理解为在Client中,客户首先传递的是Open模式(数据源),
然后去调用Open状态下的open方法,打印,然后调用Open模式下的close方法,但是close方法需要在close模式下才能打印,只能通过 context.setLiftState(context.closeIngState); 去修改之前传递的open模式,这样数据源就变成了close模式
- 核心代码
public void setLiftState(LiftState liftState){
this.liftState=liftState;
liftState.setContext(this); //将自己传给LiftState对象的Context以供子类使用,这样就做到了数据地址不变数据一样就可以实现在子类切换数据源了
}
/**
* @author LXY
* @desc
* @time 2023--12--25--18:47
*/
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 start();
public abstract void stop();
}
/**
* @author LXY
* @desc 上下文类
* @time 2023--12--25--18:46
*/
public class Context {
//实例化四个状态
public OpenIngState openIngState=new OpenIngState();
public CloseIngState closeIngState=new CloseIngState();
public RunIngState runIngState=new RunIngState();
public StopIngState stopIngState=new StopIngState();
private LiftState liftState;
public void setLiftState(LiftState liftState){
this.liftState=liftState;
liftState.setContext(this);
}
public void open() {
this.liftState.open();
}
public void close() {
this.liftState.close();
}
public void start() {
this.liftState.start();
}
public void stop() {
this.liftState.stop();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--18:49
*/
public class OpenIngState extends LiftState{
@Override
public void open() {
System.out.println("电梯门已开");
}
@Override
public void close() {
context.setLiftState(context.closeIngState);
context.close();
}
@Override
public void start() {
//电梯门开着不能启动
}
@Override
public void stop() {
//电梯门开着不能停止运行
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--18:49
*/
public class RunIngState extends LiftState{
@Override
public void open() {
//整在运行不能开门
}
@Override
public void close() {
}
@Override
public void start() {
System.out.println("正在运行");
}
@Override
public void stop() {
context.setLiftState(context.stopIngState);
context.stop();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--18:49
*/
public class StopIngState extends LiftState{
@Override
public void open() {
context.setLiftState(context.openIngState);
context.open();
}
@Override
public void close() {
context.setLiftState(context.closeIngState);
context.close();
}
@Override
public void start() {
context.setLiftState(context.runIngState);
context.start();
}
@Override
public void stop() {
System.out.println("电梯已经停止");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--18:49
*/
public class CloseIngState extends LiftState{
@Override
public void open() {
context.setLiftState(context.openIngState);
context.open();
}
@Override
public void close() {
System.out.println("电梯门已关");
}
@Override
public void start() {
context.setLiftState(context.runIngState);
context.start();
}
@Override
public void stop() {
context.setLiftState(context.stopIngState);
context.stop();
}
}
/**
* @author LXY
* @desc
* @time 2023--12--25--18:59
*/
public class Client {
public static void main(String[] args) {
Context context=new Context();
// context.setLiftState(new OpenIngState());
// context.setLiftState(new CloseIngState());
context.setLiftState(new RunIngState());
context.open();
context.close();
context.start();
context.stop();
}
}
4.5.1 优缺点和使用场景
- 优点:
- 将所有与某个状态有关的行为放到一个类中,并且可以方便的增加新的状态,只需要改变对象状态即可改变对象的行为
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
- 缺点:
- 状态模式的使用必然会增加系统类和对象的个数
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
- 状态模式对开闭原则的支持不太好
- 使用场景:
- 当一个对象的行为取决于他的状态,并且它必须在运行时根据状态改变它的行为,就尅考虑使用状态模式
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时
4.6观察者模式
- 定义:又称为发布订阅模式(Publish/subscribe)模式,它定义了一种一对多的依赖关系,让观察者对象同时监听某一个主题对象,这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己
- 结构:
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合中,每个主图都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所注册过的观察者发送通知
- observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在的发哦主题更改通知时更新自己
- ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态
4.6.1 案例实现
- 使用微信公众号,关注的内容有更新的话,它会推送给关注公众号的微信用户端,我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信用户就是被观察者
/**
* @author LXY
* @desc 抽象的被观察者
* @time 2023--12--26--10:09
*/
public abstract class Subject {
//添加方法
abstract void attach(ObjectServer server);
//删除方法
abstract void detach(ObjectServer server);
//发送消息
abstract void notify(String message);
}
/**
* @author LXY
* @desc
* @time 2023--12--26--10:12
*/
public class SubscriptionSubject extends Subject{
//观察者集合
List<ObjectServer>serverList;
public SubscriptionSubject() {
serverList=new ArrayList<>();
}
@Override
void attach(ObjectServer server) {
serverList.add(server);
}
@Override
void detach(ObjectServer server) {
serverList.remove(server);
}
@Override
void notify(String message) {
//给每一个观察者发送消息
for (ObjectServer objectServer : serverList) {
objectServer.update(message);
}
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--10:10
*/
public interface ObjectServer {
void update(String mess);
}
/**
* @author LXY
* @desc
* @time 2023--12--26--10:14
*/
public class WeixinUser implements ObjectServer{
String name;
public WeixinUser(String name) {
this.name = name;
}
@Override
public void update(String mes) {
System.out.println(name+"接收到了消息:"+mes);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--10:15
*/
public class Client {
public static void main(String[] args) {
//实例化微信用户
SubscriptionSubject subject = new SubscriptionSubject();
subject.attach(new WeixinUser("孙悟空"));
subject.attach(new WeixinUser("杨戬"));
subject.attach(new WeixinUser("哪吒"));
subject.notify("您关注的天庭趣事更新了");
}
}
4.6.2 优缺点和使用场景
- 优点:降低了目标与观察者之间的耦和关系,两者之间是抽象耦和关系
- 被观察者发送了通知,所有注册的观察者都会收到信息【可以实现广播机制】
- 缺点:
- 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会消耗
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,导致系统崩溃
- 使用场景
- 对象存在一对多关系,一个对象的状态发生改变会影响其他对象
4.7迭代器模式
- 概述: 提供了一个对戏那个来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
- 迭代器模式主要包含以下角色
- 抽象聚合(Aggregate)角色:定义存储,添加,删除聚合元素以及创建迭代器对象的接口
- 具体聚合(ConcreteAggregate)角色:实现聚合抽象类,返回一个具体迭代器的实例
- 抽象迭代器(Iterator)角色:东一访问和遍历聚合元素的接口,通常包含hashNext(),Next()等方法
- 具体迭代器(ConcreteIterator)角色:实现抽象迭代器中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置
4.7.1迭代器模式代码实现
`
/**
* @author LXY
* @desc 学生实体类
* @time 2023--12--26--11:22
*/
public class Student {
//学号,姓名
private String name;
private String num;
public Student(String name, String num) {
this.name = name;
this.num = num;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", num='" + num + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
}
}
/**
* @author LXY
* @desc 迭代器接口
* @time 2023--12--26--11:24
*/
public interface StudentItetator {
//用来判断有没有下一个元素
boolean hasNex();
Student next();
}
/**
* @author LXY
* @desc 迭代器接口的实现
* @time 2023--12--26--11:25
*/
public class StudentItetatorImple implements StudentItetator{
//学生存储对象
List<Student>studentList;
public StudentItetatorImple(List<Student> studentList) {
this.studentList = studentList;
}
private int position=0; //定义计数器
@Override
public boolean hasNex() {
return position<studentList.size();
}
@Override
public Student next() {
//拿到数据
Student student = studentList.get(position);
position++; //计数器+1
return student;
}
}
/**
* @author LXY
* @desc 对学生的功能操作的获取学生迭代器
* @time 2023--12--26--11:23
*/
public interface StudentAggregate {
void addStudent(Student s);
void removeStudent(Student s);
StudentItetator getStudentItetator();
}
/**
* @author LXY
* @desc
* @time 2023--12--26--11:29
*/
public class StudentAggregateImple implements StudentAggregate {
private List<Student>studentList;
public StudentAggregateImple() {
this.studentList = new ArrayList<>();
}
@Override
public void addStudent(Student s) {
studentList.add(s);
}
@Override
public void removeStudent(Student s) {
studentList.remove(s);
}
@Override
public StudentItetator getStudentItetator() {
return new StudentItetatorImple(studentList);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--11:32
*/
public class Client {
public static void main(String[] args) {
StudentAggregateImple studentAggregateImple = new StudentAggregateImple();
studentAggregateImple.addStudent(new Student("张三","001"));
studentAggregateImple.addStudent(new Student("李四","008"));
studentAggregateImple.addStudent(new Student("张飞","006"));
final StudentItetator studentItetator = studentAggregateImple.getStudentItetator();
while (studentItetator.hasNex()){
final Student next = studentItetator.next();
System.out.println(next);
}
}}
4.7.2优缺点和使用场景
- 优点:
- 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式,在迭代器模式中只需要用一个不同迭代器来替换原有迭代器即可改变遍历算法,我们也可以自定义迭代器的子类以支持新的遍历方式
- 迭代器简化了聚合类,由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计
- 在迭代器模式中,由于引入了抽象层,增加新的聚合类的迭代器都很方便,无需修改原有代码,满足“开闭原则”的 要求
- 缺点:
- 增加了类的使用个数,这在一点程度上增加了系统的复杂性
使用场景
当需要为聚合对象提供多种遍历方式时。
当需要为遍历不同的聚合结构提供一个统一接口时
当访问一个聚合对象的内容而无需暴露其内部细节的表示时
4.8访问者模式
- 概述:封装一些用于某种数据结构中的各元素操作,它可以在不改变这个数据结构的前提下定义作用域这些元素的新的操作
- 结构:
- 抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问模式要求元素类的个数不能改变
- 具体元素(ConcreteElement)角色 :提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者模式提供的访问该元素的方法
- 抽象元素(Element)角色:定义了一个接受访问者的方法(Accept)其意义是指每一个元素都要可以被访问者访问
- 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特征的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问
4.8.1访问者代码实现
- 以下的例子是一个喂猫的例子,有猫和狗两种类型,其中有主人来喂,也有别人代替喂
Animal中的Accept方法,其中Dog和Cat实现了是为了调用Persion中对应的重载的feed方法
/**
* @author LXY
* @desc
* @time 2023--12--26--14:53
*/
public interface Aminal {
void accept(Persion p);
}
/**
* @author LXY
* @desc
* @time 2023--12--26--14:54
*/
public class Cat implements Aminal{
@Override
public void accept(Persion p) {
p.feed(this);
System.out.println("喵喵---");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--14:54
*/
public class Dog implements Aminal{
@Override
public void accept(Persion p) {
p.feed(this);
System.out.println("汪汪汪");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--14:53
*/
public interface Persion {
void feed(Cat cat);
void feed(Dog dog);
}
/**
* @author LXY
* @desc
* @time 2023--12--26--14:56
*/
public class Someone implements Persion{
@Override
public void feed(Cat cat) {
System.out.println(" Someone 喂猫");
}
@Override
public void feed(Dog dog) {
System.out.println(" Someone 喂狗 ");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--14:55
*/
public class Owner implements Persion{
@Override
public void feed(Cat cat) {
System.out.println(" Owner 喂猫");
}
@Override
public void feed(Dog dog) {
System.out.println(" Owner 喂狗 ");
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--14:58
*/
public class Home {
//存错多个动物
List<Aminal>aminalList=new ArrayList<Aminal>();
public void add(Aminal aminal){
aminalList.add(aminal);
}
public void action(Persion persion){
for (Aminal aminal : aminalList) {
aminal.accept(persion);
}
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--15:00
*/
public class Client {
public static void main(String[] args) {
Home home=new Home();
home.add(new Cat());
home.add(new Dog());
home.action(new Someone());
}
}
4.8.2 优缺点和使用场景
- 优点
- 扩展性好
在不修改对象结构元素中的元素的情况下,为对象结构中的元素添加新的功能 - 复用性好
通过访问者来定义整个对象结构通用的功能,从而提高复用程度 - 分离无关行为
通过访问者来分离无关行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一 - 缺点:
- 对象结构变化很困难
在访问者模式中每增加一个元素类都要在具体访问者类中增加对应具体啊哦做,这违背了开闭原则
4.9备忘录模式
- 概述: 备忘录模式提供了一种状态回复的实现机制,使得用户可以方便的回到某一个特定的历史步骤,当前的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销操作,如Word,记事本,Phostsshop,IDea等软件在编辑时候按照撤销就可以撤回当前操作
- 定义:又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,一遍以后需要时能将该对象恢复到原先保存的状态
- 结构:
- 备忘录模式的主要角色如下:
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息
- 备忘录(Memente)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态发起人
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改
4.9.1 备忘录模式代码实现
例:【挑战游戏Boss】
游戏中的某个场景,一游戏角色有生命力,攻击力,防御力等数据,在打Boss前后是不一样的,我们允许玩家如果感觉与Boss决定的效果不理想可以让游戏恢复到决斗之前的状态
- 要实现上述案例,有两种方式
- 白箱备忘录模式,黑箱备忘录模式
4.9.1.1 白箱备忘录模式
- 备忘录角色对任何独享都提供一个接口,即宽接口,备忘录角色的内部存储的状态就对所有对象公开
/**
* @author LXY
* @desc 角色类
* @time 2023--12--26--16:06
*/
public class GanmeRole {
private int Vit; //生命力
private int atk; //攻击力
private int def; //防御力
//初始化状态
void initState(){
this.Vit=100;
this.atk=100;
this.def=100;
}
//和boss进行战斗
void fight(){
this.Vit=0;
this.atk=120;
this.def=30;
}
//保存备忘录
RoleStateMemento savaState(){
return new RoleStateMemento(this.Vit,this.atk,this.def);
}
//备忘录的数据进行恢复
void recoverState(RoleStateMemento roleStateMemento){
this.setAtk(roleStateMemento.getAtk());
this.setDef(roleStateMemento.getDef());
this.setVit(roleStateMemento.getVit());
}
public int getVit() {
return Vit;
}
public void setVit(int vit) {
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;
}
public GanmeRole() {
}
public GanmeRole(int vit, int atk, int def) {
Vit = vit;
this.atk = atk;
this.def = def;
}
@Override
public String toString() {
return "GanmeRole{" +
"Vit=" + Vit +
", atk=" + atk +
", def=" + def +
'}';
}
}
/**
* @author LXY
* @desc 备忘录,可以通过我来进行数据的恢复
* @time 2023--12--26--16:09
*/
public class RoleStateMemento {
private int Vit; //生命力
private int atk; //攻击力
private int def; //防御力
public RoleStateMemento() {
}
public RoleStateMemento(int vit, int atk, int def) {
Vit = vit;
this.atk = atk;
this.def = def;
}
public int getVit() {
return Vit;
}
public void setVit(int vit) {
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;
}
}
/**
* @author LXY
* @desc 游戏管理者对象
* @time 2023--12--26--16:14
*/
public class RoleStateCaretaker {
RoleStateMemento roleStateMemento;
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
}
public RoleStateCaretaker(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
}
public RoleStateCaretaker() {
}
/**
* @author LXY
* @desc
* @time 2023--12--26--16:14
*/
public class Cliet {
public static void main(String[] args) {
GanmeRole ganmeRole=new GanmeRole(); //初始化角色类
System.out.println("----------战斗开始--------");
ganmeRole.initState();
System.out.println(ganmeRole.toString());
//创建游戏管理者对象
RoleStateCaretaker roleStateCaretaker=new RoleStateCaretaker();
//战斗开始的时候做一下备份 这是备份出来的数据
roleStateCaretaker.setRoleStateMemento( ganmeRole.savaState());
System.out.println("-----------战斗结束---------");
ganmeRole.fight();
System.out.println(ganmeRole.toString());
System.out.println("------------恢复数据--------");
ganmeRole.recoverState(roleStateCaretaker.getRoleStateMemento());
System.out.println(ganmeRole.toString());
}
}
4.9.1.1 黑箱备忘录模式
- 将RoleStateMemento 变为内部类实现Memento ,对外暴露的窄宽口是Memento ,这样就不会直接保留里面的代码,暴露出去的是接口
/**
* @author LXY
* @desc 备忘录接口,对外提供的窄接口
* @time 2023--12--26--16:27
*/
public interface Memento {
}
/**
* @author LXY
* @desc 角色类
* @time 2023--12--26--16:06
*/
public class GanmeRole {
private int Vit; //生命力
private int atk; //攻击力
private int def; //防御力
//初始化状态
void initState(){
this.Vit=100;
this.atk=100;
this.def=100;
}
//和boss进行战斗
void fight(){
this.Vit=0;
this.atk=120;
this.def=30;
}
//保存备忘录
RoleStateMemento savaState(){
return new RoleStateMemento(this.Vit,this.atk,this.def);
}
//备忘录的数据进行恢复
void recoverState(Memento memento){
RoleStateMemento roleStateMemento= (RoleStateMemento) memento;
this.setAtk(roleStateMemento.getAtk());
this.setDef(roleStateMemento.getDef());
this.setVit(roleStateMemento.getVit());
}
public int getVit() {
return Vit;
}
public void setVit(int vit) {
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;
}
public GanmeRole() {
}
public GanmeRole(int vit, int atk, int def) {
Vit = vit;
this.atk = atk;
this.def = def;
}
@Override
public String toString() {
return "GanmeRole{" +
"Vit=" + Vit +
", atk=" + atk +
", def=" + def +
'}';
}
public class RoleStateMemento implements Memento{
private int Vit; //生命力
private int atk; //攻击力
private int def; //防御力
public RoleStateMemento() {
}
public RoleStateMemento(int vit, int atk, int def) {
Vit = vit;
this.atk = atk;
this.def = def;
}
public int getVit() {
return Vit;
}
public void setVit(int vit) {
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;
}
}
}
/**
* @author LXY
* @desc 游戏管理者对象
* @time 2023--12--26--16:14
*/
public class RoleStateCaretaker {
Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--16:14
*/
public class Cliet {
public static void main(String[] args) {
com.rj.jand.patten.memorandum.BlackMemoradum.GanmeRole ganmeRole=new GanmeRole(); //初始化角色类
System.out.println("----------战斗开始--------");
ganmeRole.initState();
System.out.println(ganmeRole.toString());
final RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
//战斗开始的时候做一下备份 这是备份出来的数据
roleStateCaretaker.setMemento( ganmeRole.savaState());
System.out.println("-----------战斗结束---------");
ganmeRole.fight();
System.out.println(ganmeRole.toString());
System.out.println("------------恢复数据--------");
ganmeRole.recoverState(roleStateCaretaker.getMemento());
System.out.println(ganmeRole.toString());
}
4.9.2 优缺点和使用场景
- 优点:
- 提醒了一种可以恢复状态的机制,当用户需要时能够比较方便的将数据恢复到某个历史的状态
- 实现了内部状态的封装,除了创建它的发起人之外,其他对象都不能够访问 这些状态信息
- 简化饿发起人类,发起人不再需要管理员和保存其内部状态的各个备份,所有的状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则
- 缺点:
- 资源消耗大,如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源
需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能,
需要提供一个可回滚操作的场景,如word,PS,idea等软件的撤销功能,还有数据库中事务的操作
4.10解释器模式
- 结构:
- 解释器模式包含以下主要角色
- 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法interpret()
- 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体表达式与之相对应
- 非终结符表达式(Nonterminal Expression)角色:也是抽象表达的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式
- 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一把用来传递被所有解释器共享的数据,后面解释器可以从这里获取这些值
- 客户端(Client)主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法书,然后调用解释器的解释方法
4.10.1 代码运行
- 设计只有一个加减法的计算器
/**
* @author LXY
* @desc
* @time 2023--12--26--17:04
*/
public interface AbstractEcpresssion {
int interpret(Context c);
}
/**
* @author LXY
* @desc
* @time 2023--12--26--17:06
*/
public class Plus implements AbstractEcpresssion{
private AbstractEcpresssion left;
private AbstractEcpresssion right;
public Plus(AbstractEcpresssion left, AbstractEcpresssion right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context c) {
//相加
return left.interpret(c)+right.interpret(c);
}
}
/**
* @author LXY
* @desc 减法
* @time 2023--12--26--17:06
*/
public class Minus implements AbstractEcpresssion{
private AbstractEcpresssion left;
private AbstractEcpresssion right;
public Minus(AbstractEcpresssion left, AbstractEcpresssion right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context c) {
//相加
return left.interpret(c)-right.interpret(c);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--17:05
*/
public class Variable implements AbstractEcpresssion{
String name;
public Variable(String name) {
this.name = name;
}
@Override
public int interpret(Context c) {
return c.getvalue(this);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--17:05
*/
public class Context {
//定义存储Variable
Map<Variable,Integer>map=new HashMap<>();
public void assign(Variable val,Integer value){
map.put(val,value);
}
public int getvalue(Variable v){
return map.get(v);
}
}
/**
* @author LXY
* @desc
* @time 2023--12--26--17:11
*/
public class Client {
public static void main(String[] args) {
Variable a=new Variable("a");
Variable b=new Variable("b");
Variable c=new Variable("c");
Variable d=new Variable("d");
Context context=new Context();
context.assign(a,1);
context.assign(b,2);
context.assign(c,3);
context.assign(d,4);
//简单加减法 1+2
// AbstractEcpresssion abstractEcpresssion=new Plus(a,b);
//1+2-3
AbstractEcpresssion abstractEcpresssion=new Plus(a,new Minus(b,c));
System.out.println("abstractEcpresssion.interpret(context) = " + abstractEcpresssion.interpret(context));
}
}
4.10.2 优缺点
-
优点:易于改变和扩展文法
由于在解释器模式中使用类来表示文言的文法规则,因此可以通过继承机制来改变或扩展文法,每一条文法负责都可以表示是一个类,因此可以方便的实现一个简单的语言 -
实现文法比较容易
在抽象语法书中每一个表达式节点类的实现方式都是相似的,这些类的代码编写的都不会特别复杂 -
增加新的解释表达式较为方便
如果用户需要增加新的解释表达式只需要对应增加一个新的总结福表达式或者非终结符表达式类,原有表达式类大马无需修改,符合开闭原则 -
缺点:
-
对于复杂文法难以维护
在解释器模式中,每一条规则至少需要定义一个类,因此如果一个预压包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护 -
执行效率低
由于在解释器模式使用了大量的循环和递归调用,因此在解释较为复杂的句子时速度很慢,而代码的调试过程也比较麻烦