在 Java 编程的浩瀚宇宙中,设计模式宛如璀璨星辰,照亮着开发者构建高效、可维护代码的道路。工厂模式作为广为人知的创建型模式,在对象创建的灵活性和可扩展性方面发挥着重要作用。然而,除了工厂模式,还有许多同样强大且实用的设计模式等待我们去探索。今天,就让我们一同深入了解工厂模式之外的三个冷门却堪称杀手级的设计模式:单例模式、原型模式和建造者模式,挖掘它们在 Java 开发中的独特魅力与无限潜力。

单例模式:独一无二的存在

单例模式,旨在确保一个类在整个应用程序中仅有一个实例,并为该实例提供一个全局访问点。这一模式如同应用程序中的 “管家”,掌控着特定资源或功能的唯一入口,避免了资源的重复创建与浪费,有效提升了系统的性能和稳定性。

在 Java 中,实现单例模式的方式多种多样,其中饿汉式和懒汉式最为常见。饿汉式单例在类加载时就立即创建唯一实例,如同一位勤劳的管家,早早做好准备,随时待命。示例代码如下:

java

public class HungrySingleton {
    // 私有构造函数,防止外部实例化
    private HungrySingleton() {}
    // 在类加载时创建唯一实例
    private static final HungrySingleton instance = new HungrySingleton();
    // 提供全局访问点
    public static HungrySingleton getInstance() {
        return instance;
    }
}

这种方式简单直接,天生具备线程安全性,因为在类加载阶段,JVM 会确保实例的创建过程是线程安全的。然而,它也存在一定的弊端,即使在应用程序启动时,该实例可能暂时不会被使用,但仍然会被创建,这在一定程度上可能造成资源的浪费。

懒汉式单例则恰恰相反,它秉持着 “按需创建” 的原则,只有在首次调用getInstance()方法时才会创建实例,如同一位精明的管家,只在真正需要时才出手。示例代码如下:

java

public class LazySingleton {
    // 私有构造函数,防止外部实例化
    private LazySingleton() {}
    // 声明静态实例,但初始化为null
    private static LazySingleton instance = null;
    // 提供全局访问点,使用synchronized关键字确保线程安全
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

懒汉式单例解决了饿汉式可能存在的资源浪费问题,但由于getInstance()方法添加了synchronized关键字,在多线程环境下,每次调用该方法都需要进行同步操作,这在一定程度上会影响性能。为了优化性能,我们可以采用双重检查锁定(Double-Checked Locking)机制,进一步提升懒汉式单例的效率。改进后的代码如下:

java

public class DoubleCheckedLockingSingleton {
    // 私有构造函数,防止外部实例化
    private DoubleCheckedLockingSingleton() {}
    // 使用volatile关键字确保可见性和禁止指令重排序
    private static volatile DoubleCheckedLockingSingleton instance = null;
    // 提供全局访问点
    public static DoubleCheckedLockingSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLockingSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
}

在上述代码中,通过双重检查锁定,只有在实例为null时才进行同步操作,并且在同步块内部再次检查实例是否为null,以确保在多线程环境下只创建一个实例。同时,使用volatile关键字修饰instance变量,保证了变量的可见性和禁止指令重排序,避免了可能出现的空指针异常等问题。

单例模式在实际应用中有着广泛的场景。例如,在数据库连接池的管理中,为了避免频繁创建和销毁数据库连接,通常会采用单例模式,确保整个应用程序中只有一个数据库连接池实例,所有需要数据库连接的模块都共享该实例,从而提高了数据库连接的复用性和系统的性能。又如,在日志记录器的实现中,使用单例模式可以保证整个应用程序的日志记录都由同一个日志记录器实例负责,确保了日志记录的一致性和完整性。

原型模式:复制的艺术

原型模式,是一种通过复制现有对象(原型)来创建新对象的设计模式。与传统的通过new关键字创建对象的方式不同,原型模式利用对象的克隆机制,直接从已有的对象中复制出一个新的对象,从而避免了复杂的对象初始化过程,提高了对象创建的效率。

在 Java 中,实现原型模式需要借助Cloneable接口和clone()方法。Cloneable接口是一个标记接口,用于标识该类的对象可以被克隆。clone()方法则定义在Object类中,默认实现为浅克隆(Shallow Clone)。浅克隆仅仅复制对象的基本类型属性和对象引用,而不会递归地复制引用对象的内部属性。示例代码如下:

java

public class Prototype implements Cloneable {
    private int id;
    private String name;
    // 构造函数
    public Prototype(int id, String name) {
        this.id = id;
        this.name = name;
    }
    // Getter和Setter方法
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    protected Prototype clone() throws CloneNotSupportedException {
        return (Prototype) super.clone();
    }
}

在上述代码中,Prototype类实现了Cloneable接口,并覆盖了clone()方法。在clone()方法中,通过调用super.clone()方法来实现浅克隆。当我们需要创建一个新的Prototype对象时,可以通过调用已有对象的clone()方法来实现,如下所示:

java

public class Main {
    public static void main(String[] args) {
        Prototype prototype = new Prototype(1, "原型对象");
        try {
            Prototype clonedPrototype = prototype.clone();
            System.out.println("克隆对象的ID: " + clonedPrototype.getId());
            System.out.println("克隆对象的名称: " + clonedPrototype.getName());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

运行上述代码,我们可以看到克隆对象的属性值与原型对象相同,证明克隆操作成功。然而,对于包含复杂对象引用的情况,浅克隆可能无法满足需求,因为克隆对象和原型对象会共享引用对象,当其中一个对象修改了引用对象的属性时,另一个对象也会受到影响。为了解决这个问题,我们需要实现深克隆(Deep Clone)。

深克隆不仅复制对象的基本类型属性和对象引用,还会递归地复制引用对象的内部属性,确保克隆对象和原型对象完全独立。实现深克隆的方式有多种,其中一种常用的方法是通过对象序列化和反序列化来实现。示例代码如下:

java

import java.io.*;
public class DeepPrototype implements Serializable {
    private static final long serialVersionUID = 1L;
    private int id;
    private String name;
    // 构造函数
    public DeepPrototype(int id, String name) {
        this.id = id;
        this.name = name;
    }
    // Getter和Setter方法
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public DeepPrototype deepClone() {
        try {
            // 将对象写入字节数组输出流
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            oos.close();
            // 从字节数组输入流中读取对象
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            DeepPrototype clonedPrototype = (DeepPrototype) ois.readObject();
            ois.close();
            return clonedPrototype;
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}

在上述代码中,DeepPrototype类实现了Serializable接口,表明该类的对象可以被序列化和反序列化。deepClone()方法通过将对象写入字节数组输出流,再从字节数组输入流中读取对象,实现了对象的深克隆。使用深克隆的示例如下:

java

public class Main {
    public static void main(String[] args) {
        DeepPrototype prototype = new DeepPrototype(1, "原型对象");
        DeepPrototype clonedPrototype = prototype.deepClone();
        System.out.println("克隆对象的ID: " + clonedPrototype.getId());
        System.out.println("克隆对象的名称: " + clonedPrototype.getName());
    }
}

运行上述代码,我们可以得到一个与原型对象完全独立的克隆对象,即使修改克隆对象或原型对象中引用对象的属性,也不会影响到另一个对象。

原型模式在实际应用中常用于创建复杂对象或需要频繁创建相似对象的场景。例如,在游戏开发中,创建大量具有相似属性的游戏角色时,可以先创建一个角色原型,然后通过克隆原型来快速创建多个角色实例,减少了对象创建的时间和资源开销。又如,在数据备份和恢复场景中,可以将当前系统状态作为原型,通过克隆来创建备份副本,以便在需要时进行恢复操作。

建造者模式:复杂对象的构建蓝图

建造者模式,将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。它通过定义一个建造者接口,具体的建造者类实现该接口来构建对象的各个部分,最后由导演类来控制对象的构建过程。这种模式就如同建筑工程中的施工蓝图,设计师负责设计蓝图(建造者接口),工人按照蓝图进行施工(具体建造者类),项目经理负责协调施工过程(导演类),最终建造出不同风格和功能的建筑(复杂对象)。

在 Java 中,建造者模式通常包含以下几个角色:抽象建造者(Builder)、具体建造者(ConcreteBuilder)、产品(Product)和导演(Director)。抽象建造者定义了构建产品各个部分的抽象方法,具体建造者实现这些方法来构建具体的产品,产品是最终构建出来的复杂对象,导演类负责调用建造者的方法来控制构建过程。示例代码如下:

java

// 产品类:表示复杂对象
class Computer {
    private String cpu;
    private String memory;
    private String hardDisk;
    // Getter和Setter方法
    public String getCpu() {
        return cpu;
    }
    public void setCpu(String cpu) {
        this.cpu = cpu;
    }
    public String getMemory() {
        return memory;
    }
    public void setMemory(String memory) {
        this.memory = memory;
    }
    public String getHardDisk() {
        return hardDisk;
    }
    public void setHardDisk(String hardDisk) {
        this.hardDisk = hardDisk;
    }
    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", memory='" + memory + '\'' +
                ", hardDisk='" + hardDisk + '\'' +
                '}';
    }
}
// 抽象建造者接口
interface ComputerBuilder {
    void buildCpu();
    void buildMemory();
    void buildHardDisk();
    Computer getComputer();
}
// 具体建造者类:实现抽象建造者接口
class HighEndComputerBuilder implements ComputerBuilder {
    private Computer computer = new Computer();
    @Override
    public void buildCpu() {
        computer.setCpu("Intel Core i9");
    }
    @Override
    public void buildMemory() {
        computer.setMemory("32GB DDR4");
    }
    @Override
    public void buildHardDisk() {
        computer.setHardDisk("1TB SSD");
    }
    @Override
    public Computer getComputer() {
        return computer;
    }
}
// 导演类:控制构建过程
class Director {
    private ComputerBuilder computerBuilder;
    public Director(ComputerBuilder computerBuilder) {
        this.computerBuilder = computerBuilder;
    }
    public Computer constructComputer() {
        computerBuilder.buildCpu();
        computerBuilder.buildMemory();
        computerBuilder.buildHardDisk();
        return computerBuilder.getComputer();
    }
}

在上述代码中,Computer类表示复杂对象,包含cpumemoryhardDisk等属性。ComputerBuilder接口定义了构建Computer对象各个部分的抽象方法,HighEndComputerBuilder类实现了该接口,具体构建高端电脑的各个组件。Director类负责调用ComputerBuilder的方法来构建电脑对象。使用建造者模式的示例如下:

java

public class Main {
    public static void main(String[] args) {
        ComputerBuilder highEndBuilder = new HighEndComputerBuilder();
        Director director = new Director(highEndBuilder);
        Computer highEndComputer = director.constructComputer();
        System.out.println(highEndComputer);
    }
}

运行上述代码,我们可以得到一个配置高端的电脑对象。通过建造者模式,我们可以方便地扩展和修改电脑的配置,只需要创建新的具体建造者类并实现相应的构建方法即可,而不需要修改导演类和其他相关类的代码,符合开闭原则。

建造者模式在实际应用中常用于创建具有复杂内部结构和多种配置选项的对象。例如,在构建复杂的用户界面时,不同的用户界面可能包含不同的组件和布局,通过建造者模式可以将界面的构建过程与具体的界面表示分离,使得我们可以轻松地创建出不同风格和功能的用户界面。又如,在构建数据库查询语句时,查询语句可能包含多个条件、排序方式和返回字段等复杂配置,使用建造者模式可以方便地构建出各种不同的查询语句。

单例模式、原型模式和建造者模式,它们各自拥有独特的设计理念和应用场景。单例模式确保对象的唯一性,为系统资源管理提供了高效的解决方案;原型模式通过复制对象提高了对象创建的效率,适用于复杂对象的快速创建;建造者模式将复杂对象的构建过程分离,使得构建过程更加灵活和可维护。在实际的 Java 开发中,我们应根据具体的需求和场景,合理运用这些设计模式,让我们的代码更加优雅、高效和可扩展。希望通过本文的介绍,能让大家对这三个冷门却强大的设计模式有更深入的理解和认识,在未来的编程之旅中,充分发挥它们的优势,创造出更加优秀的软件作品。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐