设计模式,创建型设计模式,工厂模式,建造者模式,单例模式

发布于:2025-03-27 ⋅ 阅读:(46) ⋅ 点赞:(0)

创建型模式

简单工厂

介绍:

简单工厂是一种编程习惯,也是一种建造型设计模式,提供了一种对象创建方式,无需向客户端暴露对象创建逻辑,只需要知道接口,即可创建不同的对象。

例子:

假设我们要创建不同类型的形状(如圆形、矩形等),我们可以使用简单工厂模式来实现。

Shape接口

public interface Shape {
    void draw();
}

Circle类

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a Circle");
    }
}

Rectangle类

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a Rectangle");
    }
}

ShapeFactory类

public class ShapeFactory {
    // 使用 getShape 方法获取形状类型的对象
    public static Shape getShape(String shapeType) {
        if (shapeType == null) {
            return null;
        }
        if (shapeType.equalsIgnoreCase("CIRCLE")) {
            return new Circle();
        } else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
            return new Rectangle();
        }
        return null;
    }
}

客户端代码

public class Client {
    public static void main(String[] args) {
        Shape shape1 = ShapeFactory.getShape("CIRCLE");
        shape1.draw();

        Shape shape2 = ShapeFactory.getShape("RECTANGLE");
        shape2.draw();
    }
}

UML图

在这里插入图片描述

优点

降低耦合度:将逻辑代码封装到工厂类中,客户端不需要知道创建的逻辑,只需要调用工厂方法即可,降低客户端的耦合度。

提高可扩展性:后续需要添加产品,只需要在工厂类扩展即可,不需要修改客户端代码,符合开闭原则。

缺点

一定程度上违反开闭原则:虽然简化了客户端代码,但是需要扩展的时候还是需要修改工厂类的逻辑,在一定程度上违反了开闭原则。

工厂类职责过重:随着类型的增加,导致工厂的代码逻辑越来越重。

工厂方法

介绍:

工厂方法模式是一种创建型设计模式,提供 ··············了一个创建对象的接口,让子类(具体工厂)决定创建的对象。后续如果有型对象新增时,只需要新增具体工厂类和具体产品即可,不需要修改原来的代码,符合开闭原则。

角色·······························

产品接口:定义产品的规范,所有具体产品类都必须实现该接口。

具体产品类:实现了产品接口的具体类。

创建者接口:声明工厂方法,该方法返回一个产品对象。

具体创建者类:实现了创建者接口,负责实际创建具体产品类的实例。

例子

假设我们要创建不同类型的交通工具(如汽车、自行车等),可以使用工厂方法模式来实现。

产品接口

// 产品接口
public interface Vehicle {
    void drive();
}

具体产品类

// 具体产品类 - 汽车
public class Car implements Vehicle {
    @Override
    public void drive() {
        System.out.println("Driving a Car");
    }
}

// 具体产品类 - 自行车
public class Bicycle implements Vehicle {
    @Override
    public void drive() {
        System.out.println("Riding a Bicycle");
    }
}

创建者接口

// 创建者接口
public abstract class VehicleFactory {
    // 工厂方法
    public abstract Vehicle createVehicle();
}

具体创建者类

// 具体创建者类 - 汽车工厂
public class CarFactory extends VehicleFactory {
    @Override
    public Vehicle createVehicle() {
        return new Car();
    }
}

// 具体创建者类 - 自行车工厂
public class BicycleFactory extends VehicleFactory {
    @Override
    public Vehicle createVehicle() {
        return new Bicycle();
    }
}

客户端代码

public class Client {
    public static void main(String[] args) {
        // 创建汽车工厂并生产汽车
        VehicleFactory carFactory = new CarFactory();
        Vehicle car = carFactory.createVehicle();
        car.drive();

        // 创建自行车工厂并生产自行车
        VehicleFactory bicycleFactory = new BicycleFactory();
        Vehicle bicycle = bicycleFactory.createVehicle();
        bicycle.drive();
    }
}

UML

在这里插入图片描述

优点

开闭原则:新产品的添加不需要修改原有代码,符合开闭原则。

提高灵活性和可扩展性:客户端可以根据不同需求选择不同工厂创建对象。

职责单一:每一个类只负责创建一种产品。

缺点

类爆炸:随着系统的维护,类会越来越多,增加系统复杂性。

客户端需要了解工厂类:客户端需要知道创建的对象使用哪一个工厂创建。

抽象工厂

介绍

抽象工厂是一种创建型设计模式,提供了一个接口,用于创建一系类相关或相互依赖的对象,不需要指定具体的类。通常定义一个抽象工厂接口,由实现类决定创建的具体产品族

角色

抽象产品接口:定义每一类产品的规范,所有具体产品类都必须实现这些接口。

具体产品类:实现了抽象产品接口的具体类,通常每个具体工厂类会创建一组具体的产品。

抽象工厂接口:声明创建一系列相关产品的方法。

具体工厂类:实现了抽象工厂接口,负责实际创建具体的产品族。

例子

抽象产品接口

// 抽象产品接口 - 按钮
public interface Button {
    void render();
}

// 抽象产品接口 - 复选框
public interface Checkbox {
    void render();
}

具体产品类

// 具体产品类 - Windows按钮
public class WindowsButton implements Button {
    @Override
    public void render() {
        System.out.println("Rendering a Windows button.");
    }
}

// 具体产品类 - Mac按钮
public class MacButton implements Button {
    @Override
    public void render() {
        System.out.println("Rendering a Mac button.");
    }
}

// 具体产品类 - Windows复选框
public class WindowsCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Rendering a Windows checkbox.");
    }
}

// 具体产品类 - Mac复选框
public class MacCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Rendering a Mac checkbox.");
    }
}

抽象工厂接口

// 抽象工厂接口
public interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

具体工厂类

// 具体工厂类 - Windows工厂
public class WindowsFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

// 具体工厂类 - Mac工厂
public class MacFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new MacButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new MacCheckbox();
    }
}

客户端代码

public class Client {
    private Button button;
    private Checkbox checkbox;

    public Client(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void renderUI() {
        button.render();
        checkbox.render();
    }

    public static void main(String[] args) {
        // 创建Windows主题的UI
        GUIFactory windowsFactory = new WindowsFactory();
        Client windowsClient = new Client(windowsFactory);
        windowsClient.renderUI();

        // 创建Mac主题的UI
        GUIFactory macFactory = new MacFactory();
        Client macClient = new Client(macFactory);
        macClient.renderUI();
    }
}

UML

在这里插入图片描述

抽象工厂和工厂方法的区别

  • 目的:工厂方法模式,定义一个创建单一产品对象的接口,让子类决定具体的实例化对象,抽象工厂方法模式,定义一个接口,用户创建一系类相关或相互依赖的接口,不需要指定他们具体的类。
  • 关注点:工厂方法模式关注单一产品,抽象工厂模式关注一整套相关产品。

建造者模式

介绍

建造者模式是一种创建型设计模式,他允许你分步骤的创建一个复杂对象,它的目的是让一个复杂对象的构建和表示进行分离,使得不同的构建过程能够的到不同的表示。

例如我要配置一台电脑,我有好几个方案,方案一是配置为(4060显卡,32G内存,1T硬盘空间),方案二是配置为(3050显卡,16G内存,512G硬盘空间),我根据我的需要告诉装机师傅,我要按照什么方案配置,按照方案一配置一台性能好的电脑,按照方案二配置一台性能弱一点的电脑,我的电脑就是产品,方案就是具体建造者,师傅就是指挥者,这个过程实现了构建与表示分离,可以根据不同的构建方案出现不同的表示(电脑)。

角色

  • 产品:一个复杂的对象
  • 抽象建造者:定义了创建复杂产品个个内容的抽象方法。
  • 具体建造者:实现了抽象建造者的接口,有它决定每一个内容的具体创建
  • 指挥者:由他嗲用建造者的方法,决定整个构建过程。

例子

// 产品类:电脑
class Computer {
    private String cpu;
    private String memory;
    private String hardDisk;

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public void setMemory(String memory) {
        this.memory = memory;
    }

    public void setHardDisk(String hardDisk) {
        this.hardDisk = hardDisk;
    }

    @Override
    public String toString() {
        return "CPU: " + cpu + ", Memory: " + memory + ", Hard Disk: " + hardDisk;
    }
}

// 抽象建造者类
abstract class ComputerBuilder {
    protected Computer computer;

    public ComputerBuilder() {
        this.computer = new Computer();
    }

    public abstract void setCPU(String cpu);
    public abstract void setMemory(String memory);
    public abstract void setHardDisk(String hardDisk);

    public Computer getComputer() {
        return computer;
    }
}

// 具体建造者类:组装特定配置的电脑
class GamingComputerBuilder extends ComputerBuilder {
    @Override
    public void setCPU(String cpu) {
        computer.setCpu(cpu);
    }

    @Override
    public void setMemory(String memory) {
        computer.setMemory(memory);
    }

    @Override
    public void setHardDisk(String hardDisk) {
        computer.setHardDisk(hardDisk);
    }
}

// 指挥者类
class Director {
    private ComputerBuilder builder;

    public Director(ComputerBuilder builder) {
        this.builder = builder;
    }

    public Computer constructComputer() {
        builder.setCPU("Intel i9");
        builder.setMemory("32GB");
        builder.setHardDisk("1TB SSD");
        return builder.getComputer();
    }
}

// 主类,测试建造者模式
public class BuilderPatternExample {
    public static void main(String[] args) {
        // 创建具体建造者
        GamingComputerBuilder gamingBuilder = new GamingComputerBuilder();
        // 创建指挥者,并传入具体建造者
        Director director = new Director(gamingBuilder);
        // 指挥者构建电脑
        Computer gamingComputer = director.constructComputer();
        System.out.println(gamingComputer);
    }
}

UML

在这里插入图片描述

单例模式

介绍

单例模式是一种创建型的设计模式,确保一个类只有一个实例化对象,并提供一个全局访问点,访问该对象。

饿汉模式

在类加载的时候初始化对象。

// 饿汉式单例模式
public class EagerSingleton {
    // 在类加载时就创建好单例对象
    private static final EagerSingleton INSTANCE = new EagerSingleton();

    // 私有构造函数,防止外部通过 new 创建实例
    private EagerSingleton() {}

    // 提供一个全局访问点
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

优点:实现简单,线程安全。

缺点:如果对象占用资源很多,一开始实例化又不使用,占用大量资源。

懒汉模式(非线程安全)

在类第一次调用的时候再初始化。

// 懒汉式单例模式(非线程安全)
public class LazySingleton {
    private static LazySingleton INSTANCE;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }
}

懒汉式单例(线程安全,加锁)

// 懒汉式单例模式(线程安全,加锁)
public class ThreadSafeLazySingleton {
    private static ThreadSafeLazySingleton INSTANCE;

    private ThreadSafeLazySingleton() {}

    public static synchronized ThreadSafeLazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new ThreadSafeLazySingleton();
        }
        return INSTANCE;
    }
}

双重检查锁(DCL)单例

// 双重检查锁单例模式
public class DclSingleton {
    // 使用 volatile 关键字保证可见性和禁止指令重排
    private static volatile DclSingleton INSTANCE;

    private DclSingleton() {}

    public static DclSingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (DclSingleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new DclSingleton();
                }
            }
        }
        return INSTANCE;
    }
}

了解一下volatile

什么是volatile:它是一个修饰符,用于修饰变量,它有以下两种功能。

  • 可见性:被它修饰的变量,被修改时,新值可以马上别其他线程看到
  • 禁止指令重排序:编译器和处理器为了提高效率,可能会将指令重新编排,导致意想不到的错误,使用了这个修饰符,可以禁止指令重新编排

为什么要只用volatile

举个例子,在执行new DclSingleton()的时候,会有以下几个步骤:

  • 分配内存
  • 初始化对象
    • 设置默认值
    • 执行构造器
  • 将INSTANCE指向分配的内存地址

由于指令重排序,步骤2,3可能会被重排序,先将INSTANCE指向分配的内存地址,在初始化对象

线程A先执行将INSTANCE指向分配的内存地址,这个时候还没有初始化对象,线程B判断这个对象已经指向一个地址了,不为null,放回一个未初始化的对象,导致程序出现问题。