常用设计模式介绍

常用设计模式介绍

工厂方法模式

工厂方法设计模式(Factory Method Pattern)是一种创建型设计模式,它提供了一种封装对象创建的方式。通过这种模式,父类将对象的创建延迟到子类中实现,从而使得基类或接口的代码与具体实现解耦。

工厂方法设计模式的核心思想:

  1. 定义一个用于创建对象的接口,但让实现这个接口的类决定实例化哪一个类。

  2. 工厂方法让类的实例化推迟到子类,这样客户端代码只需要依赖于抽象接口,而无需关注具体的实现细节。


举个例子: 场景描述: 假设我们有一个"支付系统",用户可以选择使用不同的支付方式(例如:支付宝、微信支付、银行卡等)。我们需要根据用户的输入来创建对应的支付对象,并调用其支付功能。

  1. 定义产品接口 首先,定义一个通用的支付接口 Payment,所有的具体支付方式都必须实现这个接口。

    java 复制代码
    public interface Payment {
        void pay(double amount);
    }
  2. 具体产品类 接着,实现两个具体的支付方式:支付宝和微信支付。

    java 复制代码
    public class Alipay implements Payment {
        @Override
        public void pay(double amount) {
            System.out.println("使用支付宝支付了:" + amount + "元");
        }
    }
    
    public class WechatPay implements Payment {
        @Override
        public void pay(double amount) {
            System.out.println("使用微信支付了:" + amount + "元");
        }
    }
  3. 定义工厂接口(可选) 为了进一步解耦,可以定义一个工厂接口 PaymentFactory,它声明了一个工厂方法 createPayment()。

    java 复制代码
    public interface PaymentFactory {
        Payment createPayment();
    }
  4. 具体工厂类 每个具体工厂类负责创建对应的具体产品。

    java 复制代码
    public class AlipayFactory implements PaymentFactory {
        @Override
        public Payment createPayment() {
            return new Alipay();
        }
    }
    
    public class WechatPayFactory implements PaymentFactory {
        @Override
        public Payment createPayment() {
            return new WechatPay();
        }
    }
  5. 使用示例 在客户端代码中,我们可以根据用户的选择动态地选择支付方式。

    java 复制代码
    public class Client {
        public static void main(String[] args) {
            // 假设用户选择了支付宝支付
            PaymentFactory factory = new AlipayFactory();
            Payment payment = factory.createPayment();
            payment.pay(100.0);
    
            // 如果需要切换为微信支付,只需更换工厂
            factory = new WechatPayFactory();
            payment = factory.createPayment();
            payment.pay(200.0);
        }
    }

工厂方法模式的优点:

  1. 解耦:客户端代码只依赖于抽象接口,不直接依赖具体类,这有助于降低模块之间的耦合度。

  2. 扩展性好:当需要新增一种支付方式时,只需添加新的具体产品类和对应的工厂类,而不需要修改已有的代码。

  3. 符合开闭原则:对扩展开放,对修改关闭。

适用场景:

  1. 当一个类不知道它所需要的对象的具体类时。

  2. 当一个类希望由它的子类来指定它所创建的对象的时候。

  3. 当一组相关类构成一个层次结构,且每组都需要实例化其中一个类时。

总结: 工厂方法模式通过将对象的创建交给子类,实现了对象创建的灵活性和扩展性。它非常适合用于需要根据条件动态选择不同实现的场景,同时也能很好地遵循面向对象的设计原则。


装饰器模式

装饰器设计模式(Decorator Pattern)是一种结构型设计模式,它允许在不修改对象接口的前提下,动态地为对象添加新的功能。这种模式通过组合的方式扩展对象的功能,而不是通过继承来实现,因此比传统的子类化方式更加灵活。


装饰器设计模式的核心思想:

  1. 保持对象接口一致:装饰器类与被装饰的对象实现相同的接口,这样客户端代码可以透明地使用原始对象和装饰后的对象。

  2. 组合优于继承:通过将原始对象包装在装饰器中,并在其基础上增加新功能,可以避免由于多层继承导致的类爆炸问题。

  3. 动态增强功能:可以在运行时根据需要多次装饰一个对象,从而逐步增强其行为


举个例子: 场景描述: 假设我们有一个简单的文本消息处理系统,我们需要对文本进行不同的修饰操作,例如加密、压缩、格式化等。我们可以使用装饰器模式来实现这些功能。

  1. 定义组件接口 首先定义一个通用的消息处理接口 Message。

    java 复制代码
    public interface Message {
        String getContent();
    }
  2. 具体组件类 创建一个基本的消息处理器 PlainTextMessage。

    java 复制代码
    public class PlainTextMessage implements Message {
        private String content;
    
        public PlainTextMessage(String content) {
            this.content = content;
        }
    
        @Override
        public String getContent() {
            return content;
        }
    }
  3. 定义装饰器抽象类 创建一个装饰器抽象类 MessageDecorator,它实现了 Message 接口,并持有一个 Message 对象的引用。

    java 复制代码
    public abstract class MessageDecorator implements Message {
        protected Message decoratedMessage;
    
        public MessageDecorator(Message decoratedMessage) {
            this.decoratedMessage = decoratedMessage;
        }
    
        @Override
        public String getContent() {
            return decoratedMessage.getContent();
        }
    }
  4. 具体装饰器类 实现具体的装饰器类,例如加密装饰器 EncryptedMessageDecorator 和压缩装饰器 CompressedMessageDecorator。

    java 复制代码
    public class EncryptedMessageDecorator extends MessageDecorator {
        public EncryptedMessageDecorator(Message decoratedMessage) {
            super(decoratedMessage);
        }
    
        @Override
        public String getContent() {
            // 在原有内容的基础上进行加密处理
            String originalContent = super.getContent();
            return encrypt(originalContent);
        }
    
        private String encrypt(String content) {
            // 简单的加密逻辑(实际应用中应使用更安全的算法)
            return "加密后的文本: " + content.hashCode();
        }
    }
    
    public class CompressedMessageDecorator extends MessageDecorator {
        public CompressedMessageDecorator(Message decoratedMessage) {
            super(decoratedMessage);
        }
    
        @Override
        public String getContent() {
            // 在原有内容的基础上进行压缩处理
            String originalContent = super.getContent();
            return compress(originalContent);
        }
    
        private String compress(String content) {
            // 简单的压缩逻辑(实际应用中应使用更高效的压缩算法)
            return "压缩后的文本: " + content.substring(0, Math.min(10, content.length())) + "...";
        }
    }
  5. 使用示例 在客户端代码中,我们可以根据需求动态地为消息添加加密和压缩功能。

    java 复制代码
    public class Client {
        public static void main(String[] args) {
            // 原始文本消息
            Message message = new PlainTextMessage("这是一个测试消息");
            System.out.println("原始消息: " + message.getContent());
    
            // 加密消息
            Message encryptedMessage = new EncryptedMessageDecorator(message);
            System.out.println("加密后的消息: " + encryptedMessage.getContent());
    
            // 加密并压缩消息
            Message compressedAndEncryptedMessage = new CompressedMessageDecorator(new EncryptedMessageDecorator(message));
            System.out.println("加密并压缩后的消息: " + compressedAndEncryptedMessage.getContent());
        }
    }

装饰器模式的优点:

  1. 动态扩展功能:可以在运行时根据需要动态地为对象添加功能,而不需要修改现有代码。

  2. 避免类爆炸:相比于通过继承来扩展功能,装饰器模式可以大大减少类的数量,避免了类爆炸问题。

  3. 符合开闭原则:对扩展开放,对修改关闭,新增装饰器不会影响已有代码。

适用场景:

  1. 当需要在不影响其他对象的情况下,以动态、透明的方式给对象添加职责时。

  2. 当子类扩展不再可行时(因为可能有太多组合,或者需要在运行时决定如何扩展)。

总结: 装饰器设计模式通过组合的方式,提供了一种灵活且可扩展的方式来增强对象的功能。它非常适合用于需要在运行时动态地为对象添加新功能的场景,同时也能很好地遵循面向对象的设计原则。


适配器模式

适配器设计模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户端期望的另一个兼容接口。适配器模式常用于解决已有类与目标接口不兼容的问题,使得原本由于接口不匹配而无法协同工作的类能够一起工作。


适配器模式的核心思想:

  1. 接口转换:通过创建一个适配器类,将一个类的接口包装或转换为另一个接口,从而满足客户端的需求。

  2. 复用已有代码:无需修改现有类的实现,就可以将其集成到新的系统中。

  3. 组合优于继承:适配器通常会持有被适配对象的实例,并在其基础上提供适配后的接口。


适配器模式的两种实现方式:

  1. 类适配器(使用继承)

    1. 适配器类同时继承目标接口和被适配的类。

    2. 适用于支持多重继承的语言(如 C++),但在 Java 中由于单继承限制,这种方式不太常用。

  2. 对象适配器(使用组合)

    1. 适配器类实现目标接口,并持有一个被适配对象的引用。

    2. 更加灵活,是 Java 等语言中更常见的做法。


示例说明: 场景描述: 假设我们正在开发一个日志分析系统,系统希望调用一个 Logger 接口来记录日志信息。但目前我们有一个第三方库提供的 LegacyLogSystem 类,它的方法名是 logMessage(String msg),而不是我们期望的 log(String message)。我们可以使用适配器模式来解决这个接口不一致的问题。

  1. 定义目标接口

    java 复制代码
    public interface Logger {
        void log(String message);
    }
  2. 已有的具体类

    java 复制代码
    public class LegacyLogSystem {
        public void logMessage(String msg) {
            System.out.println("Legacy Log: " + msg);
        }
    }
  3. 创建适配器类(对象适配器)

    java 复制代码
    public class LoggerAdapter implements Logger {
        private LegacyLogSystem legacyLogSystem;
    
        public LoggerAdapter(LegacyLogSystem legacyLogSystem) {
            this.legacyLogSystem = legacyLogSystem;
        }
    
        @Override
        public void log(String message) {
            // 将目标接口的方法调用适配到 LegacyLogSystem 的方法上
            legacyLogSystem.logMessage(message);
        }
    }
  4. 使用示例

    java 复制代码
    public class Client {
        public static void main(String[] args) {
            // 创建旧系统的日志对象
            LegacyLogSystem legacyLogSystem = new LegacyLogSystem();
    
            // 通过适配器将其接入新系统
            Logger logger = new LoggerAdapter(legacyLogSystem);
    
            // 调用统一的日志接口
            logger.log("这是一条测试日志消息");
        }
    }

输出结果:

Legacy Log: 这是一条测试日志消息


适配器模式的优点:

  1. 兼容性:可以让不兼容的接口协同工作,提高系统的兼容性和可扩展性。

  2. 复用性:可以复用已有的类,避免重复造轮子。

  3. 开闭原则:增加新的适配器不会影响原有代码逻辑。

适用场景:

  1. 当你想使用一个已经存在的类,但它的接口不符合你的需求时。

  2. 当你希望在不修改现有代码的前提下,让多个不同接口的类协同工作时。

  3. 当你需要对多个类进行统一接口封装时。

总结: 适配器设计模式是一种非常实用的设计模式,尤其适用于系统集成和遗留系统改造。通过适配器,我们可以轻松地将不同接口的类整合在一起,使得系统更加灵活、可维护和可扩展


策略模式

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。策略模式让算法的变化独立于使用它的客户端,从而实现动态切换行为的目的。


策略模式的核心思想:

  1. 封装变化:将一组相似的行为(如算法、策略)封装为不同的类。

  2. 多态调用:客户端通过统一接口来调用这些策略,达到运行时动态切换的效果。

  3. 解耦逻辑:避免在业务代码中使用大量的 if-else 或 switch-case 来判断不同策略。


示例说明: 场景描述: 我们继续以"支付系统"为例。用户可以选择多种支付方式(如支付宝、微信支付、银行卡等),每种支付方式有不同的验证和执行流程。我们可以使用策略模式来实现这个场景。

  1. 定义策略接口 首先定义一个通用的支付策略接口 PaymentStrategy。

    java 复制代码
    public interface PaymentStrategy {
        void pay(double amount);
    }
  2. 实现具体策略类

    java 复制代码
    //支付宝支付策略
    public class AlipayStrategy implements PaymentStrategy {
        @Override
        public void pay(double amount) {
            System.out.println("使用支付宝支付:" + amount + "元");
        }
    }
    //微信支付策略
    public class WechatPayStrategy implements PaymentStrategy {
        @Override
        public void pay(double amount) {
            System.out.println("使用微信支付:" + amount + "元");
        }
    }
    //银行卡支付策略
    public class BankCardStrategy implements PaymentStrategy {
        private String cardNumber;
    
        public BankCardStrategy(String cardNumber) {
            this.cardNumber = cardNumber;
        }
    
        @Override
        public void pay(double amount) {
            System.out.println("使用银行卡(尾号:" + cardNumber.substring(cardNumber.length() - 4) + ")支付:" + amount + "元");
        }
    }
  3. 创建上下文类(Context) 该类用于持有当前使用的策略对象,并对外提供统一的方法供客户端调用

    java 复制代码
    public class PaymentContext {
        private PaymentStrategy paymentStrategy;
    
        public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
            this.paymentStrategy = paymentStrategy;
        }
    
        public void executePayment(double amount) {
            paymentStrategy.pay(amount);
        }
    }
  4. 使用示例 客户端可以根据用户的输入动态选择不同的支付方式。

    java 复制代码
    public class Client {
        public static void main(String[] args) {
            PaymentContext context = new PaymentContext();
    
            // 用户选择支付宝支付
            context.setPaymentStrategy(new AlipayStrategy());
            context.executePayment(100.0);
    
            // 用户选择微信支付
            context.setPaymentStrategy(new WechatPayStrategy());
            context.executePayment(200.0);
    
            // 用户选择银行卡支付
            context.setPaymentStrategy(new BankCardStrategy("6225880123456789"));
            context.executePayment(300.0);
        }
    }

输出结果:

使用支付宝支付:100.0元 使用微信支付:200.0元 使用银行卡(尾号:6789)支付:300.0元


策略模式的优点:

  1. 可扩展性强:新增一种支付方式只需添加新的策略类,无需修改已有代码。

  2. 高内聚低耦合:每个策略独立封装,便于维护和测试。

  3. 易于替换:运行时可以根据需要动态更换策略。

适用场景:

  1. 当某个业务逻辑有多个变体(如支付方式、折扣策略、排序算法等),且需要在运行时动态切换时。

  2. 避免大量 if-else 或 switch-case 判断逻辑。

  3. 当希望将复杂行为从主业务逻辑中剥离出来进行独立管理时。

总结: 策略模式通过将不同的行为封装为独立的类,并通过统一接口进行调用,实现了灵活切换和良好扩展性。它非常适合用于需要根据不同条件选择不同处理逻辑的场景,是解耦和提高可维护性的有力工具。


模板方法模式

模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个算法的骨架(即一个操作中的"固定流程"),而将一些步骤延迟到子类中去实现。这样可以在不改变算法结构的前提下,允许子类重新定义算法中的某些步骤。


模板方法模式的核心思想:

  1. 定义算法框架:在抽象类中定义一个 final 方法(即模板方法),该方法包含了算法的主要步骤。

  2. 延迟具体实现:将算法中可能变化的部分定义为抽象方法,由子类来具体实现。

  3. 钩子方法(可选):提供一些可选的钩子方法(hook methods),让子类可以有选择地参与算法流程。


示例说明: 场景描述: 我们以"制作咖啡和茶"为例。无论是泡咖啡还是泡茶,它们的基本步骤是相似的:

  1. 把水烧开

  2. 冲泡饮品

  3. 倒入杯子

  4. 添加调料(如糖或牛奶)

但每一步的具体实现会有所不同,我们可以使用模板方法模式来统一这个流程。

  1. 定义抽象类(模板类)

    java 复制代码
    public abstract class Beverage {
    
        // 模板方法:定义算法骨架
        public final void prepareBeverage() {
            boilWater();
            brew();
            pourInCup();
            addCondiments();
        }
    
        // 公共方法:所有饮料都需要执行的操作
        private void boilWater() {
            System.out.println("烧开水");
        }
    
        private void pourInCup() {
            System.out.println("倒入杯中");
        }
    
        // 抽象方法:需要子类实现
        protected abstract void brew();
    
        protected abstract void addCondiments();
    }
  2. 创建具体子类

    java 复制代码
    //咖啡类
    public class Coffee extends Beverage {
        @Override
        protected void brew() {
            System.out.println("用滤纸冲泡咖啡");
        }
    
        @Override
        protected void addCondiments() {
            System.out.println("添加糖和牛奶");
        }
    }
    //茶类
    public class Tea extends Beverage {
        @Override
        protected void brew() {
            System.out.println("用热水泡茶叶");
        }
    
        @Override
        protected void addCondiments() {
            System.out.println("添加柠檬片");
        }
    }
csharp 复制代码
3. 使用示例

```java
public class Client {
    public static void main(String[] args) {
        System.out.println("准备一杯咖啡:");
        Beverage coffee = new Coffee();
        coffee.prepareBeverage();

        System.out.println("\n准备一杯茶:");
        Beverage tea = new Tea();
        tea.prepareBeverage();
    }
}

输出结果:

准备一杯咖啡: 烧开水 用滤纸冲泡咖啡 倒入杯中 添加糖和牛奶

准备一杯茶: 烧开水 用热水泡茶叶 倒入杯中 添加柠檬片


模板方法模式的优点:

  1. 代码复用性高:将公共逻辑封装在父类中,避免重复代码。

  2. 扩展性强:新增子类时只需重写特定步骤,而不影响整体流程。

  3. 符合开闭原则:对扩展开放,对修改关闭。

  4. 控制流程一致性:确保算法结构不变,避免子类破坏流程顺序。


钩子方法(Hook Method)的扩展: 你还可以在模板类中加入钩子方法,让子类决定是否执行某个步骤。例如:

java 复制代码
protected boolean customerWantsCondiments() {
    return true;
}

然后在模板方法中判断是否调用添加调料的方法:

java 复制代码
if (customerWantsCondiments()) {
    addCondiments();
}

子类可以选择覆盖这个钩子方法来决定是否添加调料。


适用场景:

  1. 当多个类有相似的算法结构,但具体实现细节不同时。

  2. 当希望统一控制算法流程,防止子类破坏流程逻辑时。

  3. 当希望减少重复代码并提高可维护性时。


总结: 模板方法模式通过在抽象类中定义算法骨架,并将具体实现延迟到子类中,实现了良好的封装性和扩展性。它是实现"算法复用 + 行为定制"的一种非常有效的方式,广泛应用于框架设计、业务流程标准化等场景


责任链模式

责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它将请求的发送者和接收者解耦,并通过一个对象链来处理请求。每个处理者对象在接收到请求后,可以选择自己处理、转发给下一个处理者或者直接丢弃请求。


责任链模式的核心思想:

  1. 请求的传递链:多个处理器构成一条链,请求从链的一端发起,沿着链依次传递。

  2. 动态处理流程:每个处理器决定是否处理请求或将其传递给下一个处理器。

  3. 解耦请求与处理逻辑:客户端不需要知道具体由谁处理,只需要将请求发到链上即可。


示例说明: 场景描述: 我们以"审批流程"为例。例如员工提交请假申请,根据请假天数不同,审批人也不同:

  1. 1~3天:主管审批

  2. 4~7天:部门经理审批

  3. 8天以上:总经理审批

我们可以使用责任链模式实现这个流程。


  1. 定义抽象处理类

    java 复制代码
    public abstract class Approver {
        protected Approver nextApprover;
    
        public void setNextApprover(Approver nextApprover) {
            this.nextApprover = nextApprover;
        }
    
        public abstract void approve(int days);
    }
  2. 实现具体的处理类

    java 复制代码
    //主管审批类
    public class TeamLeaderApprover extends Approver {
        @Override
        public void approve(int days) {
            if (days <= 3) {
                System.out.println("主管审批了 " + days + " 天的请假");
            } else if (nextApprover != null) {
                System.out.println("主管无权审批,转交下一级...");
                nextApprover.approve(days);
            }
        }
    }
    //部门经理审批类
    public class DepartmentManagerApprover extends Approver {
        @Override
        public void approve(int days) {
            if (days > 3 && days <= 7) {
                System.out.println("部门经理审批了 " + days + " 天的请假");
            } else if (nextApprover != null) {
                System.out.println("部门经理无权审批,转交下一级...");
                nextApprover.approve(days);
            }
        }
    }
    //总经理审批类
    public class CEOApprover extends Approver {
        @Override
        public void approve(int days) {
            if (days > 7) {
                System.out.println("总经理审批了 " + days + " 天的请假");
            } else if (nextApprover != null) {
                nextApprover.approve(days);
            } else {
                System.out.println("没有合适的审批人,请假申请被拒绝");
            }
        }
    }
  3. 使用示例

    java 复制代码
    public class Client {
        public static void main(String[] args) {
            // 构建审批责任链
            Approver teamLeader = new TeamLeaderApprover();
            Approver departmentManager = new DepartmentManagerApprover();
            Approver ceo = new CEOApprover();
    
            teamLeader.setNextApprover(departmentManager);
            departmentManager.setNextApprover(ceo);
    
            // 模拟不同天数的请假申请
            System.out.println("请假1天:");
            teamLeader.approve(1);
    
            System.out.println("\n请假5天:");
            teamLeader.approve(5);
    
            System.out.println("\n请假10天:");
            teamLeader.approve(10);
        }
    }

输出结果:

请假1天: 主管审批了 1 天的请假

请假5天: 主管无权审批,转交下一级... 部门经理审批了 5 天的请假

请假10天: 主管无权审批,转交下一级... 部门经理无权审批,转交下一级... 总经理审批了 10 天的请假


责任链模式的优点:

优点 描述
解耦请求与处理 请求发送者不需要知道具体的处理者是谁,只需把请求发到链上。
可扩展性强 可以灵活地增加或修改处理节点,符合开闭原则。
职责清晰 每个处理者只负责自己的业务逻辑,便于维护和测试。
支持动态流程 可在运行时根据条件构建不同的处理链路。

适用场景:

  1. 工作流审批系统(如报销、请假、采购等)

  2. 过滤器/拦截器机制(如 Spring 中的 HandlerInterceptor)

  3. 日志处理(如按级别过滤日志输出)


总结: 责任链模式非常适合用于多级判断处理的场景,它让请求处理流程更加清晰、灵活。通过将处理逻辑拆分为独立的节点,不仅提高了系统的可扩展性,也增强了代码的可读性和可维护性。


单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类在整个应用程序生命周期中只有一个实例存在,并提供一个全局访问点来获取这个实例。


单例模式的核心思想:

  1. 私有构造方法:防止外部通过 new 关键字创建多个实例。

  2. 静态私有实例:在类内部维护一个唯一的实例。

  3. 公共静态方法:对外提供获取该唯一实例的方法


优点:

优点 描述
资源节约 避免频繁创建和销毁对象,节省系统资源。
全局访问 提供统一的访问入口,便于共享数据或服务。
控制实例数量 保证在整个程序中某个类只有一个实例,避免冲突

示例说明: 场景描述: 我们以"数据库连接池"为例。数据库连接是一种有限资源,通常希望在整个系统中只使用一个连接池管理器,这样可以统一管理和调度连接。


  1. 简单懒汉式单例(线程不安全)

    java 复制代码
    public class DatabaseConnection {
    
        // 私有静态实例
        private static DatabaseConnection instance;
    
        // 私有构造方法
        private DatabaseConnection() {
            System.out.println("数据库连接池初始化...");
        }
    
        // 公共静态方法获取实例
        public static DatabaseConnection getInstance() {
            if (instance == null) {
                instance = new DatabaseConnection();
            }
            return instance;
        }
    
        public void connect() {
            System.out.println("连接到数据库");
        }
    }

⚠️ 缺点:在多线程环境下可能创建多个实例。


  1. 懒汉式 + 同步方法(线程安全)

    java 复制代码
    public class DatabaseConnection {
    
        private static DatabaseConnection instance;
    
        private DatabaseConnection() {
            System.out.println("数据库连接池初始化...");
        }
    
        // 加同步锁保证线程安全
        public static synchronized DatabaseConnection getInstance() {
            if (instance == null) {
                instance = new DatabaseConnection();
            }
            return instance;
        }
    
        public void connect() {
            System.out.println("连接到数据库");
        }
    }

✅ 优点:线程安全

⚠️ 缺点:每次调用 getInstance() 都需要加锁,性能较差。


  1. 双重检查锁定(Double-Checked Locking)
java 复制代码
public class DatabaseConnection {

    // 使用 volatile 保证多线程可见性
    private static volatile DatabaseConnection instance;

    private DatabaseConnection() {
        System.out.println("数据库连接池初始化...");
    }

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

    public void connect() {
        System.out.println("连接到数据库");
    }
}

✅ 推荐方式,兼顾性能与线程安全。


  1. 饿汉式单例(类加载时就初始化)
java 复制代码
public class DatabaseConnection {

    // 类加载时就初始化
    private static final DatabaseConnection INSTANCE = new DatabaseConnection();

    private DatabaseConnection() {
        System.out.println("数据库连接池初始化...");
    }

    public static DatabaseConnection getInstance() {
        return INSTANCE;
    }

    public void connect() {
        System.out.println("连接到数据库");
    }
}

✅ 线程安全,简单高效

⚠️ 缺点:不管是否使用都会初始化,造成资源浪费。


  1. 枚举实现单例(推荐方式)

    java 复制代码
    public enum DatabaseConnectionEnum {
        INSTANCE;
    
        public void connect() {
            System.out.println("连接到数据库");
        }
    }

✅ 最安全的方式,防止反射攻击、序列化破坏单例

✅ 天然支持线程安全和反序列化机制


使用示例:

java 复制代码
public class Client {
    public static void main(String[] args) {
        DatabaseConnection conn1 = DatabaseConnection.getInstance();
        DatabaseConnection conn2 = DatabaseConnection.getInstance();

        System.out.println("conn1 == conn2: " + (conn1 == conn2)); // 输出 true
        conn1.connect();
    }
}

输出结果:

数据库连接池初始化... conn1 == conn2: true 连接到数据库


适用场景:

  1. 日志记录器(如日志文件只能有一个写入者)

  2. 缓存管理器(如 Redis 连接池)

  3. 线程池管理器

  4. 配置管理器(如读取配置文件后缓存配置信息)


注意事项:

问题 建议
多线程安全 使用双重检查锁定或枚举实现
反射攻击 使用枚举或在构造函数中加判断防止多次创建
序列化/反序列化破坏单例 使用枚举或自定义 readResolve() 方法
过度使用 避免滥用单例,可能导致代码难以测试和扩展

总结: 单例模式是应用最广泛的设计模式之一,适用于需要全局唯一实例的场景。虽然实现方式多样,但推荐使用双重检查锁定或枚举实现,既能保证线程安全,又能防止反射和序列化带来的破坏。合理使用单例模式能提高系统的稳定性和资源利用率。

相关推荐
我的炸串拌饼店11 分钟前
ASP.NET MVC 中SignalR实现实时进度通信的深度解析
后端·asp.net·mvc
挑战者6668881 小时前
springboot入门之路(一)
java·spring boot·后端
wmze2 小时前
InnoDB存储引擎
后端
lifallen3 小时前
Java BitSet类解析:高效位向量实现
java·开发语言·后端·算法
子恒20054 小时前
警惕GO的重复初始化
开发语言·后端·云原生·golang
daiyunchao4 小时前
如何理解"LLM并不理解用户的需求,只是下一个Token的预测,但他能很好的完成任务,比如写对你想要的代码"
后端·ai编程
Android洋芋4 小时前
SettingsActivity.kt深度解析
后端
onejason5 小时前
如何利用 PHP 爬虫按关键字搜索 Amazon 商品
前端·后端·php
Java水解5 小时前
深度解析MySQL中的Join算法:原理、实现与优化
后端·mysql