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.首先定义一个卖票接口与具体的火车站买票实现类实现卖票接口。

java 复制代码
/**
 * @author nuist__NJUPT
 * @InterfaceName SellTickets
 * @description: 买票接口
 * @date 2024年01月20日
 */
public interface SellTickets {
    void sell() ;
}
java 复制代码
/**
 * @author nuist__NJUPT
 * @ClassName TrainStation
 * @description: 火车站
 * @date 2024年01月20日
 */
public class TrainStation implements SellTickets {

    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}

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

java 复制代码
/**
 * @author nuist__NJUPT
 * @ClassName ProxyPoint
 * @description: 代理类对象
 * @date 2024年01月20日
 */
public class ProxyPoint implements SellTickets {

    private TrainStation trainStation = new TrainStation();

    @Override
    public void sell() {
        // 方法增强
        System.out.println("代售点收取一些费用!");
        // 代理目标对象
        trainStation.sell();
    }
}

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

java 复制代码
/**
 * @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.首先还是定义卖票接口与其具体目标实现类,也称为目标对象。

java 复制代码
/**
 * @author nuist__NJUPT
 * @InterfaceName SellTickets
 * @description: 卖票接口
 * @date 2024年01月20日
 */
public interface SellTickets {
    void sell() ;
}
java 复制代码
/**
 * @author nuist__NJUPT
 * @ClassName TrainStation
 * @description: 火车站接口:目标对象类
 * @date 2024年01月20日
 */
public class TrainStation implements SellTickets {

    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}

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

java 复制代码
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
                     */
                    @Override
                    public 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 复制代码
<?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.编写目标对象类,该类是用于被代理的对象。

java 复制代码
/**
 * @author nuist__NJUPT
 * @ClassName TrainStation
 * @description: 目标对象类
 * @date 2024年01月21日
 */

public class TrainStation {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

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

java 复制代码
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动态代理。

java 复制代码
/**
 * @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.我们通过下面一个案例去感受一下类适配器,首先我们定义适配者接口与器适配者实现类。

java 复制代码
/**
 * @author nuist__NJUPT
 * @InterfaceName TFCard
 * @description: 适配者类接口
 * @date 2024年01月21日
 */
public interface TFCard {

    // 从TF卡中读取数据
    String readTF() ;
    // 向TF卡中写数据
    void writeTF(String msg) ;

}
java 复制代码
/**
 * @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.然后我们定义目标接口和目标接口实现类。

java 复制代码
/**
 * @author nuist__NJUPT
 * @InterfaceName SDCard
 * @description: 目标接口
 * @date 2024年01月21日
 */
public interface SDCard {

    // 从SD卡中读取数据
    String readSD() ;
    // 向TF卡中写数据
    void writeSD(String msg) ;

}
java 复制代码
/**
 * @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卡读取数据。

java 复制代码
/**
 * @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.我们定义适配器类用于通过目标对象类去适配待适配者类,进行进行读取数据。

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

java 复制代码
/**
 * @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.通过测试类测试通过适配器类适配,使用目标对象接口格式从待适配者中读取数据。

java 复制代码
/**
 * @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、对象适配器模式

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

java 复制代码
/**
 * @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.首先定义一个抽象构建角色,即快餐类。

java 复制代码
/**
 * @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.根据父类的抽象构建角色,通过继承的方式进行具体构建角色的创建。

java 复制代码
/**
 * @author nuist__NJUPT
 * @ClassName FriedRice
 * @description: 炒饭-具体构建角色
 * @date 2024年01月21日
 */
public class FriedRice extends FastFood{

    public FriedRice(){
        super(10, "炒饭");
    }

    public float cost() {
        return getPrice();
    }

}
java 复制代码
/**
 * @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.定义一个抽象的装饰者类,将抽象的构建角色类注入。

java 复制代码
/**
 * @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.定义两个具体的装饰者类,继承抽象装饰者类对具体构建对象进行装饰。

java 复制代码
/**
 * @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();
    }

    @Override
    public String getDescription() {
        return super.getDescription() + getFastFood().getDescription();
    }

}
java 复制代码
/**
 * @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();
    }

    @Override
    public String getDescription() {
        return super.getDescription() + getFastFood().getDescription();
    }
}

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

java 复制代码
/**
 * @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.具体实现化角色:给出实现化 角色接口的具体实现。

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

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

实现细节对客户透明

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

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

java 复制代码
/**
 * @author nuist__NJUPT
 * @InterfaceName VideoFile
 * @description: 视频文件-实现化角色
 * @date 2024年01月21日
 */
public interface VideoFile {
    // 解码功能
    void decode(String fileName) ;
}
java 复制代码
/**
 * @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);
    }
}
java 复制代码
/**
 * @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.定义抽象化角色与扩展抽象化角色。

java 复制代码
/**
 * @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) ;
}
java 复制代码
/**
 * @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);
    }
}
java 复制代码
/**
 * @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.定义客户端测试类进行测试。

java 复制代码
/**
 * @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.首先定义三个类,开灯、开空调、开电视类。

java 复制代码
/**
 * @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("关闭电灯...");
    }

}
java 复制代码
/**
 * @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("关闭电视...");
    }
}
java 复制代码
/**
 * @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.定义一个外观类,用户通过外观类实现控制家电得开启与关闭。

java 复制代码
/**
 * @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.定义测试类,测试使用外观类开启与关闭家电。

java 复制代码
/**
 * @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.首先定义菜单组件,表示抽象根节点,定义系统各层次共用属性与方法。

java 复制代码
/**
 * @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.然后定义菜单类,表示树枝节点,如下:

java 复制代码
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 ;
    }


    @Override
    public void add(MenuComponent menuComponent) throws Exception {
        menuComponents.add(menuComponent);
    }

    @Override
    public void remove(MenuComponent menuComponent) throws Exception {
        menuComponents.remove(menuComponent);
    }

    @Override
    public 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.然后定义菜单项类表示叶子节点,如下:

java 复制代码
/**
 * @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.最后,在客户端进行测试。

java 复制代码
/**
 * @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.定义一个抽象享元角色,其中定义公用方法由具体享元重写。

java 复制代码
/**
 * @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.定义三个具体享元角色继承抽象享元角色并重写公用方法。

java 复制代码
/**
 * @author nuist__NJUPT
 * @ClassName IBox
 * @description: 具体享元角色-I图形类
 * @date 2024年01月22日
 */
public class IBox extends AbstractBox{

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

    public String getShape() {
        return "O";
    }
}
java 复制代码
/**
 * @author nuist__NJUPT
 * @ClassName LBox
 * @description: TODO
 * @date 2024年01月22日
 */
public class LBox extends AbstractBox{

    public String getShape() {
        return "L";
    }
}

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

java 复制代码
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.最后定义测试类测试享元模式。

java 复制代码
/**
 * @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("绿色");

    }
}
相关推荐
电子科技圈27 分钟前
IAR开发平台升级Arm和RISC-V开发工具链,加速现代嵌入式系统开发
arm开发·嵌入式硬件·设计模式·性能优化·软件工程·代码规范·risc-v
啾啾Fun42 分钟前
Java反射操作百倍性能优化
java·性能优化·反射·缓存思想
20岁30年经验的码农1 小时前
若依微服务Openfeign接口调用超时问题
java·微服务·架构
昕冉1 小时前
利用 Axrue9 中继器实现表格数据的查询
设计模式·设计
曲莫终1 小时前
SpEl表达式之强大的集合选择(Collection Selection)和集合投影(Collection Projection)
java·spring boot·spring
ajassi20001 小时前
开源 java android app 开发(十二)封库.aar
android·java·linux·开源
q567315231 小时前
Java使用Selenium反爬虫优化方案
java·开发语言·分布式·爬虫·selenium
kaikaile19951 小时前
解密Spring Boot:深入理解条件装配与条件注解
java·spring boot·spring
守护者1702 小时前
JAVA学习-练习试用Java实现“一个词频统计工具 :读取文本文件,统计并输出每个单词的频率”
java·学习
bing_1582 小时前
Spring Boot 中ConditionalOnClass、ConditionalOnMissingBean 注解详解
java·spring boot·后端