三大设计模式:巩固封装、继承和多态

本文档针对 Java 新手,基于封装、继承、多态三大特性,详细讲解单例模式、工厂模式、模板方法模式,重点补充单例模式的饿汉式、懒汉式实现细节,所有示例均为可直接运行的 Java 代码。

一、单例模式(Singleton Pattern)

1. 核心定义

单例模式是创建型设计模式,核心目标是:保证一个类在程序运行期间仅存在一个实例对象,并提供一个全局统一的访问入口,避免重复创建对象造成的资源浪费。

2. 核心设计思路

  • 私有化构造方法:禁止外部通过 new 关键字创建实例;
  • 类内部自行创建实例:通过静态属性存储唯一实例;
  • 提供公共静态方法:返回该唯一实例,作为全局访问点。

3. 常见实现方式

1. 饿汉式(立即加载)

核心特点

  • 类加载时(ClassLoader 加载阶段)就创建实例,属于 “立即初始化”;
  • 天然线程安全(JVM 保证类加载过程是线程安全的);
  • 简单易实现,但可能造成资源浪费(即使实例未被使用,也会占用内存)。

完整代码

/**
 * 单例模式 - 饿汉式(基础版)
 * 优点:线程安全、实现简单;缺点:类加载即初始化,可能浪费内存
 */
public class HungrySingleton {
    // 1. 私有化静态实例:类加载时直接初始化,JVM 保证线程安全
    private static final HungrySingleton INSTANCE = new HungrySingleton();
    
    // 2. 私有化构造方法:禁止外部通过 new 创建对象
    private HungrySingleton() {
        // 防止反射破坏单例(可选,新手可先忽略)
        if (INSTANCE != null) {
            throw new RuntimeException("禁止创建多个单例实例");
        }
    }
    
    // 3. 公共静态方法:返回唯一实例
    public static HungrySingleton getInstance() {
        return INSTANCE;
    }
    
    // 测试方法:验证实例唯一性
    public void showInfo() {
        System.out.println("饿汉式单例实例的哈希值:" + this.hashCode());
    }
    
    // 测试主方法
    public static void main(String[] args) {
        // 获取两个实例
        HungrySingleton s1 = HungrySingleton.getInstance();
        HungrySingleton s2 = HungrySingleton.getInstance();
        
        // 验证是否为同一个对象(哈希值相同,== 结果为 true)
        s1.showInfo();
        s2.showInfo();
        System.out.println("s1 和 s2 是否为同一个实例:" + (s1 == s2)); // 输出 true
    }
}
2. 懒汉式(延迟加载)

核心特点

  • 实例在首次调用 getInstance() 方法时才创建,属于 “延迟初始化”;
  • 节省内存(按需创建),但需要手动保证线程安全;
  • 新手需重点掌握 “基础懒汉式” 和 “线程安全懒汉式” 的区别。
① 基础懒汉式(非线程安全)
/**
 * 单例模式 - 懒汉式(基础版,非线程安全)
 * 优点:延迟加载、节省内存;缺点:多线程环境下会创建多个实例
 */
public class LazySingletonUnsafe {
    // 1. 私有化静态实例:初始为 null,延迟初始化
    //此时就不能像饿汉式那样在这里用final了,因为下面还要赋值
    private static LazySingletonUnsafe INSTANCE = null;
    
    // 2. 私有化构造方法
    private LazySingletonUnsafe() {}
    
    // 3. 公共静态方法:首次调用时创建实例
    public static LazySingletonUnsafe getInstance() {
        // 问题:多线程同时进入 if 判断,会创建多个实例
        if (INSTANCE == null) {
            INSTANCE = new LazySingletonUnsafe();
        }
        return INSTANCE;
    }
    
    // 测试方法
    public void showInfo() {
        System.out.println("非线程安全懒汉式实例哈希值:" + this.hashCode());
    }
    
    // 多线程测试(验证线程不安全)
    public static void main(String[] args) {
        // 开启 10 个线程,同时获取实例
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazySingletonUnsafe instance = LazySingletonUnsafe.getInstance();
                instance.showInfo();
            }).start();
        }
        // 运行结果:会出现多个不同的哈希值,说明创建了多个实例
    }
}
② 线程安全懒汉式(加锁版)
/**
 * 单例模式 - 懒汉式(线程安全版,加 synchronized 锁)
 * 优点:延迟加载、线程安全;缺点:加锁导致性能损耗(每次调用都要抢锁)
 */
public class LazySingletonSafe {
    private static LazySingletonSafe INSTANCE = null;
    
    private LazySingletonSafe() {}
    
    // 给方法加 synchronized 锁,保证同一时间只有一个线程能执行
    public static synchronized LazySingletonSafe getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazySingletonSafe();
        }
        return INSTANCE;
    }
    
    public void showInfo() {
        System.out.println("线程安全懒汉式实例哈希值:" + this.hashCode());
    }
    
    // 多线程测试(验证线程安全)
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazySingletonSafe instance = LazySingletonSafe.getInstance();
                instance.showInfo();
            }).start();
        }
        // 运行结果:所有哈希值相同,仅创建一个实例
    }
}
③ 双重检查锁(DCL,最优懒汉式)
/**
 * 单例模式 - 懒汉式(双重检查锁 DCL,新手进阶版)
 * 优点:延迟加载、线程安全、性能优(仅首次创建时加锁);
 * 注意:INSTANCE 必须加 volatile 关键字,防止指令重排序
 */
public class LazySingletonDCL {
    // volatile 关键字:禁止指令重排序,保证 INSTANCE 初始化完成后才被读取
    private static volatile LazySingletonDCL INSTANCE = null;
    
    private LazySingletonDCL() {}
    
    public static LazySingletonDCL getInstance() {
        // 第一次检查:实例已创建则直接返回,避免每次抢锁
        if (INSTANCE == null) {
            // 加锁:保证同一时间只有一个线程进入
            synchronized (LazySingletonDCL.class) {
                // 第二次检查:防止多个线程等待锁后重复创建
                if (INSTANCE == null) {
                    INSTANCE = new LazySingletonDCL();
                }
            }
        }
        return INSTANCE;
    }
    
    public void showInfo() {
        System.out.println("DCL 懒汉式实例哈希值:" + this.hashCode());
    }
    
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazySingletonDCL instance = LazySingletonDCL.getInstance();
                instance.showInfo();
            }).start();
        }
    }
}

4. 饿汉式 vs 懒汉式 对比

特性 饿汉式 懒汉式(DCL)
初始化时机 类加载时 首次调用 getInstance ()
线程安全 天然安全(JVM 保证) 手动加锁(DCL)保证
内存占用 可能浪费(提前初始化) 按需加载,节省内存
性能 无锁,性能优 首次创建加锁,后续无锁
实现复杂度 简单(新手首选) 稍复杂(需注意 volatile

5. 适用场景

  • 配置管理类(如数据库配置、系统参数配置):全局唯一配置,避免重复加载;
  • 日志工具类:保证日志写入顺序,避免多实例导致日志混乱;
  • 数据库连接池:减少连接创建开销,统一管理连接资源。

二、工厂模式(Factory Pattern)

1. 核心定义

工厂模式是创建型设计模式,核心目标是:将对象的创建逻辑从业务代码中抽离,封装到专门的 “工厂类” 中,外部只需通过工厂获取实例,无需关心创建细节,核心体现 “多态” 和 “封装”。

2. 核心设计思路

  • 定义产品接口 / 抽象类:统一所有产品的行为规范;
  • 实现具体产品类:不同产品实现接口,体现多态;
  • 定义工厂类:封装对象创建逻辑,根据入参 / 规则返回对应产品实例。

3. 分类与实现

1. 简单工厂模式(静态工厂,新手首选)

核心特点:一个工厂类创建所有产品,无需创建工厂实例,通过静态方法调用。

// 步骤 1:定义产品接口(统一行为)
public interface Shape {
    void draw(); // 所有图形都有“绘制”行为
}

// 步骤 2:实现具体产品类
// 圆形
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
}

// 矩形
public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("绘制矩形");
    }
}

// 步骤 3:创建简单工厂类
public class ShapeFactory {
    // 静态方法:根据类型创建对应图形
    public static Shape createShape(String shapeType) {
        // 封装创建逻辑,外部无需知道 new Circle() 还是 new Rectangle()
        switch (shapeType) {
            case "circle":
                return new Circle();
            case "rectangle":
                return new Rectangle();
            default:
                throw new IllegalArgumentException("不支持的图形类型:" + shapeType);
        }
    }
}

// 步骤 4:测试代码
public class SimpleFactoryTest {
    public static void main(String[] args) {
        // 外部只需调用工厂方法,无需直接 new 产品类
        Shape circle = ShapeFactory.createShape("circle");
        circle.draw(); // 输出:绘制圆形
        
        Shape rectangle = ShapeFactory.createShape("rectangle");
        rectangle.draw(); // 输出:绘制矩形
    }
}
2. 工厂方法模式(进阶版)

核心特点:为每个产品创建专属工厂,符合 “开闭原则”(新增产品只需加工厂,无需改原有代码)。

// 步骤 1:复用上述 Shape 接口和 Circle、Rectangle 产品类

// 步骤 2:定义工厂接口
public interface ShapeFactory {
    Shape createShape(); // 工厂方法:创建对应产品
}

// 步骤 3:实现具体工厂类
// 圆形工厂
public class CircleFactory implements ShapeFactory {
    @Override
    public Shape createShape() {
        return new Circle();
    }
}

// 矩形工厂
public class RectangleFactory implements ShapeFactory {
    @Override
    public Shape createShape() {
        return new Rectangle();
    }
}

// 步骤 4:测试代码
public class FactoryMethodTest {
    public static void main(String[] args) {
        // 创建圆形工厂 → 获取圆形实例
        ShapeFactory circleFactory = new CircleFactory();
        Shape circle = circleFactory.createShape();
        circle.draw();
        
        // 创建矩形工厂 → 获取矩形实例
        ShapeFactory rectangleFactory = new RectangleFactory();
        Shape rectangle = rectangleFactory.createShape();
        rectangle.draw();
        
        // 新增三角形:只需加 Triangle 产品类 + TriangleFactory 工厂类,无需改原有代码
    }
}

4. 核心优缺点

优点 缺点
解耦:产品创建与使用分离 简单工厂模式:新增产品需修改工厂类(违反开闭原则)
多态:统一接口调用,无需区分产品类型 工厂方法模式:类数量翻倍(产品 + 工厂),增加复杂度
便于扩展:新增产品只需加类(工厂方法) 简单工厂模式:创建逻辑集中,复杂后不易维护

5. 适用场景

  • 产品类型较多且创建逻辑复杂(如支付方式:支付宝、微信、银联);
  • 外部无需知道产品创建细节(如数据库驱动创建,JDBC 用工厂模式隐藏不同数据库的驱动实现);
  • 需统一管理产品创建(如校验参数、初始化配置后再创建实例)。

三、模板方法模式(Template Method Pattern)

1. 核心定义

模板方法模式是行为型设计模式,核心目标是:在抽象父类中定义一个算法的 “骨架(模板)”,将算法中可变的步骤延迟到子类实现,固定步骤留在父类,核心体现 “继承” 和 “多态” 的结合。

2. 核心设计思路

  • 抽象父类:定义算法的固定流程(模板方法,通常用 final 修饰,防止子类修改流程);
  • 抽象方法:父类声明可变步骤,由子类实现;
  • 具体子类:重写抽象方法,定制化可变步骤,固定流程不变。

3. 完整实现

// 步骤 1:定义抽象父类(模板类)
public abstract class AbstractOrderProcessor {
    /**
     * 模板方法:订单处理的固定流程(final 修饰,禁止子类修改)
     * 流程:校验订单 → 扣减库存 → 生成订单 → 通知用户
     */
    public final void processOrder() {
        validateOrder(); // 固定步骤 1:校验订单(父类实现)
        deductStock();   // 可变步骤 1:扣减库存(子类实现)
        generateOrder(); // 固定步骤 2:生成订单(父类实现)
        notifyUser();    // 可变步骤 2:通知用户(子类实现)
    }
    
    // 固定方法:校验订单(所有订单校验逻辑相同)
    private void validateOrder() {
        System.out.println("1. 校验订单:订单号、金额、收货地址合规");
    }
    
    // 固定方法:生成订单(所有订单生成逻辑相同)
    private void generateOrder() {
        System.out.println("3. 生成订单:创建订单记录,分配订单号");
    }
    
    // 抽象方法:扣减库存(不同订单扣减逻辑不同,子类实现)
    protected abstract void deductStock();
    
    // 抽象方法:通知用户(不同订单通知方式不同,子类实现)
    protected abstract void notifyUser();
}

// 步骤 2:实现子类(普通商品订单)
public class NormalOrderProcessor extends AbstractOrderProcessor {
    @Override
    protected void deductStock() {
        System.out.println("2. 扣减普通商品库存:直接扣减仓库库存");
    }
    
    @Override
    protected void notifyUser() {
        System.out.println("4. 通知用户:发送短信告知订单创建成功");
    }
}

// 步骤 3:实现子类(预售商品订单)
public class PreSaleOrderProcessor extends AbstractOrderProcessor {
    @Override
    protected void deductStock() {
        System.out.println("2. 扣减预售商品库存:扣减预售库存,锁定货源");
    }
    
    @Override
    protected void notifyUser() {
        System.out.println("4. 通知用户:发送APP推送告知预售订单创建成功");
    }
}

// 步骤 4:测试代码
public class TemplateMethodTest {
    public static void main(String[] args) {
        // 处理普通订单
        System.out.println("=== 处理普通订单 ===");
        AbstractOrderProcessor normalOrder = new NormalOrderProcessor();
        normalOrder.processOrder();
        
        // 处理预售订单
        System.out.println("\n=== 处理预售订单 ===");
        AbstractOrderProcessor preSaleOrder = new PreSaleOrderProcessor();
        preSaleOrder.processOrder();
    }
}

4. 代码运行结果

=== 处理普通订单 ===
1. 校验订单:订单号、金额、收货地址合规
2. 扣减普通商品库存:直接扣减仓库库存
3. 生成订单:创建订单记录,分配订单号
4. 通知用户:发送短信告知订单创建成功

=== 处理预售订单 ===
1. 校验订单:订单号、金额、收货地址合规
2. 扣减预售商品库存:扣减预售库存,锁定货源
3. 生成订单:创建订单记录,分配订单号
4. 通知用户:发送APP推送告知预售订单创建成功

5. 核心优缺点

优点 缺点
代码复用:父类封装固定流程,子类只需实现可变部分 算法骨架固定,修改流程需改父类(违反开闭原则)
开闭原则:新增订单类型只需加子类,无需改父类 子类过多时,增加系统复杂度
统一流程:保证所有子类遵循相同的算法步骤 父类依赖子类实现,可能导致耦合过高

6. 适用场景

  • 多个业务逻辑流程基本一致,仅部分步骤有差异(如:不同支付方式的流程、不同报表的生成流程);
  • 希望限制子类对核心流程的修改,仅开放细节定制(如:框架中的钩子方法);
  • 典型应用:Spring 框架中的 JdbcTemplate(固定数据库操作流程,开放 SQL 执行、结果处理等可变步骤)。

四、总结

  1. 单例模式:核心是 “唯一实例”,新手优先掌握饿汉式(简单安全),进阶学习 DCL 懒汉式(性能优),volatile 关键字是 DCL 的关键;
  2. 工厂模式:核心是 “封装对象创建”,简单工厂(静态方法)适合新手,工厂方法模式更符合开闭原则,核心体现多态;
  3. 模板方法模式:核心是 “固定流程 + 可变步骤”,父类用 final 模板方法固定流程,子类重写抽象方法定制细节,核心体现继承和多态的结合。

这三个模式是 Java 面向对象编程的核心落地场景,建议先跑通所有代码示例,再尝试在小项目(如图书管理系统)中应用,加深对封装、继承、多态的理解。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐