24种设计模式之结构型模式-Java版

软件设计模式是前辈们代码设计经验的总结,可以反复使用。设计模式共分为3大类,创建者模式(6种)、结构型模式(7种)、行为型模式(11种),一共24种设计模式,软件设计一般需要满足7大基本原则。下面通过5章的学习一起来看看设计模式的魅力吧。

结构型模式(7种):本质上是将类或者对象按照某种布局组成更大的结构。

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

目录

1.1、代理模式

1.1.1、静态代理

1.1.2、JDK动态代理

1.1.3、CGlib动态代理

1.2、适配器模式

1.2.1、类适配器模式

1.2.2、对象适配器模式

1.3、装饰者模式

1.4、桥接模式

1.5、外观模式

1.6、组合模式

1.7、享元模式


1.1、代理模式

代理模式:由于某些原因不能直接访问对象,而是通过给对象提供一个代理,以确保对象的访问,代理对象作为访问对象和目标对象之间的中介。

Java中按照代理类的生成时期不同分为静态代理与动态代理,静态代理类是在变编译时候生成的,动态代理类是在Java运行的时候生成的,动态代理又分为JDK动态代理与CGlib动态代理。

三种代理模式的对比:
整体来说JDK动态代理的效率的高于CGlib,所以有接口使用JDK动态代理,没有接口使用CGlib动态代理。

动态代理与静态代理相比,最大的好处在于接口/类中 的方法都被集中到一个invoke()方法中进行处理,另外动态代理的代码耦合度低,动态生成代理类的,不是手动编写代理类。
 

代理模式的优缺点:

优点:

代理对象相当于目标对象和客户端之间的一个中介,起到了一种保护目标对象的作用。

代理对象在一定程度上扩展了目标对象的功能,即方法增强。

代理对象不改变目标对象的代码可以实现增强,即实现了解耦。

缺点:

增加了系统的复杂度。

应用场景:

远程代理,防火墙代理、保护代理

代理模式可以分为三种角色:

01.抽象主题类:通过接口或者抽象类声明的由真实主题与代理类实现的方法。

02.真实主题类:实现抽象主题中的具体业务,是最终要引用的对象。

03.代理类:内含有与真实主题相同的接口,可以访问、控制、扩展真实主题的功能。

1.1.1、静态代理

静态代理类是在变编译时候生成的,我们看一下火车票卖票的案例。

1.首先定义一个卖票接口与具体的火车站买票实现类实现卖票接口。

/*** @author nuist__NJUPT* @InterfaceName SellTickets* @description: 买票接口* @date 2024年01月20日*/
public interface SellTickets {void sell() ;
}
/*** @author nuist__NJUPT* @ClassName TrainStation* @description: 火车站* @date 2024年01月20日*/
public class TrainStation implements SellTickets {@Overridepublic void sell() {System.out.println("火车站卖票");}
}

2.通过 代理类的方式进行买票,也就是创建代理类,聚合了火车站对象,同时对火车站对象进行了方法增强,具体如下:


/*** @author nuist__NJUPT* @ClassName ProxyPoint* @description: 代理类对象* @date 2024年01月20日*/
public class ProxyPoint implements SellTickets {private TrainStation trainStation = new TrainStation();@Overridepublic void sell() {// 方法增强System.out.println("代售点收取一些费用!");// 代理目标对象trainStation.sell();}
}

3.测试类中直接访问代理对象就可以实现对目标对象的访问了,同时增强了目标对象方法。

/*** @author nuist__NJUPT* @ClassName Client* @description: 测试类* @date 2024年01月20日*/
public class Client {public static void main(String[] args) {/***   测试类中直接访问代理类就可以访问目标对象了*   同时在代理类中也可以对目标对象进行方法增强*/ProxyPoint proxyPoint = new ProxyPoint() ;proxyPoint.sell();}
}
1.1.2、JDK动态代理

Java中提供了一个动态代理类Proxy,它提供了一个创建代理对象的代理方法来获取代理对象。

1.首先还是定义卖票接口与其具体目标实现类,也称为目标对象。

/*** @author nuist__NJUPT* @InterfaceName SellTickets* @description: 卖票接口* @date 2024年01月20日*/
public interface SellTickets {void sell() ;
}
/*** @author nuist__NJUPT* @ClassName TrainStation* @description: 火车站接口:目标对象类* @date 2024年01月20日*/
public class TrainStation implements SellTickets {@Overridepublic void sell() {System.out.println("火车站卖票");}
}

2.定义代理类,通过Proxy类的静态方法newProxyInstance()去获取代理对象(反射机制通过目标对象获取代理对象),然后实现InvationHandler接口并重写invoke()方法进行目标对象的方法增强。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author nuist__NJUPT* @ClassName ProxyFactory* @description: 获取代理对象的工厂类* @date 2024年01月20日*/
public class ProxyFactory {// 声明目标对象private TrainStation trainStation = new TrainStation() ;// 获取代理对象public SellTickets getProxyObject(){/*** 返回代理对象的方法* Proxy.newProxyInstance()静态方法中包含三个参数* 1.类加载器:用于加载代理类,可以通过目标对象加载代理类* 2.实现类接口的字节码对象* 3.代理对象的调用处理程序*/SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(trainStation.getClass().getClassLoader(),trainStation.getClass().getInterfaces(),new InvocationHandler() {/**** @param proxy 代理对象* @param method 对接口中的方法进行封装的method方法* @param args 调用方法的实际参数* @return 方法返回值,sell()方法返回值* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("动态代理收取了费用,方法增强!!!");// 执行目标对象的方法Object object = method.invoke(trainStation, args);return object;}});return proxyObject ;}}

注意:我们需要明确的是ProxyFactory类是代理工厂类,并不是代理类,代理类是在运行过程中在内存中动态生成的类。

1.1.3、CGlib动态代理

如果没有定义接口,只定义了目标对象类,那么就不能使用JDK进行动态代理,因为JDK是对接口的代理,这时候可以使用CGlib代理,CGlib是一个高性能的代码生成包,CGlib是第三方提供的包,需要导入才能使用。

1.首先引入CGlib三方依赖包,如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>design</artifactId><version>1.0-SNAPSHOT</version><dependencies><!--引入CGlib动态代理依赖--><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency></dependencies></project>

2.编写目标对象类,该类是用于被代理的对象。

/*** @author nuist__NJUPT* @ClassName TrainStation* @description: 目标对象类* @date 2024年01月21日*/public class TrainStation {public void sell() {System.out.println("火车站卖票");}
}

3.编写代理工厂类,获取代理对象,通过实现MethodInterceptor接口并重写intercept()方法进行动态代理。


import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @author nuist__NJUPT* @ClassName ProxyFactory* @description: 代理工厂对象,用于获取代理对象并对目标对象进行代理* @date 2024年01月21日*/
public class ProxyFactory implements MethodInterceptor {// 声明火车站对象private TrainStation trainStation ;// 面向目标对象类进行代理public TrainStation getProxyObject(TrainStation trainStation){this.trainStation = trainStation ;// 创建Enhancer对象Enhancer enhancer = new Enhancer() ;// 设置父类的字节码对象enhancer.setSuperclass(this.trainStation.getClass());// 设置回调函数enhancer.setCallback(this);// 创建并返回代理对象TrainStation proxyObject = (TrainStation) enhancer.create();return proxyObject ;}/*** 实现MethodInterceptor接口,并重写intercept方法进行动态代理* @param o 代理对象实例本身,即CGLib动态生成的目标类的子类实例* @param method 将要被调用的目标方法的反射对象,通过它可以获取到方法的名称、返回类型以及参数列表等信息* @param objects 包含了即将传递给目标方法的实际参数值* @param methodProxy* @return 目标方法的执行结果* @throws Throwable*/public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 方法增强System.out.println("CGlib代理方法增强");// 调用目标对象的方法Object object = method.invoke(trainStation, objects);return object;}
}

4.编写测试类,测试CGlib动态代理。

/*** @author nuist__NJUPT* @ClassName Client* @description: 测试类* @date 2024年01月21日*/
public class Client {public static void main(String[] args) {// 创建代理对象工厂ProxyFactory proxyFactory = new ProxyFactory() ;// 实例化目标对象TrainStation trainStation = new TrainStation() ;// 获取代理对象TrainStation proxyObject = proxyFactory.getProxyObject(trainStation);// 通过代理对象对目标对象进行方法增强proxyObject.sell();}
}
1.2、适配器模式

适配器模式:将一个接口转换成希望的另外一个接口,使原来因为接口不兼容而不能在 一起工作的接口可以在一起工作。

适配器模式分为类适配器模式与对象适配器模式,一般来说,类适配模式的耦合度更高一些,所以一般我们使用的还是对象适配器模式。

适配器模式主要包含三个角色:

目标接口:当前系统业务期待的接口,可以是抽象类或者接口。

适配者类:它是被访问和适配的现存组件库中的接口。

适配器类:通过继承或者引用适配者对象,将适配者接口转换成为目标接口,让客户按照目标接口的形式访问适配者。
适配器模式的应用场景:

以前开发的系统的接口满足新系统的功能,但是接口类型不一致

使用第三方定义的接口,但是与自己定义的接口不同

1.2.1、类适配器模式

1.我们通过下面一个案例去感受一下类适配器,首先我们定义适配者接口与器适配者实现类。

/*** @author nuist__NJUPT* @InterfaceName TFCard* @description: 适配者类接口* @date 2024年01月21日*/
public interface TFCard {// 从TF卡中读取数据String readTF() ;// 向TF卡中写数据void writeTF(String msg) ;}
/*** @author nuist__NJUPT* @ClassName TFCardImpl* @description: 适配者类* @date 2024年01月21日*/
public class TFCardImpl implements TFCard {public String readTF() {String msg = "TF-data" ;return msg;}public void writeTF(String msg) {System.out.println("write:" + msg);}
}

2.然后我们定义目标接口和目标接口实现类。

/*** @author nuist__NJUPT* @InterfaceName SDCard* @description: 目标接口* @date 2024年01月21日*/
public interface SDCard {// 从SD卡中读取数据String readSD() ;// 向TF卡中写数据void writeSD(String msg) ;}
/*** @author nuist__NJUPT* @ClassName SDCardImpl* @description: 目标接口实现类* @date 2024年01月21日*/
public class SDCardImpl implements SDCard {public String readSD() {String msg = "SD-data" ;return msg;}public void writeSD(String msg) {System.out.println("write:" + msg);}
}

3.接着我们定义中间类用于从SD卡读取数据。

/*** @author nuist__NJUPT* @ClassName Computer* @description:* @date 2024年01月21日*/
public class Computer {// 从SD卡读数据public String readSD(SDCard sdCard) throws Exception {if(sdCard == null){throw new Exception("SD card is null") ;}return sdCard.readSD() ;}}

4.我们定义适配器类用于通过目标对象类去适配待适配者类,进行进行读取数据。

适配器类需要继承待适配者类并重写目标对象接口。


/*** @author nuist__NJUPT* @ClassName SDAdapterTF* @description: 适配器类* @date 2024年01月21日*/
public class SDAdapterTF extends TFCardImpl implements SDCard{public String readSD() {System.out.println("适配器读取TF卡");return readTF() ;}public void writeSD(String msg) {System.out.println("适配器写入IF卡");writeTF(msg);}
}

5.通过测试类测试通过适配器类适配,使用目标对象接口格式从待适配者中读取数据。

/*** @author nuist__NJUPT* @ClassName Client* @description: 客户端测试类* @date 2024年01月21日*/
public class Client {public static void main(String[] args) throws Exception {// 创建计算机对象Computer computer = new Computer() ;// 读取SD卡中的数据String msg = computer.readSD(new SDCardImpl());System.out.println(msg);System.out.println("=================================");// 使用该电脑读取TF卡中的数据//定义适配器类SDAdapterTF sdAdapterTF = new SDAdapterTF() ;// 通过适配者类适配String msg1 = computer.readSD(sdAdapterTF);System.out.println(msg1);}
}
1.2.2、对象适配器模式

对象适配器模式可以减少对待适配器类的继承,满足合成复用原则,减少代码耦合度。对于上述的类适配器,只需要做如下修改就是对象适配了,不通过继承而是通过注入和构造器构造的方式。

/*** @author nuist__NJUPT* @ClassName SDAdapterTF* @description: 适配器类* @date 2024年01月21日*/
public class SDAdapterTF  implements SDCard {//  声明适配者类private TFCard tfCard ;public SDAdapterTF(TFCard tfCard) {this.tfCard = tfCard;}public String readSD() {System.out.println("适配器读取TF卡");return tfCard.readTF();}public void writeSD(String msg) {System.out.println("适配器写入IF卡");tfCard.writeTF(msg);}
}
1.3、装饰者模式

装饰者模式:在不改变现有对象接口的情况下,动态地给对象增加一些额外的功能。

装饰者模式共包含如下几个角色:
01.抽象构建:定义一个抽象接口以规范准备接收附加责任的对象。

02.具体构建:实现抽象构建,可以 通过装饰角色为其添加一些功能。

03.抽象装饰:继承或者实现抽象构建,并且包含具体构建的实例。

04.具体装饰:实现抽象装饰的相关方法,并给具体构建对象添加附加的功能。

装饰器模式的优点:

装饰者模式比继承更方便扩展,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。

装饰类与被装饰类是相互独立的,不会互相耦合。

装饰器模式的使用场景:
当采用继承的方式不利于系统的扩展和维护的时候,可以使用装饰者模式。

在不影响其它对象的情况下,以动态、透明的方式给单个对象添加职责。

当对象的功能可以动态的添加,也可以动态的撤销的时候。

静态代理与装饰者模式的区别:

相同点:都可以在不修改目标类的前提下增强目标方法。

不同点:装饰者是增强目标对象,静态代理主要是为了保护和隐藏目标对象。

获取目标对象构建的地方也不同,装饰者是由外界传递的,静态代理是在代理类内部创建的。

我们下面通过一个炒饭加鸡蛋和培根的案例来学习一下装饰者模式:

1.首先定义一个抽象构建角色,即快餐类。


/*** @author nuist__NJUPT* @ClassName FastFood* @description: 快餐类-抽象构建角色* @date 2024年01月21日*/
public abstract class FastFood {private float price ;private String description ;public FastFood() {}public FastFood(float price, String description) {this.price = price;this.description = description;}public float getPrice() {return price;}public void setPrice(float price) {this.price = price;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}public abstract float cost() ;
}

2.根据父类的抽象构建角色,通过继承的方式进行具体构建角色的创建。

/*** @author nuist__NJUPT* @ClassName FriedRice* @description: 炒饭-具体构建角色* @date 2024年01月21日*/
public class FriedRice extends FastFood{public FriedRice(){super(10, "炒饭");}public float cost() {return getPrice();}}
/*** @author nuist__NJUPT* @ClassName FriedNoodles* @description: 炒面-具体构建角色* @date 2024年01月21日*/
public class FriedNoodles extends FastFood{public FriedNoodles(){super(12, "炒面");}public float cost() {return getPrice();}
}

3.定义一个抽象的装饰者类,将抽象的构建角色类注入。

/*** @author nuist__NJUPT* @ClassName Garnish* @description: 装饰器类-抽象装饰者角色* @date 2024年01月21日*/
public abstract class Garnish extends FastFood{// 声明快餐类的变量private FastFood fastFood ;public FastFood getFastFood() {return fastFood;}public void setFastFood(FastFood fastFood) {this.fastFood = fastFood;}public Garnish(float price, String description, FastFood fastFood) {super(price, description);this.fastFood = fastFood;}
}

4.定义两个具体的装饰者类,继承抽象装饰者类对具体构建对象进行装饰。

/*** @author nuist__NJUPT* @ClassName Bacon* @description: 培根类-具体的装饰器* @date 2024年01月21日*/
public class Bacon extends Garnish {public Bacon(FastFood fastFood) {super(2,"培根", fastFood);}public float cost() {return getPrice() + getFastFood().cost();}@Overridepublic String getDescription() {return super.getDescription() + getFastFood().getDescription();}}
/*** @author nuist__NJUPT* @ClassName Egg* @description: 鸡蛋-具体的装饰者类* @date 2024年01月21日*/
public class Egg extends Garnish {public Egg(FastFood fastFood) {super(1,"鸡蛋", fastFood);}public float cost() {return getPrice() + getFastFood().cost();}@Overridepublic String getDescription() {return super.getDescription() + getFastFood().getDescription();}
}

5.定义客户端测试类,通过装饰器类对具体对象进行装饰,如下:

/*** @author nuist__NJUPT* @ClassName Client* @description: 客户端测试类* @date 2024年01月21日*/
public class Client {public static void main(String[] args) {// 点炒饭FastFood friedRice = new FriedRice() ;System.out.println(friedRice.getDescription() + friedRice.cost() + "元");// 在炒饭中加一个鸡蛋friedRice= new Egg(friedRice) ;System.out.println(friedRice.getDescription() + friedRice.cost() + "元");// 在炒饭中加一个培根friedRice = new Bacon(friedRice) ;System.out.println(friedRice.getDescription() + friedRice.cost() + "元");}}
1.4、桥接模式

桥接模式:将抽象与实现分离,使得它们可以独立变化,利用组合关系代替继承关系来实现,降低抽象与实现这两个维度的耦合度。

桥接模式主要包含如下几个角色:
01.抽象化角色:定义抽象类,并包含一个对实现化对象的引用。

02.扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。

03.实现化角色:定义接口供扩展抽象化角色调用。

04.具体实现化角色:给出实现化 角色接口的具体实现。

桥接模式的优势与使用场景:

扩展性比较好,在两个变化维度任意修改一个维度都不需要修改原有系统。

实现细节对客户透明

当系统不希望使用继承可以使用桥接模式进行改进。

下面我们通过一个视频播放器的案例来具体理解一下桥接模式的概念,首先定义以合实现化角色,并定义两个实现化角色的实现类,具体如下:

/*** @author nuist__NJUPT* @InterfaceName VideoFile* @description: 视频文件-实现化角色* @date 2024年01月21日*/
public interface VideoFile {// 解码功能void decode(String fileName) ;
}
/*** @author nuist__NJUPT* @ClassName RmvVideo* @description: rmv视频文件-具体的实现化角色* @date 2024年01月21日*/
public class RmvVideo implements VideoFile {public void decode(String fileName) {System.out.println("rmv视频文件:" + fileName);}
}
/*** @author nuist__NJUPT* @ClassName AviVideo* @description: avi视频文件-具体实现化角色* @date 2024年01月21日*/
public class AviVideo implements VideoFile {public void decode(String fileName) {System.out.println("avi视频文件:" + fileName);}
}

2.定义抽象化角色与扩展抽象化角色。

/*** @author nuist__NJUPT* @ClassName OperatingSystem* @description: 操作系统类-抽象化角色* @date 2024年01月21日*/
public abstract class OperatingSystem {// 声明videoFile变量protected  VideoFile videoFile ;public OperatingSystem(VideoFile videoFile) {this.videoFile = videoFile;}public abstract void display(String fileName) ;
}

/*** @author nuist__NJUPT* @ClassName Mac* @description: 扩展实现化-Mac操作系统* @date 2024年01月21日*/
public class Mac extends OperatingSystem {public Mac(VideoFile videoFile) {super(videoFile);}public void display(String fileName) {videoFile.decode(fileName);}
}

/*** @author nuist__NJUPT* @ClassName Windows* @description: 扩展抽象化角色-windows操作系统* @date 2024年01月21日*/
public class Windows extends OperatingSystem {public Windows(VideoFile videoFile) {super(videoFile);}public void display(String fileName) {videoFile.decode(fileName);}
}

3.定义客户端测试类进行测试。


/*** @author nuist__NJUPT* @ClassName Client* @description: 客户端测试类* @date 2024年01月21日*/
public class Client {public static void main(String[] args) {// 创建mac系统对象OperatingSystem operatingSystem = new Mac(new AviVideo()) ;// 使用操作系统播放视频文件operatingSystem.display("战狼3");}}
1.5、外观模式

外观模式又称为门面模式,是通过为多个复杂的子系统提供一致的接口,使得这些子系统更容易被访问。外部应用程序不需要关系内部子系统的内部实现细节。

外观模式的优缺点:

优点:降低了子系统与客户端的耦合,子系统的变化不会影响调用它的客户端。

对客户屏蔽了子组件的细节,减少了客户处理子组件的数目。

缺点:

不符合开闭原则,修改比较麻烦

应用场景:

对分层系统进行构建的时候,使用外观模式定义每层系统的入口,可以简化子系统之间的依赖关系

我们下面通过一个智能家居的案例,通过提供一个智能音箱接口控制所有智能家电的开启与关闭。

1.首先定义三个类,开灯、开空调、开电视类。

/*** @author nuist__NJUPT* @ClassName Light* @description: 电灯类* @date 2024年01月22日*/
public class Light {// 开灯public void on(){System.out.println("打开电灯...");}// 关灯public void off(){System.out.println("关闭电灯...");}}
/*** @author nuist__NJUPT* @ClassName TV* @description: 电视机类* @date 2024年01月22日*/
public class TV {// 开电视public void on(){System.out.println("打开电视...");}// 关电视public void off(){System.out.println("关闭电视...");}
}
/*** @author nuist__NJUPT* @ClassName AirCondition* @description: 空调类* @date 2024年01月22日*/
public class AirCondition {// 开空调public void on(){System.out.println("打开空调...");}// 关空调public void off(){System.out.println("关闭空调...");}
}

2.定义一个外观类,用户通过外观类实现控制家电得开启与关闭。

/*** @author nuist__NJUPT* @ClassName SmartApplication* @description: 外观类,用户与外观类进行交互* @date 2024年01月22日*/
public class SmartApplication {private Light light ;private TV tv ;private AirCondition airCondition ;public SmartApplication(Light light, TV tv, AirCondition airCondition) {this.light = light;this.tv = tv;this.airCondition = airCondition;}// 通过语音控制public  void say(String msg){if(msg.equals("打开家电")){on() ;}else if(msg.equals("关闭家电")){off() ;}else{System.out.println("语音无法识别");}}// 打开功能private void on(){light.on();tv.on() ;airCondition.on() ;}// 关闭功能private void off(){light.off() ;tv.off();airCondition.off() ;}
}

3.定义测试类,测试使用外观类开启与关闭家电。

/*** @author nuist__NJUPT* @ClassName Client* @description: 客户端测试类* @date 2024年01月22日*/
public class Client {public static void main(String[] args) {SmartApplication smartApplication = new SmartApplication(new Light(), new TV(), new AirCondition()) ;// 打开家电smartApplication.say("打开家电");// 关闭家电smartApplication.say("关闭家电");}
}
1.6、组合模式

组合模式:又称为部分整体模式,用于把一组相似的对象当作一个单一的对象,组合模式依据树形结构来组合对象,用来表示部分和整体的层次。

组合模式主要包含如下三个角色:

01.抽象根节点:定义系统各层次的共有属性和方法。

02.树枝节点:定义树枝节点的行为以及存储叶子节点。

03.叶子节点:叶子节点对象,是系统遍历的最小分支。

组合模式的优点及使用场景:

优点:组合模式可以清晰地定义复杂的分层结构,表示对象的全部或者部门层次,使得客户端忽略了层次的差异。

组合模式是树形结构,适用于树形结构的场景,比如文件结构和目录结构。

下面通过一个文件结构的案例来学习一下组合模式:

1.首先定义菜单组件,表示抽象根节点,定义系统各层次共用属性与方法。

/*** @author nuist__NJUPT* @ClassName MenuComponent* @description: 菜单组件-抽象根节点* @date 2024年01月22日*/
public abstract class MenuComponent {// 菜单组件的名称protected String name ;// 菜单组件的层级protected int level ;// 添加子菜单public void add(MenuComponent menuComponent) throws Exception {throw new Exception("不支持添加操作") ;}// 移除子菜单public void remove(MenuComponent menuComponent) throws Exception {throw new Exception("不支持移除操作") ;}// 获取指定的子菜单public MenuComponent getChild(int index) throws Exception {throw new Exception("不支持获取") ;}// 获取菜单或者菜单项的名称public String getName(){return name ;}// 打印菜单名称的方法:包含子菜单和子菜单项public abstract void print() ;}

2.然后定义菜单类,表示树枝节点,如下:


import java.util.ArrayList;
import java.util.List;/*** @author nuist__NJUPT* @ClassName Menu* @description: 菜单类-树枝节点* @date 2024年01月22日*/
public class Menu extends MenuComponent{// 菜单可以有多个子菜单或者子菜单项private List<MenuComponent> menuComponents = new ArrayList<MenuComponent>() ;// 构造方法public Menu(String name, int level){this.name = name ;this.level = level ;}@Overridepublic void add(MenuComponent menuComponent) throws Exception {menuComponents.add(menuComponent);}@Overridepublic void remove(MenuComponent menuComponent) throws Exception {menuComponents.remove(menuComponent);}@Overridepublic MenuComponent getChild(int index) throws Exception {return menuComponents.get(index);}public void print() {// 打印菜单名称for(int i=0; i<level; i++){System.out.print("--");}System.out.println(name) ;// 打印子菜单或者子菜单名称for(MenuComponent component : menuComponents){component.print() ;}}
}

3.然后定义菜单项类表示叶子节点,如下:

/*** @author nuist__NJUPT* @ClassName MenuItem* @description: 菜单项类-叶子节点* @date 2024年01月22日*/
public class MenuItem extends MenuComponent{MenuItem(String name, int level){this.name = name ;this.level = level ;}public MenuItem() {}public void print() {// 打印菜单项目的名称for(int i=0; i<level; i++){System.out.print("--");}System.out.println(name);}
}

4.最后,在客户端进行测试。

/*** @author nuist__NJUPT* @ClassName Client* @description: 测试类* @date 2024年01月22日*/
public class Client {public static void main(String[] args) throws Exception {// 创建菜单树MenuComponent menuComponent1 = new Menu("菜单管理", 2) ;menuComponent1.add(new MenuItem("页面访问",3));menuComponent1.add(new MenuItem("展开菜单", 3));menuComponent1.add(new MenuItem("编辑菜单", 3)) ;menuComponent1.add(new MenuItem("删除菜单", 3));menuComponent1.add(new MenuItem("新增菜单", 3)) ;MenuComponent menuComponent2 = new Menu("权限管理", 2) ;menuComponent2.add(new MenuItem("页面访问",3));menuComponent2.add(new MenuItem("提交保存", 3));MenuComponent menuComponent3 = new Menu("角色管理", 2) ;menuComponent3.add(new MenuItem("页面访问",3));menuComponent3.add(new MenuItem("新增角色", 3));menuComponent3.add(new MenuItem("修改角色",3));// 创建一级菜单MenuComponent menuComponent = new Menu("系统管理", 1) ;// 将二级菜单添加到一级菜单中menuComponent.add(menuComponent1) ;menuComponent.add(menuComponent2) ;menuComponent.add(menuComponent3) ;// 打印菜单名称menuComponent.print();}
}
1.7、享元模式

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

享元模式共包含两种状态:

1.内部状态:不会随着环境改变而改变的可共享部分。

2.外部状态:随着环境改变而改变的不可共享的部分。

享元模式主要包含如下几个角色:

01.抽象享元角色:通常是一个接口或者抽象类,在抽象享元类中声明了具体享元类公用的方法。

02.具体享元:实现了抽象享元,在具体享元中为内部对象提供了存储空间。

03.非享元:并不是所有的抽象享元的子类都要被共享,不需要共享的子类可以设计为非享元。

04.享元工厂:复杂创建和管理享元角色。

享元模式的优缺点:

优点:极大减少了相似或者相同的对象,节约了系统资源,提升了系统性能。

享元模式的外部状态不会影响内部状态。

缺点:分离内部外状态会导致逻辑复杂。

下面通过俄罗斯方块案例来学习一下享元模式。

1.定义一个抽象享元角色,其中定义公用方法由具体享元重写。

/*** @author nuist__NJUPT* @ClassName AbstractBox* @description: 抽象享元角色* @date 2024年01月22日*/
public abstract class AbstractBox {// 获取图像的方法public abstract String getShape() ;// 显示图形及颜色public void display(String color){System.out.println("方块的形状:"+getShape() + "方块的颜色:" + color);}
}

2.定义三个具体享元角色继承抽象享元角色并重写公用方法。

/*** @author nuist__NJUPT* @ClassName IBox* @description: 具体享元角色-I图形类* @date 2024年01月22日*/
public class IBox extends AbstractBox{public String getShape() {return "I";}
}
/*** @author nuist__NJUPT* @ClassName OBox* @description: 具体享元角色-O图形类* @date 2024年01月22日*/
public class OBox extends AbstractBox {public String getShape() {return "O";}
}
/*** @author nuist__NJUPT* @ClassName LBox* @description: TODO* @date 2024年01月22日*/
public class LBox extends AbstractBox{public String getShape() {return "L";}
}

3.定义享元工厂,用于创建与管理享元角色。


import java.util.HashMap;/*** @author nuist__NJUPT* @ClassName BoxFactory* @description: 享元工厂* @date 2024年01月22日*/
public class BoxFactory {private HashMap<String, AbstractBox> map ;// 在构造方法中进行初始化操作private BoxFactory(){map = new HashMap<String, AbstractBox>() ;map.put("I", new IBox()) ;map.put("L", new LBox()) ;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) ;}}

4.最后定义测试类测试享元模式。


/*** @author nuist__NJUPT* @ClassName Client* @description: 客户端测试类* @date 2024年01月22日*/
public class Client {public static void main(String[] args) {// 获取I图形对象AbstractBox box1 = BoxFactory.getInstance().getShape("I");box1.display("红色");// 获取O图形对象AbstractBox box2 = BoxFactory.getInstance().getShape("O");box2.display("蓝色");// 获取L图形对象AbstractBox box3 = BoxFactory.getInstance().getShape("L");box3.display("绿色");}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/641866.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java应用崩溃的排查流程

目录 分析问题 hs_err_pid.log 上周排查了一个java应用的崩溃问题&#xff0c;在这里记录一下。 分析问题 首先是排查到/tmp目录下有很多的core文件&#xff0c;形式类似&#xff1a; core-18238-java-1705462412 1.3 GB 程序崩溃数据 2024-01-17 11:33:44 core-18108…

Leetcode28-合并相似的物品(2363)

1、题目 给你两个二维整数数组 items1 和 items2 &#xff0c;表示两个物品集合。每个数组 items 有以下特质&#xff1a; items[i] [valuei, weighti] 其中 valuei 表示第 i 件物品的 价值 &#xff0c;weighti 表示第 i 件物品的 重量 。 items 中每件物品的价值都是 唯一…

jasypt集成spring-boot原理解析

背景 每个应用都有很多配置项&#xff0c;有些配置项对外非常敏感&#xff0c;例如数据库连接密码、私钥等。使用明文存在泄露的风险&#xff0c;生产环境要配合加密算法。jasypt是一个方便、流行的加密工具&#xff0c;支持PBE、AEC和对称加密。它与spring-boot的集成度很高&…

语义分割常用评价指标

在图像处理领域中&#xff0c;语义分割是很重要的一个任务。在实际项目开发中,评估模型预测效果以及各指标的含义对于优化模型极为重要。 本文将主要评价指标的计算算法进行了详细说明,并加上注释解释每个指标的含义。这对理解各指标背后的数学原理以及能否在实践中应用或许有…

对接银行、第三方机构思考

对接银行、第三方机构思考 前置工作 1、产品对接(商务+产品) ①了解准入流程,需要签署哪些协议、合约场景、额度、费率等,估算内部准入流程需要时间判断是否对项目上线排期有影响 ②是否符合自己所要承接的业务场景,有哪些合适,有哪些不合适,不合适的地方对方是否有备…

GPS位置虚拟软件 AnyGo mac激活版

AnyGo for Mac是一款一键将iPhone的GPS位置更改为任何位置的强大软件&#xff01;使用AnyGo在其iOS或Android设备上改变其GPS位置&#xff0c;并在任何想要的地方显示自己的位置。这对那些需要测试应用程序、游戏或其他依赖于地理位置信息的应用程序的开发人员来说非常有用&…

Python - SnowNLP 情感分析与自定义训练

目录 一.引言 二.SnowNLP 情感分析 1.安装 SnowNLP 2.测试 SnowNLP 三.SnowNLP 自定义训练 1.数据集准备 2.训练与保存 3.模型替换 4.模型测试 5.SnowNLP 原理 ◆ Bayes 公式 ◆ 先验概率 ◆ 后验概率 ◆ 情感模型 四.总结 一.引言 SnowNLP 是一个基于 Python …

网络安全产品之认识防毒墙

在互联网发展的初期&#xff0c;网络结构相对简单&#xff0c;病毒通常利用操作系统和软件程序的漏洞发起攻击&#xff0c;厂商们针对这些漏洞发布补丁程序。然而&#xff0c;并不是所有终端都能及时更新这些补丁&#xff0c;随着网络安全威胁的不断升级和互联网的普及&#xf…

Android双指缩放ScaleGestureDetector检测放大因子大图移动到双指中心点ImageView区域中心,Kotlin

Android双指缩放ScaleGestureDetector检测放大因子大图移动到双指中心点ImageView区域中心&#xff0c;Kotlin 在 Android双击图片放大移动图中双击点到ImageView区域中心&#xff0c;Kotlin-CSDN博客 基础上&#xff0c;这次使用ScaleGestureDetector检测两根手指的缩放动作&a…

Python如何叠加两张图片

我这里有如下两张图片&#xff0c;需要把他们叠加在一起&#xff0c;进行查看。这两张图片的大小都是300 300。不拼接在一起就不方便查看。需要把左边的小图&#xff0c;放到右边大图的中间。 一、拼接两个图片的代码 要解决这个问题&#xff0c;你可以使用fromarray()方法将…

JoyRL Actor-Critic算法

策略梯度算法的缺点 这里策略梯度算法特指蒙特卡洛策略梯度算法&#xff0c;即 REINFORCE 算法。 相比于 DQN 之类的基于价值的算法&#xff0c;策略梯度算法有以下优点。 适配连续动作空间。在将策略函数设计的时候我们已经展开过&#xff0c;这里不再赘述。适配随机策略。由…

MATLAB数据处理: 每种样本类型随机抽样

tn5;% 每种类型随机抽样数 indextrain[];% 训练样本序号集 for i1:typenumber index301 find(typemat i); n2length(index301); index302randperm(n2); index401index301(index302(1:tn)); indextrain[indextrain; index401]; end 该代码可以对大样…

java进阶

文章目录 一、Java进阶1.注解&#xff08;Annotation&#xff09;a.内置注解b.元注解c.自定义注解 2.对象克隆3. Java设计模式&#xff08;Java design patterns&#xff09;a.软件设计模式概念b.建模语言&#xff08;UML&#xff09;c.面向对象设计原则d.设计模式 总结面向对象…

从0开始学习mysql 第十六课:数据库锁及InnoDB锁机制

第十六课&#xff1a;数据库锁及InnoDB锁机制 学习目标 今天我们要深入学习以下内容&#xff1a; 理解数据库锁的作用及其对事务控制的重要性。详细了解InnoDB支持的锁类型&#xff1a;共享锁&#xff08;S锁&#xff09;、排他锁&#xff08;X锁&#xff09;。掌握意向锁&a…

分享一个C++下使用简单的反射实现的程序模块化的思路

分享一个C下使用简单的反射实现的程序模块化的思路 首先说一个基本问题&#xff0c;项目大了以后&#xff0c;一定要做模块化处理&#xff0c;每个模块处理各自的事情&#xff0c;各个模块之间尽量不要有太多的耦合&#xff0c;就是说模块A尽量不要依赖模块B&#xff0c;模块B…

栈的基本操作(c++题解)

题目描述 栈&#xff1a;插入元素和删除元素只能在线性表的一端进行&#xff0c;所以遵循“先进后出 (LIFO) ”原则&#xff0c;其中插入和删除的一端称为栈顶 (top)。我们可以把栈比喻成一个箱子&#xff0c;只能在箱子的开口处放入和取出物体&#xff0c;而且是后放入的物体…

项目工程下载与XML配置文件下载:EtherCAT超高速实时运动控制卡XPCIE1032H上位机C#开发(十)

XPCIE1032H功能简介 XPCIE1032H是一款基于PCI Express的EtherCAT总线运动控制卡&#xff0c;可选6-64轴运动控制&#xff0c;支持多路高速数字输入输出&#xff0c;可轻松实现多轴同步控制和高速数据传输。 XPCIE1032H集成了强大的运动控制功能&#xff0c;结合MotionRT7运动…

MongoDB之整合SpringBoot

MongoTemplate 方式 依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency>创建账户 # 切换到数据库 use study # 创建账户 db.createUser({user:&quo…

深度解析Oladance、韶音、南卡开放式耳机:选购指南与天花板级推荐

​随着开放式耳机在日常生活中越来越受欢迎&#xff0c;许多品牌纷纷降低材料品质以迎合大众需求&#xff0c;导致耳机的性能和音质严重下滑。这让消费者在选择优质开放式耳机时感到困惑。作为一名专业的耳机评测人员&#xff0c;我近期对多款热门开放式耳机进行了深入的测评&a…

Leetcode—92.反转链表II【中等】

2023每日刷题&#xff08;八十一&#xff09; Leetcode—92.反转链表II 算法思想 实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), n…