设计模式专栏(二):创建型设计模式(Creational Design Patterns)

68 阅读8分钟

设计模式专栏(二):创建型设计模式(Creational Design Patterns)(关注“Beyond Code 程序员”订阅号查看更多文章)

模式介绍

创建型设计模式主要关注如何实例化对象,即解决“对象如何被创建”的问题。它们将对象的创建过程与使用过程分离,通过不同的设计方案实现灵活、高效和可复用的对象创建方式。

通过创建型模式,可以:

  • 降低系统与具体类的耦合度
  • 提高系统的灵活性和可扩展性
  • 方便对象的复用和管理

主要包含五种模式

  1. 单例模式(Singleton):保证一个类只有一个实例,并提供全局访问点。
  2. 工厂方法模式(Factory Method):定义一个创建对象的接口,由子类决定具体实例化的类。
  3. 抽象工厂模式(Abstract Factory):提供创建一系列相关对象的接口,而无需指定具体类。
  4. 建造者模式(Builder):将复杂对象的构建与表示分离,使同样的构建过程可以创建不同的表示。
  5. 原型模式(Prototype):通过复制已有对象来创建新对象,减少重复初始化成本。

⚠️ 提示:设计模式是一种编程思想,其实现方式并非唯一。本文示例代码仅作为参考,并不意味着设计模式必须按照示例中的方式实现。

1. 单例模式(Singleton Pattern)

模式解释

单例模式的核心思想是:

一个类只允许创建一个实例,并提供全局访问点。

适用场景:

  • 配置管理类(全局只有一份配置)
  • 数据库连接池(避免重复创建连接池)
  • 日志管理器(全局统一日志入口)

代码示例(Java,懒汉式 + 双重检查锁,含测试代码)

// 单例类
public class Singleton {

    // 1. 私有构造方法,禁止外部创建
    private Singleton() {
        System.out.println("单例对象被创建");
    }

    // 2. 使用 volatile 保证多线程可见性
    private static volatile Singleton instance = null;

    // 3. 提供全局访问点(线程安全)
    public static Singleton getInstance() {
         // 第一次检查,如果是全局第一次创建,才需要同步加锁创建对象,避免不必要的同步加锁
        if (instance == null) {
            synchronized (Singleton.class) {
                // 当多线程同时到达这里,只有一个线程能获取到锁,该线程创建好单例对象后,会释放锁;
                // 第二个线程获取锁时,由于单例对象已经被第一个线程创建,如果不再次判断,会导致重复创建;
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    // 业务方法
    public void doSomething() {
        System.out.println("单例方法执行中...");
    }

    // 测试入口
    public static void main(String[] args) {
        // 第一次调用会创建实例
        Singleton s1 = Singleton.getInstance();
        s1.doSomething();

        // 第二次调用不会创建新实例
        Singleton s2 = Singleton.getInstance();

        // 验证两个对象是否相同
        System.out.println("两个实例是否相同:" + (s1 == s2));
    }
}

运行效果

单例对象被创建
单例方法执行中...
两个实例是否相同:true

总结

优点:

  • 节省系统资源(避免重复创建对象)
  • 全局统一访问点(方便管理)

缺点:

  • 多线程下需小心处理(本例用双重检查锁解决)
  • 可能隐藏类间依赖关系(调试时不易发现)

2. 工厂方法模式(Factory Method Pattern)

模式解释

工厂方法模式核心思想是:

定义一个创建对象的接口,但由子类决定实例化哪一个类。使一个类的实例化延迟到子类。

适用场景:

  • 需要灵活创建不同类型对象的场景
  • 客户端不关心具体产品实现,只关心接口

代码示例(Java,含测试代码)

// 产品接口
interface Product {
    void use();
}

// 具体产品A
class ConcreteProductA implements Product {
    public void use() {
        System.out.println("使用产品A");
    }
}

// 具体产品B
class ConcreteProductB implements Product {
    public void use() {
        System.out.println("使用产品B");
    }
}

// 工厂接口
interface Factory {
    Product createProduct();
}

// 具体工厂A
class ConcreteFactoryA implements Factory {
    public Product createProduct() {
        return new ConcreteProductA();
    }
}

// 具体工厂B
class ConcreteFactoryB implements Factory {
    public Product createProduct() {
        return new ConcreteProductB();
    }
}

// 测试代码
public class FactoryMethodDemo {
    public static void main(String[] args) {
        Factory factoryA = new ConcreteFactoryA();
        Product productA = factoryA.createProduct();
        productA.use();

        Factory factoryB = new ConcreteFactoryB();
        Product productB = factoryB.createProduct();
        productB.use();
    }
}

运行效果

使用产品A
使用产品B

总结

优点:

  • 遵循单一职责原则,封装了具体产品创建过程
  • 扩展性好,添加新产品只需新建工厂和产品类

缺点:

  • 类数量增加,系统复杂度上升
  • 增加了系统的抽象性,理解和维护成本提升

3. 抽象工厂模式(Abstract Factory Pattern)

模式解释

抽象工厂模式核心思想是:

提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定具体类。

适用场景:

  • 需要创建多个产品族的对象,且这些对象之间有相互依赖关系
  • 需要确保同一产品族的产品一起使用

代码示例(Java,含测试代码)

// 抽象产品A
interface ProductA {
    void use();
}

// 抽象产品B
interface ProductB {
    void eat();
}

// 具体产品A1
class ConcreteProductA1 implements ProductA {
    public void use() {
        System.out.println("使用产品A1");
    }
}

// 具体产品A2
class ConcreteProductA2 implements ProductA {
    public void use() {
        System.out.println("使用产品A2");
    }
}

// 具体产品B1
class ConcreteProductB1 implements ProductB {
    public void eat() {
        System.out.println("吃产品B1");
    }
}

// 具体产品B2
class ConcreteProductB2 implements ProductB {
    public void eat() {
        System.out.println("吃产品B2");
    }
}

// 抽象工厂
interface AbstractFactory {
    ProductA createProductA();
    ProductB createProductB();
}

// 具体工厂1
class ConcreteFactory1 implements AbstractFactory {
    public ProductA createProductA() {
        return new ConcreteProductA1();
    }
    public ProductB createProductB() {
        return new ConcreteProductB1();
    }
}

// 具体工厂2
class ConcreteFactory2 implements AbstractFactory {
    public ProductA createProductA() {
        return new ConcreteProductA2();
    }
    public ProductB createProductB() {
        return new ConcreteProductB2();
    }
}

// 测试代码
public class AbstractFactoryDemo {
    public static void main(String[] args) {
        AbstractFactory factory1 = new ConcreteFactory1();
        ProductA a1 = factory1.createProductA();
        ProductB b1 = factory1.createProductB();
        a1.use();
        b1.eat();

        AbstractFactory factory2 = new ConcreteFactory2();
        ProductA a2 = factory2.createProductA();
        ProductB b2 = factory2.createProductB();
        a2.use();
        b2.eat();
    }
}

运行效果

使用产品A1
吃产品B1
使用产品A2
吃产品B2

总结

优点:

  • 保证同一产品族的产品一起使用,避免混用
  • 易于产品族的整体替换和扩展

缺点:

  • 系统扩展困难,增加新的产品族需要修改抽象工厂接口
  • 结构复杂,增加设计难度

4. 建造者模式(Builder Pattern)

模式解释

建造者模式核心思想是:

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

适用场景:

  • 需要构建复杂对象,且构建过程独立于组成部分的创建和组合方式
  • 需要多个不同的表示

代码示例(Java,含测试代码)

// 产品类
class Product {
    private String partA;
    private String partB;
    private String partC;

    public void setPartA(String partA) { this.partA = partA; }
    public void setPartB(String partB) { this.partB = partB; }
    public void setPartC(String partC) { this.partC = partC; }

    public void show() {
        System.out.println("产品组成:");
        System.out.println("部件A:" + partA);
        System.out.println("部件B:" + partB);
        System.out.println("部件C:" + partC);
    }
}

// 抽象建造者
abstract class Builder {
    protected Product product = new Product();

    public abstract void buildPartA();
    public abstract void buildPartB();
    public abstract void buildPartC();

    public Product getResult() {
        return product;
    }
}

// 具体建造者1
class ConcreteBuilder1 extends Builder {
    public void buildPartA() {
        product.setPartA("具体建造者1的部件A");
    }
    public void buildPartB() {
        product.setPartB("具体建造者1的部件B");
    }
    public void buildPartC() {
        product.setPartC("具体建造者1的部件C");
    }
}

// 具体建造者2
class ConcreteBuilder2 extends Builder {
    public void buildPartA() {
        product.setPartA("具体建造者2的部件A");
    }
    public void buildPartB() {
        product.setPartB("具体建造者2的部件B");
    }
    public void buildPartC() {
        product.setPartC("具体建造者2的部件C");
    }
}

// 指挥者
class Director {
    private Builder builder;

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

    public void construct() {
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
    }

    public Product getProduct() {
        return builder.getResult();
    }
}

// 测试代码
public class BuilderDemo {
    public static void main(String[] args) {
        Builder builder1 = new ConcreteBuilder1();
        Director director1 = new Director(builder1);
        director1.construct();
        Product product1 = director1.getProduct();
        product1.show();

        Builder builder2 = new ConcreteBuilder2();
        Director director2 = new Director(builder2);
        director2.construct();
        Product product2 = director2.getProduct();
        product2.show();
    }
}

运行效果

产品组成:
部件A:具体建造者1的部件A
部件B:具体建造者1的部件B
部件C:具体建造者1的部件C
产品组成:
部件A:具体建造者2的部件A
部件B:具体建造者2的部件B
部件C:具体建造者2的部件C

总结

优点:

  • 将复杂对象的构建过程封装,易于控制构建细节
  • 具体建造者之间独立,便于扩展和维护
  • 客户端不需要知道产品内部组成细节

缺点:

  • 产生多余的Builder类,系统更复杂
  • 建造者和产品之间关系紧密,需协调设计

5. 原型模式(Prototype Pattern)

模式解释

原型模式核心思想是:

通过复制已有的实例来创建新的实例,而不是通过 new 关键字创建,减少创建对象的成本。

适用场景:

  • 对象创建成本较高,需要大量相似对象
  • 希望通过拷贝现有对象快速创建新对象

代码示例(Java,含测试代码)

// 原型接口
interface Prototype extends Cloneable {
    Prototype clone();
}

// 具体原型类
class ConcretePrototype implements Prototype {
    private String name;

    public ConcretePrototype(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void show() {
        System.out.println("对象名称:" + name);
    }

    // 实现clone方法,浅拷贝
    public Prototype clone() {
        try {
            return (Prototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

// 测试代码
public class PrototypeDemo {
    public static void main(String[] args) {
        ConcretePrototype prototype1 = new ConcretePrototype("原型对象1");
        prototype1.show();

        ConcretePrototype prototype2 = (ConcretePrototype) prototype1.clone();
        prototype2.setName("克隆对象2");
        prototype2.show();

        System.out.println("prototype1和prototype2是否是同一个对象?" + (prototype1 == prototype2));
    }
}

运行效果

对象名称:原型对象1
对象名称:克隆对象2
prototype1和prototype2是否是同一个对象?false

总结

优点:

  • 通过克隆对象,避免了重新初始化资源消耗
  • 运行时动态决定创建哪种类型的对象,灵活性好

缺点:

  • 需要实现 Cloneable 接口,clone() 可能带来浅拷贝风险
  • 对复杂对象需要实现深拷贝,增加复杂度

📌 下一篇《设计模式专栏(三):结构型模式全解》


扫码关注订阅号

Beyond Code 程序员公众号二维码.jpg