设计模式(1)创建型模式和结构型模式

news2024/11/14 13:59:31

1、目标

本文的主要目标是学习创建型模式和结构型模式,并分别代码实现每种设计模式

2、创建型模式

2.1 单例模式(singleton)

单例模式是创建一个对象保证只有这个类的唯一实例,单例模式分为饿汉式和懒汉式,饿汉式是类加载的时候就创建这个类的唯一对象,懒汉式是获取这个类的对象时才会创建对象

程序:

public class SingletonFactory {

    // 饿汉式的单例模式
    public static class Singleton01 {
        private static final Singleton01 INSTANCE = new Singleton01();
        // 构造器私有化
        private Singleton01() {}
        public static Singleton01 getInstance() {
            return INSTANCE;
        }
    }

    // 懒汉式的单例模式(不加锁,推荐)
    public static class Singleton02 {
        // 使用静态内部类封装单例对象,实现懒加载,也可以保证线程安全
        private static class Singleton02Holder {
            private static final Singleton02 INSTANCE = new Singleton02();
        }
        // 构造器私有化
        private Singleton02() {}
        public static Singleton02 getInstance() {
            return Singleton02Holder.INSTANCE;
        }
    }

    // 懒汉式的单例模式(加锁,双端检查)
    public static class Singleton03 {
        // 不会初始化INSTANCE,必须加上volatile保证可见性和禁止指令重排序
        // volatile的作用:
        // 1、保证可见性:修改INSTANCE变量会立即更新到主内存中,其他线程读取INSTANCE变量会从主内存中读取数据,保证实例的唯一性
        // 2、禁止指令重排序:必须先初始化INSTANCE对象然后对象空间赋值给INSTANCE变量,保证实例的完整性
        private static volatile Singleton03 INSTANCE;
        // 构造器私有化
        private Singleton03() {}
        public static Singleton03 getInstance() {
            if(INSTANCE != null) {
                return INSTANCE;
            }
            synchronized (Singleton03.class) {
                if(INSTANCE == null) {
                    INSTANCE = new Singleton03();
                }
            }
            return INSTANCE;
        }
    }

}

volatile的作用:
1、保证可见性:修改INSTANCE变量会立即更新到主内存中,其他线程读取INSTANCE变量会从主内存中读取数据,保证实例的唯一性
2、禁止指令重排序:必须先初始化INSTANCE对象然后对象空间赋值给INSTANCE变量,保证实例的完整性
volatile是禁止指令重排序,因为对象创建的顺序是 ① 在堆中创建对象空间 ② 属性默认初始化、构造器初始化 ③ 将堆中的对象空间指向栈中的对象引用,如果没有volatile可能会重排序,先将堆中的对象空间指向栈中的对象引用,此时另一个线程发现对象创建了就直接返回,但此时这个对象还没有属性默认初始化、构造器初始化,是一个不完整的对象
构造器私有化
双端检测double check lock,第一个if判断是否是单例对象是否为空是因为已经创建单例对象了就不会获取锁,第二个if判断是否是单例对象是因为多个线程并发获取锁,获取锁失败的要进来再次初始化,所以用if判断就不用再次初始化了

public class TaskController {
    @Test
    public void f5() {
        SingletonFactory.Singleton01 singleton01 = SingletonFactory.Singleton01.getInstance();
        System.out.println("singleton01 = " + singleton01);
        singleton01 = SingletonFactory.Singleton01.getInstance();
        System.out.println("singleton01 = " + singleton01);
        System.out.println("============================================");
        SingletonFactory.Singleton02 singleton02 = SingletonFactory.Singleton02.getInstance();
        System.out.println("singleton02 = " + singleton02);
        singleton02 = SingletonFactory.Singleton02.getInstance();
        System.out.println("singleton02 = " + singleton02);
        System.out.println("============================================");
        SingletonFactory.Singleton03 singleton03 = SingletonFactory.Singleton03.getInstance();
        System.out.println("singleton03 = " + singleton03);
        singleton03 = SingletonFactory.Singleton03.getInstance();
        System.out.println("singleton03 = " + singleton03);
    }
}

多次获取同一个类的单例对象

测试结果:
在这里插入图片描述

获取的是同一个对象

2.2 原型模式(prototype)

原型模式是通过拷贝对象来创建新的对象,定义一个抽象类可以定义clone抽象方法,子类重写这个clone方法

程序:

public class JobFactory {

    abstract public class Job {
        private String name;
        public Job(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
        abstract public Job clone();
    }

    public class Stu extends Job {
        public Stu() {
            super("学生");
        }
        @Override
        public Job clone() {
            return new Stu();
        }
    }

    public class Teacher extends Job {
        public Teacher() {
            super("老师");
        }
        @Override
        public Job clone() {
            return new Teacher();
        }
    }

}

定义一个抽象类,定义clone抽象方法,子类重写clone抽象方法

public class TaskController {
    @Test
    public void f6() {
        JobFactory jobFactory = new JobFactory();
        JobFactory.Job job = jobFactory.new Stu();
        System.out.println("job = " + job);
        job = job.clone();
        System.out.println("job = " + job);
        System.out.println("===========================================");
        job = jobFactory.new Teacher();
        System.out.println("job = " + job);
        job = job.clone();
        System.out.println("job = " + job);
    }
}

通过clone方法可以实现原型模式,通过拷贝对象来创建一个新的对象

测试结果:

在这里插入图片描述

原型模式可以创建一个新的对象

2.3 建造者模式(Builder)

建造者模式是将复杂对象的构建过程和表示分离开来,因此同样的构建过程可以创建不同的表示,即不同的属性值

程序:

public class SkillBuilder {

    private Skill skill = new Skill();

    public SkillBuilder buildFront(String name) {
        skill.setFront(name);
        return this;
    }

    public SkillBuilder buildBack(String name) {
        skill.setBack(name);
        return this;
    }

    public SkillBuilder buildTest(String name) {
        skill.setTest(name);
        return this;
    }

    public SkillBuilder buildUe(String name) {
        skill.setUe(name);
        return this;
    }

    public Skill getSkill() {
        return skill;
    }

    public class Skill {
        private String front;
        private String back;
        private String test;
        private String ue;
        public void setFront(String front) {
            this.front = front;
        }
        public void setBack(String back) {
            this.back = back;
        }
        public void setTest(String test) {
            this.test = test;
        }
        public void setUe(String ue) {
            this.ue = ue;
        }
        @Override
        public String toString() {
            return "Skill{" +
            "front='" + front + '\'' +
            ", back='" + back + '\'' +
            ", test='" + test + '\'' +
            ", ue='" + ue + '\'' +
            '}';
        }
    }

}

创建一个Builder类封装Skill对象属性的构建过程,并可以实现链式调用

public class TaskController {
    @Test
    public void f7() {
        SkillBuilder skillBuilder = new SkillBuilder();
        SkillBuilder.Skill skill = skillBuilder.buildFront("前端")
                .buildBack("后端")
                .buildTest("测试")
                .buildUe("UE")
                .getSkill();
        System.out.println("skill = " + skill);
    }
}

通过SkillBuilder可以创建Skill对象,并且是链式调用

测试结果:

在这里插入图片描述

通过建造者模式可以创建一个对象

2.4 工厂方法模式(Factory Method)

工厂方法模式是根据不同的条件创建不同类型的对象,优点是可以通过工厂类创建不同类型的对象,耦合性小

需求:根据不同类型创建不同的形状
分析:不同的类型用if else判断,硬编码

优化思路:设计一个工厂类,根据不同类型创建不同的对象,也可以将对象放到map中然后map.get获取不同类型的对象

程序:

public class ShapeFactory {

    private Map<String, Shape> map = new HashMap<>();

    public ShapeFactory() {
        map.put("rectangle", new Rectangle());
        map.put("circle", new Circle());
    }

    public Shape addShapeByType(String type) {
        // 根据map查询返回的类型,也可以直接用if else判断类型然后new一个新的对象并返回
        return map.get(type);
    }

    public abstract class Shape {
        abstract public void draw();
    }

    public class Rectangle extends Shape {
        @Override
        public void draw() {
            System.out.println("长方形");
        }
    }

    public class Circle extends Shape {
        @Override
        public void draw() {
            System.out.println("圆形");
        }
    }

}

工厂类设计一个map,将所有的形状都放到map中,然后map.get就可以获取不同类型的形状

public class TaskController {
    @Test
    public void f4() {
        ShapeFactory shapeFactory = new ShapeFactory();
        ShapeFactory.Shape shape = shapeFactory.addShapeByType("rectangle");
        System.out.println("shape = " + shape);
        shape.draw();
        System.out.println("==========================================");
        shape = shapeFactory.addShapeByType("circle");
        System.out.println("shape = " + shape);
        shape.draw();
    }
}

入参是形状类型,会返回一个指定类型的形状

测试结果:

在这里插入图片描述

测试结果是可以根据不同的类型创建不同类型的对象

2.5 抽象工厂模式(Abstract Factory)

抽象工厂模式可以创建多组相关的对象,一组中包含多个对象,定义一个抽象类指定创建一组对象,多个实现类创建多组对象,抽象工厂模式可以将使用哪些对象和如何使用这些对象的操作分离开来,优点:用来返回一组对象,耦合性小,增加一组对象会很清晰

需求:根据计算机分辨率大小创建(返回)显示和打印形状的驱动程序
分析:
① 显示形状的驱动程序有LRDD、MRDD、HRDD,打印形状的驱动程序有LRPD、MRPD、HRPD,其中L开头的是低分辨率的,M开头的是中分辨率的,H开头的是高分辨率的,因此将驱动程序分成两类:DisplayDriver、PrintDriver
② 对于不同类型的分辨率可以用if条件判断,但是如果增加一个类型的分辨率会耦合性高

优化思路:设计抽象类是分辨率Resolution,这个抽象类中指定创建DisplayDriver对象和PrintDriver对象等这一组对象,为抽象类的一组对象可以创建多个实现类即计算机分辨率有低、中、高分辨率分别是LowResolution、MiddleResolution、HighResolution,TestController类调用这些类得到一组对象进行操作,因此将使用哪些对象和如何使用这些对象的操作分离开来

程序:

public class ResolutionFactory {

    // 抽象工厂类Resolution定义一组对象,多个实现类创建多组对象
    public abstract class Resolution {
        abstract public DisplayDriver getDisplayDriver();
        abstract public PrintDriver getPrintDriver();
    }

    public class LowResolution extends Resolution {
        @Override
        public DisplayDriver getDisplayDriver() {
            return new DisplayDriver("LRDD");
        }
        @Override
        public PrintDriver getPrintDriver() {
            return new PrintDriver("LRPD");
        }
    }

    public class MiddleResolution extends Resolution {
        @Override
        public DisplayDriver getDisplayDriver() {
            return new DisplayDriver("MRDD");
        }
        @Override
        public PrintDriver getPrintDriver() {
            return new PrintDriver("MRPD");
        }
    }

    public class HighResolution extends Resolution {
        @Override
        public DisplayDriver getDisplayDriver() {
            return new DisplayDriver("HRDD");
        }
        @Override
        public PrintDriver getPrintDriver() {
            return new PrintDriver("HRPD");
        }
    }

    public class DisplayDriver {
        private String name;
        public DisplayDriver(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "DisplayDriver{" +
            "name='" + name + '\'' +
            '}';
        }
    }

    public class PrintDriver {
        private String name;
        public PrintDriver(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "PrintDriver{" +
            "name='" + name + '\'' +
            '}';
        }
    }

}

抽象工厂类Resolution定义一组对象,多个实现类创建多组对象,一组对象中包含DisplayDriver、PrintDriver

import org.junit.jupiter.api.Test;

public class TaskController {
    @Test
    public void f3() {
        ResolutionFactory resolutionFactory = new ResolutionFactory();
        ResolutionFactory.LowResolution lowResolution = resolutionFactory.new LowResolution();
        ResolutionFactory.DisplayDriver displayDriver = lowResolution.getDisplayDriver();
        ResolutionFactory.PrintDriver printDriver = lowResolution.getPrintDriver();
        System.out.println("LowResolution: displayDriver = " + displayDriver + ", printDriver = " + printDriver);
        ResolutionFactory.MiddleResolution middleResolution = resolutionFactory.new MiddleResolution();
        displayDriver = middleResolution.getDisplayDriver();
        printDriver = middleResolution.getPrintDriver();
        System.out.println("MiddleResolution: displayDriver = " + displayDriver + ", printDriver = " + printDriver);
        ResolutionFactory.HighResolution highResolution = resolutionFactory.new HighResolution();
        displayDriver = highResolution.getDisplayDriver();
        printDriver = highResolution.getPrintDriver();
        System.out.println("HighResolution: displayDriver = " + displayDriver + ", printDriver = " + printDriver);
    }
}

TaskController通过getDisplayDriver方法和getPrintDriver方法得到显示驱动程序和打印驱动程序

测试结果是:

在这里插入图片描述

为了得到显示驱动程序和打印驱动程序这一组对象,设置一个抽象类分辨率和多个分辨率实现类,可以创建多组相关的对象

3、结构型模式

3.1 外观模式(Facade)

外观模式是为复杂系统提高一个新的接口,作用是可以简化复杂系统的使用,或者只使用复杂系统的一部分功能,可以封装系统功能,因此用外观模式

程序:

public class ComputerFactory {

    public class Computer {
        private Cpu cpu;
        private Memory memory;
        private Disk disk;
        public Computer(Cpu cpu, Memory memory, Disk disk) {
            this.cpu = cpu;
            this.memory = memory;
            this.disk = disk;
        }
        public void start() {
            cpu.start();
            memory.start();
            disk.start();
            System.out.println("Computer started ok");
        }
        public void end() {
            cpu.end();
            memory.end();
            disk.end();
            System.out.println("Computer ended ok");
        }
    }

    public class Cpu {
        public void start() {
            System.out.println("Cpu started");
        }
        public void end() {
            System.out.println("Cpu ended");
        }
    }

    public class Memory {
        public void start() {
            System.out.println("Memory started");
        }
        public void end() {
            System.out.println("Memory ended");
        }
    }

    public class Disk {
        public void start() {
            System.out.println("Disk started");
        }
        public void end() {
            System.out.println("Disk ended");
        }
    }

}

开启电脑会启动Cpu、Memory、Disk等组件,因此外观模式相当于封装了电脑的各个组件的启动过程

public class TaskController {
    @Test
    public void f9() {
        ComputerFactory computerFactory = new ComputerFactory();
        ComputerFactory.Cpu cpu = computerFactory.new Cpu();
        ComputerFactory.Memory memory = computerFactory.new Memory();
        ComputerFactory.Disk disk = computerFactory.new Disk();
        ComputerFactory.Computer computer = computerFactory.new Computer(cpu, memory, disk);
        computer.start();
        System.out.println("================================================");
        computer.end();
    }
}

测试结果:

在这里插入图片描述

外观模式可以封装一个复杂对象的启动过程

3.2 适配器模式(Adapter)

适配器模式是将一个接口转换成另一个接口,作用是保证接口是兼容的,保证多态

程序:

public class Shape02Factory {

    abstract public class Shape {
        private String name;
        public Shape(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        abstract public void show();
    }

    public class Circle extends Shape {
        // 适配器模式是组合另一个类
        private final XXCircle xxCircle;

        public Circle(String name, XXCircle xxCircle) {
            super(name);
            this.xxCircle = xxCircle;
        }
        @Override
        public void show() {
            xxCircle.display();
        }
    }

    public class XXCircle {
        public void display() {
            System.out.println("XXCircle display圆形");
        }
    }
}

Circle继承了Shape抽象类,重写了display方法,Circle类组合了XXCircle对象用来调用它的displayIt方法,可以保证多态

public class TaskController {
    @Test
    public void f8() {
        Shape02Factory shape02Factory = new Shape02Factory();
        Shape02Factory.XXCircle xxCircle = shape02Factory.new XXCircle();
        Shape02Factory.Circle circle = shape02Factory.new Circle("圆形", xxCircle);
        circle.show();
    }
}

测试结果:

在这里插入图片描述

适配器模式会调用另一个类的方法

3.3 桥接模式(Bridge)

桥接模式是将抽象和实现解耦,使它们都能独立的变化,寻找可变的参数并封装到一个类中,拆分出抽象和实现,实现作为抽象的一个属性,即用组合代替继承,如果每个抽象都调用具体的实现会出现抽象和实现耦合并且类很多,因此实现也需要抽象出一个抽象类或者接口,每个抽象只需要调用实现的抽象类即可,比如这里的抽象指的是形状,实现指的是画出这个形状需要的画线或者画圆方法,优点:抽象和实现解耦,耦合性小,增加一个抽象或者实现会很清晰

需求:画某个形状的图,比如长方形或者圆形,用多个画图程序(包括画线、画圆)实现画某个形状的图的功能

分析:

在这里插入图片描述

假设现在有多个形状,有多个程序都可以实现每个形状的绘制功能,如果每个形状都创建多个类用来调用多个程序的绘制功能或者每个形状都组合多个程序类来绘制同一个形状,这样会造成类或者组合数量多,并且耦合性高

优化思路:

在这里插入图片描述

寻找可变的参数并封装到一个类中,拆分出抽象和实现,将多个实现封装在一个抽象类中,然后用组合替代继承,将实现的抽象类组合在抽象中
这里抽象出Shape抽象类和Drawing抽象类,Shape抽象类作为抽象,Drawing抽象类作为实现

程序:

public class ShapeFactory {

    public void process(Shape shape) {
        shape.draw();
    }

    // 形状抽象类是抽象
    public abstract class Shape {
        abstract public void draw();
    }

    public class Rectangle extends Shape {
        private int x1, y1, x2, y2;
        private Drawing drawing;

        public Rectangle(int x1, int y1, int x2, int y2, Drawing drawing) {
            this.x1 = x1;
            this.y1 = y1;
            this.x2 = x2;
            this.y2 = y2;
            this.drawing = drawing;
        }

        @Override
        public void draw() {
            // Rectangle组合了Drawing实现,避免分别调用V1Drawing和V2Drawing的drawLine方法
            drawing.drawLine(x1, y1, x1, y2);
            drawing.drawLine(x1, y2, x2, y2);
            drawing.drawLine(x2, y2, x2, y1);
            drawing.drawLine(x2, y1, x1, y1);
        }
    }

    public class Circle extends Shape {
        private int x, y, r;
        private Drawing drawing;

        public Circle(int x, int y, int r, Drawing drawing) {
            this.x = x;
            this.y = y;
            this.r = r;
            this.drawing = drawing;
        }

        @Override
        public void draw() {
            // Circle组合了Drawing实现,避免分别调用V1Drawing和V2Drawing的drawCircle方法
            drawing.drawCircle(x, y, r);
        }
    }

    // 多个绘图程序抽象出绘图抽象类,绘图抽象类就是形状抽象的实现
    public abstract class Drawing {
        public abstract void drawLine(int x1, int y1, int x2, int y2);
        public abstract void drawCircle(int x, int y, int r);
    }

    public class V1Drawing extends Drawing {
        @Override
        public void drawLine(int x1, int y1, int x2, int y2) {
            System.out.println("使用绘图程序 1 绘制线条line: x1 " + x1 + " y1 " + y1 + " x2 " + x2 + " y2 " + y2);
        }
        @Override
        public void drawCircle(int x, int y, int r) {
            System.out.println("使用绘图程序 1 绘制圆circle: x " + x + " y " + y + " r " + r);
        }
    }

    public class V2Drawing extends Drawing {
        @Override
        public void drawLine(int x1, int y1, int x2, int y2) {
            System.out.println("使用绘图程序 2 绘制线条line: x1 " + x1 + " y1 " + y1 + " x2 " + x2 + " y2 " + y2);
        }
        @Override
        public void drawCircle(int x, int y, int r) {
            System.out.println("使用绘图程序 2 绘制圆circle: x " + x + " y " + y + " r " + r);
        }
    }

}

ShapeFactory类封装了process方法可以调用形状Shape抽象类的draw方法,实现用Drawing抽象类来表示

其中,画长方形这个形状的draw方法调用了4个drawLine方法

在这里插入图片描述

长方形画图程序有4个点,可以只记录x1,y1,x2,y2这4个参数即可画4条线,4条线的起始点和终止点分别是(x1,y1)和(x1,y2)、(x1,y2)和(x2,y2)、(x2,y2)和(x2,y1)、(x2,y1)和(x1,y1)

优点:增加一个画图方法或者一个形状修改的话会很清晰

import org.junit.jupiter.api.Test;

public class TaskController {
    @Test
    public void f2() {
        ShapeFactory shapeFactory = new ShapeFactory();
        ShapeFactory.Drawing drawing = shapeFactory.new V1Drawing();
        ShapeFactory.Shape shape = shapeFactory.new Rectangle(1,2, 4,6, drawing);
        shapeFactory.process(shape);
        System.out.println("=============================================");
        drawing = shapeFactory.new V2Drawing();
        shape = shapeFactory.new Rectangle(1, 2, 4, 6, drawing);
        shapeFactory.process(shape);
        System.out.println("=============================================");
        drawing = shapeFactory.new V1Drawing();
        shape = shapeFactory.new Circle(1,3, 2, drawing);
        shapeFactory.process(shape);
        System.out.println("=============================================");
        drawing = shapeFactory.new V2Drawing();
        shape = shapeFactory.new Circle(1, 3, 2, drawing);
        shapeFactory.process(shape);
    }
}

测试方法会先创建ShapeFactory对象,然后创建Drawing实现,接着创建Shape抽象,因为已经将Drawing实现组合到Shape抽象,因此Shape抽象调用Drawing实现即可,最后调用ShapeFactory对象的process方法执行画图功能

测试结果:

在这里插入图片描述

分别用绘图程序1和2绘制线条line和圆circle

3.4 装饰器模式(Decorator)

装饰器模式可以在对象的之前或者之后动态添加功能,动态调整顺序,不用创建子类就可以动态扩展功能,创建一个对象链实现动态添加功能的效果,如何实现这种链式调用呢?创建一个装饰器抽象类,它包含Component对象,包含的show方法会调用Component对象的show方法,由于java的多态是动态绑定机制,因此会调用运行类型对象的show方法,从而实现Component的多个实现类对象的链式调用,优点:装饰器模式可以在对象的之前或者之后动态添加功能,不用创建子类就可以动态扩展功能,扩展性强,Java的IO流广泛使用了装饰器模式

需求:为销售票据添加表头、页脚等信息
分析:

创建票据对象,创建子类直接在方法开头加上表头,在方法结尾加上页脚,这是硬编码,如果有多个表头和页脚那怎么办,只能创建多个子类即硬编码

优化思路:
采用装饰器模式,创建一个对象链,这个对象链起始于装饰器Decorator对象,终止于原始对象,对象链可以在对象之前或者之后动态添加功能

程序:

public class ComponentFactory {

    public void show(Component component) {
        component.show();
    }

    abstract public class Component {
        abstract public void show();
    }

    // 原始对象:票据对象SaleTicket
    public class SaleTicket extends Component {
        @Override
        public void show() {
            System.out.println("销售票据SaleTicket");
        }
    }

    // 装饰器抽象类:组合了Component对象,可以调用component的show方法实现链式调用,不用创建子类就可以动态扩展功能
    abstract public class TicketDecorator extends Component {
        private Component component;
        public TicketDecorator(Component component) {
            this.component = component;
        }
        @Override
        public void show() {
            // 运行类型(子类)调用show方法
            component.show();
        }
    }

    public class Header extends TicketDecorator {
        private String msg;
        public Header(Component component, String msg) {
            super(component);
            this.msg = msg;
        }
        @Override
        public void show() {
            System.out.println("Header: " + msg);
            // 调用父类的show方法
            super.show();
        }
    }

    public class Footer extends TicketDecorator {
        private String msg;
        public Footer(Component component, String msg) {
            super(component);
            this.msg = msg;
        }
        @Override
        public void show() {
            // 调用父类的show方法
            super.show();
            System.out.println("Footer: " + msg);
        }
    }

}

创建Component抽象类,创建票据对象SaleTicket,要在这个对象的之前或者之后添加功能,因此SaleTicket对象是原始对象,用装饰器模式,创建装饰器抽象类TicketDecorator,它组合了Component对象,它包含的show方法可以调用Component对象的show方法实现链式调用,因为java的多态是动态绑定机制因此会调用运行类型对象的show方法,装饰器模式的好处是不用创建子类就可以动态扩展功能

public class TaskController {
    @Test
    public void f1() {
        ComponentFactory componentFactory = new ComponentFactory();
        ComponentFactory.SaleTicket saleTicket = componentFactory.new SaleTicket();
        ComponentFactory.Footer footer = componentFactory.new Footer(saleTicket, "部门yyy");
        footer = componentFactory.new Footer(footer, "页号1");
        ComponentFactory.Header header = componentFactory.new Header(footer, "申请人zzz");
        header = componentFactory.new Header(header, "公司xxx");
        componentFactory.show(header);
    }
}

先创建ComponentFactory工厂类对象,然后创建原始对象SaleTicket对象,接着创建Footer对象并将SaleTicket对象组合到其中,然后创建Header对象并将Footer对象组合到其中,最后调用ComponentFactory工厂类对象的show方法会链式调用Component对象的show方法

测试结果:

在这里插入图片描述

测试结果是原始对象销售票据之前添加了两个头部,之后添加了两个页脚,可以动态扩展功能

3.5 享元模式(Flyweight)

享元模式是共享的对象只会创建1次,避免创建大量相似的对象,思路是创建一个享元工厂就是一个map,获取对象的时候会判断如果对象不存在就创建对象并添加到map中,如果对象存在就不用创建对象了直接从map获取

程序:

public class CheseFactory {

    private Map<String, Chese> map = new HashMap<>();

    public Chese getChese(String color) {
        if(map.containsKey(color)) {
            return map.get(color);
        }
        Chese chese = new Chese(color);
        map.put(color, chese);
        return chese;
    }

    public class Chese {
        private String color;
        public Chese(String color) {
            this.color = color;
        }
        public String getColor() {
            return color;
        }
        public void setColor(String color) {
            this.color = color;
        }
    }

}

有多个棋子,如果颜色相同不用重复创建,用享元模式,创建一个对象会放到map中

public class TaskController {
    @Test
    public void f10() {
        CheseFactory cheseFactory = new CheseFactory();
        CheseFactory.Chese chese = cheseFactory.getChese("red");
        System.out.println("chese = " + chese);
        chese = cheseFactory.getChese("blue");
        System.out.println("chese = " + chese);
        chese = cheseFactory.getChese("red");
        System.out.println("chese = " + chese);
        chese = cheseFactory.getChese("blue");
        System.out.println("chese = " + chese);
    }
}

通过getChese方法传入参数是颜色

测试结果:

在这里插入图片描述

通过getChese方法传入参数如果map中已经存在这个颜色的棋子就会直接返回,不会重复创建,避免创建大量重复对象

3.6 代理模式(Proxy)

代理模式是不改变原始对象的情况下对功能的增强,这样可以保证原始对象的单一职责原则,思路是创建一个代理对象,代理对象中持有了原始对象的引用

程序:

public class SportsFactory {

    public interface Sports {
        public void doSports();
    }

    public class SportsImpl implements Sports {
        @Override
        public void doSports() {
            System.out.println("doSports ing~");
        }
    }

    public class JDKInvocationHandler implements InvocationHandler {
        private Object target;
        public JDKInvocationHandler(Object target) {
            this.target = target;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before doSports: buy water");
            Object res = method.invoke(target, args);
            System.out.println("after doSports: take a shower");
            return res;
        }
        public Object getProxy() {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
    }

}

创建一个接口,创建一个实现类,创建一个JDK动态代理对象,实现InvocationHandler接口,重写invoke方法,用Proxy.newProxyInstance方法创建基于接口的JDK动态代理对象

@Test
public void f12() {
    SportsFactory sportsFactory = new SportsFactory();
    SportsFactory.Sports sports = sportsFactory.new SportsImpl();
    SportsFactory.JDKInvocationHandler jdkInvocationHandler = sportsFactory.new JDKInvocationHandler(sports);
    // 创建JDK动态代理对象
    sports = (SportsFactory.Sports) jdkInvocationHandler.getProxy();
    sports.doSports();
}

创建基于接口的JDK动态代理对象,JDK代理对象和实现类对象是兄弟的关系,因为它们都实现了同一个接口

测试结果:

在这里插入图片描述

JDK动态代理可以在实现类的方法之前和之后分别输出,这是为了保证设计模式中一个类的单一职责原则

3.7 组合模式(Composite)

组合模式是将对象组合成树状结构表示树状的层次关系,思路是创建一个抽象类,创建目录和文件,目录中可以包含文件或者子目录,因此目录这个类中包含一个List集合

程序:

public class CompositeFactory {

    abstract public class Component {
        private String name;
        public Component(String name) {
            this.name = name;
        }
        abstract public void addComponent(Component component);
        abstract public void show();
    }

    public class File extends Component {
        public File(String name) {
            super(name);
        }
        @Override
        public void addComponent(Component component) {
            throw new RuntimeException("文件不能添加子元素");
        }
        @Override
        public void show() {
            System.out.println("File name = " + super.name);
        }
        @Override
        public String toString() {
            return " { File name = " + super.name + " } ";
        }
    }

    public class Dict extends Component {
        private List<Component> list = new ArrayList<>();
        public Dict(String name) {
            super(name);
        }
        @Override
        public void addComponent(Component component) {
            list.add(component);
        }
        @Override
        public void show() {
            // 将list集合中的Component对象转成String字符串并按照逗号分隔
            String s = list.stream().map(component -> component.toString()).collect(Collectors.joining(","));
            System.out.println("Dict name = " + super.name + ",它包含了list = " + s);
        }
        @Override
        public String toString() {
            return " { Dict name = " + super.name + ", list = " + list.toString() + " } ";
        }
    }

}

创建一个抽象类,创建文件和目录,目录中可以存放子目录和文件,因此用一个List集合存放它们

@Test
public void f11() {
    CompositeFactory compositeFactory = new CompositeFactory();
    CompositeFactory.File file01 = compositeFactory.new File("file01");
    CompositeFactory.File file02 = compositeFactory.new File("file02");
    CompositeFactory.File file03 = compositeFactory.new File("file03");
    CompositeFactory.File file04 = compositeFactory.new File("file04");
    CompositeFactory.Dict dict01 = compositeFactory.new Dict("dict01");
    CompositeFactory.Dict dict02 = compositeFactory.new Dict("dict02");
    CompositeFactory.Dict dict03 = compositeFactory.new Dict("dict03");
    dict01.addComponent(dict02);
    dict01.addComponent(dict03);
    dict02.addComponent(file01);
    dict02.addComponent(file02);
    dict03.addComponent(file03);
    dict03.addComponent(file04);
    dict01.show();
}

创建树状结构

测试结果:

在这里插入图片描述

组合模式可以展示树状结构

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

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

相关文章

IP问题总结

IP基础知识 IP 在 TCP/IP 参考模型中处于第三层&#xff0c;也就是⽹络层。 ⽹络层的主要作⽤是&#xff1a;实现主机与主机之间的通信&#xff0c;也叫点对点&#xff08;end to end&#xff09;通信。 1.⽹络层与数据链路层有什么关系呢&#xff1f; 其实很容易区分&#…

eNSP 华为浮动路由

R1&#xff1a; <Huawei>system-view [Huawei]sysname R1 [R1]int g0/0/0 [R1-GigabitEthernet0/0/0]ip add 172.16.1.1 24 [R1-GigabitEthernet0/0/0]int g0/0/1 [R1-GigabitEthernet0/0/1]ip add 10.10.1.1 24 [R1-GigabitEthernet0/0/1]quit [R1]vlan 10 //e口是…

使用docker搭建aria2-pro+ariang并在alist中配置

一、安装aria2-pro 1.创建映射目录 # 配置目录 mkdir -p /usr/local/docker/aria2/config # 下载目录 mkdir -p /share_root/download-aria22.创建容器 docker run -d \--name aria2-pro \--restart unless-stopped \--log-opt max-size1m \--network host \-e PUID$UID \-e …

【秋招笔试】8.12-4399秋招(第一套)-三语言题解

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收…

全网最详细HAProxy入门小知识

目录 一. 负载均衡 负载均衡的意义&#xff1a; 负载均衡的类型&#xff1a; 二. HAProxy 简介 HAProxy 的特点&#xff1a; 社区版和企业版&#xff1a; 三. HAProxy 的安装和服务信息 1、实验环境 1&#xff09;安装并配置 Nginx 2&#xff09;在客户端测试 2、安装…

【项目实战】C++视频共享点播系统

目录 一、项目介绍 1.1 对视频共享点播系统的认识 1.2服务端程序负责功能 1.3 服务端功能模块划分 1.4 项目界面演示 1.5预备知识 二.环境搭建 2.1 安装 Jsoncpp 库 2.1.1 使用jsoncpp 2.2 引入httplib库 2.2.1 安装Git&#xff08;如果你的系统尚未安装Git&#xf…

基于Hadoop的共享单车分布式存储与计算

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍研究背景研究目的和意义国内外研究现状总体研究思路数据可视化每文一语 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 项目介绍 共享单车的普及带…

Elasticsearch:使用 ES|QL 进行地理空间搜索

作者&#xff1a;来自 Elastic Craig Taverner 多年来&#xff0c;Elasticsearch 一直具有强大的地理空间搜索和分析功能&#xff0c;但其 API 与典型的 GIS 用户习惯的 API 截然不同。在过去的一年中&#xff0c;我们添加了 ES|QL 查询语言&#xff0c;这是一种管道查询语言&a…

React原理之React整体渲染流程

前置知识&#xff1a;深度优先搜索(DFS)、Fiber 节点 在上一篇 React原理篇之 React 整体架构解读中&#xff0c;提到了 Fiber 架构中的几个核心概念&#xff1a; Scheduler&#xff08;调度器&#xff09;&#xff1a;根据任务的优先级安排任务执行顺序。Reconciler&#xff…

CUDA-MODE 第一课课后实战(下)

我的课程笔记&#xff0c;欢迎关注&#xff1a;https://github.com/BBuf/how-to-optim-algorithm-in-cuda/tree/master/cuda-mode CUDA-MODE 第一课课后实战&#xff08;下&#xff09; Nsight Compute Profile结果分析 继续对Nsight Compute的Profile结果进行分析&#xff0…

PyQT 串口改动每次点开时更新串口信息

class MainWindow(QWidget, Ui_Form):def __init__(self):super().__init__(parentNone)self.setupUi(self)self.comboBox.installEventFilter(self) # 加载事件过滤器self.comboBox.addItems(get_ports())def eventFilter(self, obj, event): # 定义事件过滤器if isinstance(o…

前端容器化部署:解决重启容器时的静态资源丢失问题

文章目录 什么是前端容器化&#xff1f;重启容器时静态资源丢失的问题解决静态资源丢失的方案1. 使用持久化卷创建和挂载卷使用Docker Compose定义卷 2. 使用对象存储将静态资源上传到对象存储 3. 使用构建时持久化使用CI/CD管道 4. 使用动态加载和缓存使用浏览器缓存使用服务端…

Java 8日期时间API革新:从Date到LocalDate、LocalTime与LocalDateTime的转型与优势解析

文章目录 前言一、基础介绍1.Date2.LocalDate3.LocalTime4.LocalDateTime 二、区别三、推荐场景四、推荐原因总结 前言 在Java的发展历程中&#xff0c;日期和时间的处理一直是开发者们关注的焦点。从早期的java.util.Date类到java.util.Calendar接口&#xff0c;虽然为日期时间…

Linux从0到1——进程池

Linux从0到1——进程池 1. 进程池的概念2. 进程池实现思路3. 进程池的代码实现3.1 创建管道&#xff0c;创建子进程3.2 封装任务3.3 Work接口3.4 发送任务3.5 回收资源&#xff0c;关闭管道&#xff08;重点&#xff09;3.6 改造CreatChannels接口 4. 完整代码 1. 进程池的概念…

数据结构面试-核心概念-问题理解

目录 1.数据结构及其分类 2.时间复杂度与空间复杂度及大O表示法 3.循环队列及其判断队空和队满的方法 4.栈与队列在计算机系统中的应用 5.串的模式匹配算法 6.线索二叉树、二叉搜索树、平衡二叉树 7.哈夫曼树与哈夫曼编码 8.DFS与BFS 9.最小生成树及其构建算法 10.最短…

谭晓生解读:AI如何重塑网络安全的未来?

导语 | 当前&#xff0c;对网络安全而言&#xff0c;每一次新的信息技术浪潮都蕴含着巨大机会&#xff0c;同时也意味着巨大的挑战。大模型的发展&#xff0c;是带来了更大的AI安全风险&#xff0c;还是将赋能提升网络安全呢&#xff1f;网络安全正在迎来一场重大范式转移&…

【网络编程】TCP通信基础模型实现

tcpSer.c #include <myhead.h> #define SER_IP "192.168.119.143" // 设置IP地址 #define SER_PORT 6666 // 设置端口号 int main(int argc, const char *argv[]) {// 1.创建socketint serfd socket(AF_INET, SOCK_STREAM, 0);// 参数1表示ipv4// 参数2表…

基于redis的zset实现排行榜

文章目录 &#x1f31e; Sun Frame&#xff1a;SpringBoot 的轻量级开发框架&#xff08;个人开源项目推荐&#xff09;&#x1f31f; 亮点功能&#x1f4e6; spring cloud模块概览常用工具 &#x1f517; 更多信息1.sun-club-subject集成redis1.sun-club-domain引入依赖2.sun-…

Linux 内核源码分析---内核 Netlink 套接字

linux 内核一直存在的一个严重问题就是内核态和用户态的交互的问题&#xff0c;对于这个问题内核大佬们一直在研究各种方法&#xff0c;想让内核和用户态交互能够安全高效的进行。如系统调用&#xff0c;proc&#xff0c;sysfs 等内存文件系统&#xff0c;但是这些方式一般都比…

从今年的计算机视觉比赛看风向

记第一次参加CV比赛的经历-长三角&#xff08;芜湖&#xff09;人工智能视觉算法大赛-CSDN博客 去年参赛的记录里说了&#xff1a; 最近&#xff0c;同样的由芜湖举办的比赛又上线了&#xff0c;果然&#xff1a; 2023年是这些赛题&#xff0c;典型的CV&#xff1a; 今年变成…