设计模式(二)-创建者模式

1.基本概述

这些模式关注对象的创建机制,即如何实例化对象或者组装对象的创建流程。创建型模式的目标是将对象的创建与对象的使用分离,从而提高系统的灵活性和可维护性。

包括单例模式、抽象工厂模式、工厂方法模式、建造者模式和原型模式。

 

2.单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点以访问该实例。

比如Runtime类使用的是饿汉式(静态属性)方式来实现单例模式的。

 

饿汉式

饿汉式(Eager Initialization): 在类加载时就已经创建好实例。

创建方法:

  • 静态常量:在静态初始化器中创建实例。
  • 静态代码块:在静态代码块中创建实例。

1.静态常量实现

实例是在静态常量 instance 的声明时就被创建了,因此这是一种饿汉式的实现方式。它保证了在类加载时就创建了唯一实例,不会存在多线程并发访问的问题。

package com.dreams.pattern.singleton.demo1;

public class EagerSingletonStaticConstant {
    // 声明一个私有静态常量来保存唯一实例,直接初始化
    private static final EagerSingletonStaticConstant instance = new EagerSingletonStaticConstant();

    // 私有化构造函数,防止外部直接实例化该类
    private EagerSingletonStaticConstant() {
        // 防止外部实例化
    }

    // 提供一个静态方法来获取唯一实例
    public static EagerSingletonStaticConstant getInstance() {
        // 直接返回静态常量中的实例
        return instance;
    }

    // 其他方法...
}

 

2.静态代码块实现

在这个实现中,实例是在静态代码块中被创建的,这也是一种饿汉式的实现方式。静态代码块在类加载时执行,因此保证了实例的唯一性,不会存在多线程并发访问的问题。

package com.dreams.pattern.singleton.demo1;

public class EagerSingletonStaticBlock {
    // 声明一个私有静态变量来保存唯一实例
    private static EagerSingletonStaticBlock instance;

    // 静态代码块,在类加载时执行,用于初始化实例
    static {
        instance = new EagerSingletonStaticBlock();
    }

    // 私有化构造函数,防止外部直接实例化该类
    private EagerSingletonStaticBlock() {
        // 防止外部实例化
    }

    // 提供一个静态方法来获取唯一实例
    public static EagerSingletonStaticBlock getInstance() {
        // 直接返回静态变量中的实例
        return instance;
    }

    // 其他方法...
}

 

 

懒汉式

懒汉式(Lazy Initialization): 在首次被调用时才创建实例。

1.基本方式:

package com.dreams.pattern.singleton.demo2;

public class LazySingleton {
    // 声明一个私有静态变量来保存唯一实例
    private static LazySingleton instance;

    // 私有化构造函数,防止外部直接实例化该类
    private LazySingleton() {
        // 防止外部实例化
    }

    // 提供一个静态方法来获取唯一实例
    public static LazySingleton getInstance() {
        // 如果实例不存在,则创建一个新实例
        if (instance == null) {
            instance = new LazySingleton();
        }
        // 返回已有的实例
        return instance;
    }

    // 其他方法...
}

getInstance() 方法简单地检查实例是否已经被创建,如果没有则创建一个新实例并返回,如果已经存在则直接返回现有实例。这种方式简单直观,但是在多线程环境下可能存在线程安全问题。

 

2.加锁实现线程安全的懒汉式:

通过在 getInstance() 方法上加上 synchronized 关键字,保证了在多线程环境下的线程安全性,但是会影响性能。

package com.dreams.pattern.singleton.demo2;

public class LazySingletonThreadSafe {
    // 声明一个私有静态变量来保存唯一实例
    private static LazySingletonThreadSafe instance;

    // 私有化构造函数,防止外部直接实例化该类
    private LazySingletonThreadSafe() {
        // 防止外部实例化
    }

    // 提供一个静态方法来获取唯一实例,使用 synchronized 关键字确保线程安全
    public static synchronized LazySingletonThreadSafe getInstance() {
        // 如果实例不存在,则创建一个新实例
        if (instance == null) {
            instance = new LazySingletonThreadSafe();
        }
        // 返回已有的实例
        return instance;
    }

    // 其他方法...
}

 

3.双重检查锁定实现线程安全的懒汉式:

对于 getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式

这种方式在第一次检查实例是否存在时避免了每次调用都进入同步区块,提高了性能。同时,双重检查锁定保证了在多线程环境下的线程安全性。需要注意的是,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。使用 volatile 关键字即可确保实例的可见性,防止指令重排序导致的问题。

package com.dreams.pattern.singleton.demo2;

public class LazySingletonDoubleCheckedLocking {
    // 声明一个私有静态变量来保存唯一实例,使用 volatile 关键字确保线程可见性
    private static volatile LazySingletonDoubleCheckedLocking instance;

    // 私有化构造函数,防止外部直接实例化该类
    private LazySingletonDoubleCheckedLocking() {
        // 防止外部实例化
    }

    // 提供一个静态方法来获取唯一实例
    public static LazySingletonDoubleCheckedLocking getInstance() {
        // 第一次检查,如果实例不存在,则进入同步区块
        if (instance == null) {
            // 同步区块,只有第一次会进入,其他线程等待
            synchronized (LazySingletonDoubleCheckedLocking.class) {
                // 第二次检查,确保在同步区块内部实例仍未被创建
                if (instance == null) {
                    // 创建实例
                    instance = new LazySingletonDoubleCheckedLocking();
                }
            }
        }
        // 返回已有的实例
        return instance;
    }

    // 其他方法...
}

 

4.懒汉式静态内部类方式的实现

解决指令重排序问题还可以使用静态内部类方式

静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。

package com.dreams.pattern.singleton.demo2;

public class LazySingletonStaticInnerClass {

    // 私有化构造函数,防止外部直接实例化该类
    private LazySingletonStaticInnerClass() {
        // 防止外部实例化
    }

    // 静态内部类,用于延迟加载单例实例
    private static class SingletonHolder {
        // 静态变量,在类加载时初始化,保证线程安全
        private static final LazySingletonStaticInnerClass INSTANCE = new LazySingletonStaticInnerClass();
    }

    // 提供一个静态方法来获取唯一实例
    public static LazySingletonStaticInnerClass getInstance() {
        // 返回静态内部类中的实例
        return SingletonHolder.INSTANCE;
    }

    // 其他方法...
}

静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

 

5.枚举方式

枚举方式是一种简洁高效且线程安全的单例模式实现方式。在Java中,枚举类型的实例是线程安全的,并且在类加载时就被初始化,因此可以保证实例的唯一性和线程安全性。

在这个实现中,LazySingletonEnum.INSTANCE 就是单例的唯一实例。由于枚举类型的特性,它在类加载时就被初始化,且保证线程安全,因此无需额外的同步机制。此外,枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式,枚举类型天然防止了反序列化、反射等方式创建新的实例,从而保证了单例的唯一性。

枚举方式属于饿汉式方式。

package com.dreams.pattern.singleton.demo2;

public enum LazySingletonEnum {

    // 定义一个枚举元素,它代表单例的实例
    INSTANCE;

    // 可以在枚举中定义其他成员变量和方法

    // 示例方法
    public void doSomething() {
        // 单例实例的操作
    }
}

调用

package com.dreams.pattern.singleton.demo2;

public class Client {
    public static void main(String[] args) {
        LazySingletonEnum instance1 = LazySingletonEnum.INSTANCE;
        LazySingletonEnum instance2 = LazySingletonEnum.INSTANCE;

        System.out.println(instance1 == instance2);
    }
}

可以看到确实是同一个对象。

 

存在问题

破坏单例模式通常指的是通过某种手段绕过了单例模式的设计,使得可以创建多个实例或者访问私有构造函数来实例化新对象。

反射破坏单例

package com.dreams.pattern.singleton.demo2;

import java.lang.reflect.Constructor;

public class Singleton {
    // 单例模式中的静态实例
    private static Singleton instance = new Singleton();

    // 私有构造函数,防止外部实例化
    private Singleton() {
        System.out.println("Singleton instance created");
    }

    // 获取单例实例的静态方法
    public static Singleton getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        // 获取单例实例
        Singleton singleton = Singleton.getInstance();

        try {
            // 使用反射获取构造函数
            Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
            // 设置私有构造函数可访问
            constructor.setAccessible(true);
            // 通过反射创建新的实例
            Singleton anotherSingleton = constructor.newInstance();

            // 输出两个实例的内存地址,如果地址不同则说明成功破坏了单例模式
            System.out.println("Original Singleton: " + singleton);
            System.out.println("Another Singleton: " + anotherSingleton);

            if (singleton != anotherSingleton){
                System.out.println("破坏了单例模式");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

要解决通过反射机制破坏单例模式的问题,可以在单例类的构造函数中添加防御措施,防止通过反射方式创建新对象。这可以通过在构造函数中检查实例是否已经存在来实现。

当尝试通过反射方式创建新实例时,会在构造函数中抛出 IllegalStateException 异常,提示单例实例已经存在,从而防止了通过反射破坏单例模式。

package com.dreams.pattern.singleton.demo2;

import java.lang.reflect.Constructor;

public class Singleton {
    // 单例模式中的静态实例
    private static Singleton instance = new Singleton();

    // 私有构造函数,防止外部实例化
    private Singleton() {
        // 防止通过反射创建新实例
        if (instance != null) {
            throw new IllegalStateException("破坏了单例模式");
        }
        System.out.println("Singleton instance created");
    }

    // 获取单例实例的静态方法
    public static Singleton getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        // 获取单例实例
        Singleton singleton = Singleton.getInstance();

        try {
            // 使用反射获取构造函数
            Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
            // 设置私有构造函数可访问
            constructor.setAccessible(true);
            // 通过反射创建新的实例,这里会抛出异常
            Singleton anotherSingleton = constructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

序列化破坏单例

首先,让我们定义一个简单的单例类 Singleton:

package com.dreams.pattern.singleton.demo2;

import java.io.Serializable;

public class Singleton implements Serializable {
    // 单例模式中的静态实例
    private static Singleton instance = new Singleton();

    // 私有构造函数,防止外部实例化
    private Singleton() {
        System.out.println("Singleton instance created");
    }

    // 获取单例实例的静态方法
    public static Singleton getInstance() {
        return instance;
    }
}

然后,我们尝试将这个单例对象序列化到文件中,再从文件中反序列化出来:

package com.dreams.pattern.singleton.demo2;

import java.io.*;

public class Main {
    public static void main(String[] args) {
        // 获取单例实例
        Singleton singleton = Singleton.getInstance();

        // 将单例对象序列化到文件中
        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
            out.writeObject(singleton);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 从文件中反序列化单例对象
        try {
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
            Singleton deserializedSingleton = (Singleton) in.readObject();
            in.close();

            // 输出反序列化后的对象的内存地址,如果地址与单例对象相同,则说明反序列化成功
            System.out.println("Deserialized Singleton: " + deserializedSingleton);
            // 判断反序列化得到的对象是否与原单例对象是同一个实例
            System.out.println("Are they the same instance? " + (singleton == deserializedSingleton));
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

我们可以看到序列化后,再反序列化的对象实际上不是同一个实例,从而破坏了单例模式。

解决方法就是可以通过实现 readResolve() 方法,当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回。这样可以防止通过反序列化创建新的实例。

package com.dreams.pattern.singleton.demo2;

import java.io.Serializable;

public class Singleton implements Serializable {
    // 单例模式中的静态实例
    private static Singleton instance = new Singleton();

    // 私有构造函数,防止外部实例化
    private Singleton() {
        System.out.println("Singleton instance created");
    }

    // 获取单例实例的静态方法
    public static Singleton getInstance() {
        return instance;
    }

    // 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
    protected Object readResolve() {
        return instance;
    }
}

再调用就可以看到是同一个对象了。

在源码里可以看到它会去判断有没有readResolve()这个方法

 

3.简单工厂模式

简单工厂模式虽然是一种常见的设计模式,但它并没有被 GoF 所归类为经典的 23 种设计模式之一,可能是因为它相对来说较为简单,而且在一些场景下可能会违反开闭原则,即新增产品类型时需要修改工厂类的代码,导致扩展性不佳。

简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。简单工厂一般分为:普通简单工厂,多方法简单工厂,静态简单工厂。

代码:

package com.dreams.pattern.simpleFactory;

// 定义一个抽象的图形接口
interface Shape {
    void draw();
}

// 具体的圆形类实现图形接口
class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("画一个圆形");
    }
}

// 具体的矩形类实现图形接口
class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("画一个矩形");
    }
}

// 具体的正方形类实现图形接口
class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("画一个正方形");
    }
}

// 简单工厂类
class ShapeFactory {
    // 根据传入的参数创建不同类型的图形对象
    public static Shape createShape(String shapeType) {
        if (shapeType.equalsIgnoreCase("circle")) {
            return new Circle();
        } else if (shapeType.equalsIgnoreCase("rectangle")) {
            return new Rectangle();
        } else if (shapeType.equalsIgnoreCase("square")) {
            return new Square();
        } else {
            throw new IllegalArgumentException("Unsupported shape type");
        }
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        // 使用简单工厂创建圆形对象
        Shape circle = ShapeFactory.createShape("circle");
        circle.draw();

        // 使用简单工厂创建矩形对象
        Shape rectangle = ShapeFactory.createShape("rectangle");
        rectangle.draw();

        // 使用简单工厂创建正方形对象
        Shape square = ShapeFactory.createShape("square");
        square.draw();
    }
}

可以看到如果想要扩展新的类型就需要更改工厂类的代码,违反了开闭原则。

简单工厂模式优点和缺点:

优点:

  1. 封装了对象的创建过程:简单工厂模式将对象的创建逻辑封装在工厂类中,客户端只需要通过工厂类来获取对象,无需关心对象的创建细节。
  2. 降低了耦合度:客户端代码只依赖于工厂类而不依赖于具体的产品类,这降低了客户端代码与产品类之间的耦合度,使得系统更易于维护和扩展。
  3. 统一了对象的管理:通过工厂类统一管理对象的创建过程,可以更方便地对对象进行管理和控制。

缺点:

  1. 违反了开闭原则:每当需要添加新的产品类时,都需要修改工厂类的代码,这违反了开闭原则,增加了系统的维护成本。
  2. 工厂类职责过重:随着产品类的增加,工厂类的职责会变得越来越重,可能会导致工厂类代码过于臃肿,不利于系统的扩展和维护。
  3. 不够灵活:简单工厂模式通常只能创建一种类型的对象,无法根据客户端的需求灵活地选择创建哪种类型的对象。

 

4.工厂方法模式

使用工厂方法模式就完全遵循开闭原则。

工厂方法模式是一种创建型设计模式,它定义了一个用于创建对象的接口,但将对象的实际创建延迟到子类中。这样,客户端代码就不需要知道具体要实例化哪个类,而是由子类来决定实例化哪个类。这种模式通过提供一个抽象的创建方法,使得系统在不同条件下能够选择创建不同类型的对象,从而提高了代码的灵活性和可扩展性。

工厂方法模式包含以下几个角色:

  • 抽象产品(Product):定义了产品的接口,是具体产品类的公共接口。
  • 具体产品(Concrete Product):实现了抽象产品接口,是被创建的对象。
  • 抽象工厂(Creator):声明了一个创建产品对象的工厂方法,返回一个抽象产品类型的对象。
  • 具体工厂(Concrete Creator):实现了抽象工厂接口,负责实际创建具体产品对象。

代码:

package com.dreams.pattern.factoryMethod;

// 抽象产品接口
interface Product {
    void display();
}

// 具体产品A
class ConcreteProductA implements Product {
    @Override
    public void display() {
        System.out.println("Concrete Product A");
    }
}

// 具体产品B
class ConcreteProductB implements Product {
    @Override
    public void display() {
        System.out.println("Concrete Product B");
    }
}

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

// 具体工厂A,用于创建具体产品A
class ConcreteFactoryA implements Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductA();
    }
}

// 具体工厂B,用于创建具体产品B
class ConcreteFactoryB implements Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductB();
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建具体工厂A
        Factory factoryA = new ConcreteFactoryA();
        // 使用具体工厂A创建产品A
        Product productA = factoryA.createProduct();
        // 调用产品A的方法
        productA.display();
        
        // 创建具体工厂B
        Factory factoryB = new ConcreteFactoryB();
        // 使用具体工厂B创建产品B
        Product productB = factoryB.createProduct();
        // 调用产品B的方法
        productB.display();
    }
}

在这个示例中,抽象产品接口 Product 定义了产品的行为,具体产品类 ConcreteProductA 和 ConcreteProductB 实现了该接口。抽象工厂接口 Factory 定义了创建产品的方法,具体工厂类 ConcreteFactoryA 和 ConcreteFactoryB 分别实现了该接口,并负责创建对应的具体产品。客户端通过具体工厂来创建产品对象,并调用其方法来实现具体的功能。

添加新的产品只要实现具体工厂类即可,完全遵循开闭原则。

优缺点:

优点:

  • 符合开闭原则:工厂方法模式通过引入抽象工厂接口和具体工厂类,使得系统在增加新的产品类时更加灵活,无需修改已有的代码。
  • 降低了客户端代码的耦合度:客户端代码只依赖于抽象工厂接口和抽象产品接口,不需要直接依赖于具体产品类,从而降低了客户端代码与具体产品类之间的耦合度。
  • 易于扩展:添加新的具体产品类时,只需要创建对应的具体产品类和具体工厂类,不需要修改已有的代码,符合单一职责原则。

缺点:

  • 类的数量增加:引入了抽象工厂接口和多个具体工厂类,导致系统中类的数量增加,增加了系统的复杂度。
  • 增加了系统的复杂度:具体工厂类的增加会增加系统的复杂度,不利于理解和维护。
  • 可能引入不必要的抽象层次:如果系统中只有一个具体产品类,引入工厂方法模式可能会引入不必要的抽象层次,增加了代码的复杂度。

 

5.抽象工厂模式

抽象工厂模式是一种创建型设计模式,它提供了一种将多个相关或相互依赖的对象组合成一个“家族”并且能够相互替换的方式。该模式通过提供一个接口来创建一系列相关或相互依赖的对象,而不需要指定它们具体的类。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

比如A工厂生产运动风的衣物(衣服,裤子),B工厂生产商务风的衣物(衣服,裤子)。

以下是抽象工厂模式中涉及的主要角色:

  • 抽象工厂(Abstract Factory):声明创建抽象产品对象的方法,通常是一个接口,包含多个创建产品的方法,每个方法用于创建一个具体产品对象的实例。
  • 具体工厂(Concrete Factory):实现抽象工厂接口,负责创建一组具体产品对象,这些产品对象构成了一个产品族,每个具体工厂对应一个产品族。
  • 抽象产品(Abstract Product):声明具体产品对象的接口,通常是一个接口或抽象类,定义了产品对象的通用方法。
  • 具体产品(Concrete Product):实现抽象产品接口,是具体工厂所创建的产品对象的实例。

抽象工厂模式的核心思想是,将具体产品的创建延迟到具体工厂中,每个具体工厂负责创建一组相关的产品对象,这样可以保证一组产品对象之间的兼容性,并且客户端代码与具体产品类之间的耦合度较低。

代码:

package com.dreams.pattern.abstractFactory;

// 抽象产品A接口
interface AbstractProductA {
    void methodA();
}

// 具体产品A1
class ConcreteProductA1 implements AbstractProductA {
    @Override
    public void methodA() {
        System.out.println("具体产品A1的方法A");
    }
}

// 具体产品A2
class ConcreteProductA2 implements AbstractProductA {
    @Override
    public void methodA() {
        System.out.println("具体产品A2的方法A");
    }
}

// 抽象产品B接口
interface AbstractProductB {
    void methodB();
}

// 具体产品B1
class ConcreteProductB1 implements AbstractProductB {
    @Override
    public void methodB() {
        System.out.println("具体产品B1的方法B");
    }
}

// 具体产品B2
class ConcreteProductB2 implements AbstractProductB {
    @Override
    public void methodB() {
        System.out.println("具体产品B2的方法B");
    }
}

// 抽象工厂接口
interface AbstractFactory {
    AbstractProductA createProductA();
    AbstractProductB createProductB();
}

// 具体工厂1,用于创建产品族1
class ConcreteFactory1 implements AbstractFactory {
    @Override
    public AbstractProductA createProductA() {
        return new ConcreteProductA1();
    }

    @Override
    public AbstractProductB createProductB() {
        return new ConcreteProductB1();
    }
}

// 具体工厂2,用于创建产品族2
class ConcreteFactory2 implements AbstractFactory {
    @Override
    public AbstractProductA createProductA() {
        return new ConcreteProductA2();
    }

    @Override
    public AbstractProductB createProductB() {
        return new ConcreteProductB2();
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建具体工厂1
        AbstractFactory factory1 = new ConcreteFactory1();
        // 创建产品族1
        AbstractProductA productA1 = factory1.createProductA();
        AbstractProductB productB1 = factory1.createProductB();
        productA1.methodA();
        productB1.methodB();

        // 创建具体工厂2
        AbstractFactory factory2 = new ConcreteFactory2();
        // 创建产品族2
        AbstractProductA productA2 = factory2.createProductA();
        AbstractProductB productB2 = factory2.createProductB();
        productA2.methodA();
        productB2.methodB();
    }
}

抽象产品A和抽象产品B分别定义了产品族中的产品类型,并提供了各自的方法。具体产品A1、A2、B1和B2分别实现了抽象产品A和抽象产品B的接口。抽象工厂接口定义了创建产品族中的产品对象的方法,具体工厂1和具体工厂2分别实现了抽象工厂接口,并分别负责创建产品族1和产品族2的产品对象。最后,在客户端代码中,通过具体工厂来创建对应的产品对象,实现了产品对象的创建和使用。

优缺点:

优点:

  • 提供了产品族的统一接口:抽象工厂模式可以为一组相关或相互依赖的产品对象提供一个统一的接口,客户端可以通过抽象接口来操作不同的具体产品对象,而无需关注其具体实现。
  • 实现了产品族的变化:由于具体工厂负责创建一组产品对象,因此可以轻松地替换整个产品族,而不影响客户端代码。这使得系统更容易进行扩展和维护。
  • 降低了客户端与具体产品类之间的耦合度:客户端只需要知道抽象工厂和抽象产品的接口,而不需要了解具体的产品实现细节,从而降低了系统的耦合度。
  • 符合开闭原则:抽象工厂模式通过添加新的具体工厂和具体产品类来扩展系统,而不需要修改现有的代码,符合开闭原则。

缺点:

  • 不易扩展新产品:如果需要向系统中添加新的产品对象,除了需要创建新的具体产品类外,还需要修改抽象工厂接口及其所有的具体工厂实现,这会增加系统的复杂度。
  • 产品族内的增加新产品困难:抽象工厂模式将一组产品对象组合成一个产品族,如果需要在产品族内增加新的产品,所有的具体工厂类都需要进行修改,这可能会影响到整个系统的稳定性。
  • 容易造成类爆炸:当系统中存在多个产品族和多个产品等级结构时,抽象工厂模式的类数量会呈指数级增长,导致类的数量急剧增加,容易造成类爆炸问题。

 

6.原型模式

原型模式是一种创建型设计模式,其核心思想是通过复制现有对象来创建新对象,而不是通过实例化类来创建对象。在原型模式中,通过克隆已有对象来创建新对象,从而避免了使用new关键字进行实例化,这样可以提高系统的性能并简化对象创建过程。

原型模式包含如下角色:

  • 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
  • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

原型模式的克隆操作可以分为浅克隆和深克隆两种:

  • 浅克隆:仅复制对象本身,而不复制对象引用的成员对象。被复制的对象和原始对象共享相同的成员对象。对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
  • 深克隆:不仅复制对象本身,还会复制对象引用的成员对象。被复制的对象拥有一份独立的成员对象副本,修改其中一个对象的成员对象不会影响到另一个对象。

浅克隆实现

package com.dreams.pattern.prototype.simple;

import java.util.ArrayList;
import java.util.List;

// 定义原型接口
interface Prototype {
    Prototype clone();
}

// 具体原型类
class ConcretePrototype implements Prototype,Cloneable {
    private int value;
    private List<String> list;

    public ConcretePrototype(int value, List<String> list) {
        this.value = value;
        this.list = list;
    }

    @Override
    public Prototype clone() {
        // 使用默认的浅拷贝实现
        try {
            return (Prototype) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e);
        }
        return null;

    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建具体原型对象
        List<String> originalList = new ArrayList<>();
        originalList.add("A");
        originalList.add("B");
        ConcretePrototype prototype = new ConcretePrototype(10, originalList);
        System.out.println("Original Object: " + prototype.getValue() + ", " + prototype.getList());

        // 克隆原型对象
        ConcretePrototype clonedPrototype = (ConcretePrototype) prototype.clone();
        System.out.println("Cloned Object: " + clonedPrototype.getValue() + ", " + clonedPrototype.getList());

        // 修改克隆对象的值
        clonedPrototype.setValue(20);
        clonedPrototype.getList().add("C");

        // 输出克隆对象的值,注意原型对象的值应受到影响
        System.out.println("Cloned Object after modification: " + clonedPrototype.getValue() + ", " + clonedPrototype.getList());
        System.out.println("Original Object after modification: " + prototype.getValue() + ", " + prototype.getList());
    }
}

 

深克隆实现

package com.dreams.pattern.prototype.deep;

import java.util.ArrayList;
import java.util.List;

// 定义原型接口
interface Prototype {
    Prototype clone();
}

// 具体原型类
class ConcretePrototype implements Prototype {
    private int value;
    private List<String> list;

    public ConcretePrototype(int value, List<String> list) {
        this.value = value;
        this.list = list;
    }

    @Override
    public Prototype clone() {
        // 创建一个新对象
        List<String> newList = new ArrayList<>(this.list);
        return new ConcretePrototype(this.value, newList);
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建具体原型对象
        List<String> originalList = new ArrayList<>();
        originalList.add("A");
        originalList.add("B");
        ConcretePrototype prototype = new ConcretePrototype(10, originalList);
        System.out.println("Original Object: " + prototype.getValue() + ", " + prototype.getList());

        // 克隆原型对象
        ConcretePrototype clonedPrototype = (ConcretePrototype) prototype.clone();
        System.out.println("Cloned Object: " + clonedPrototype.getValue() + ", " + clonedPrototype.getList());

        // 修改克隆对象的值
        clonedPrototype.setValue(20);
        clonedPrototype.getList().add("C");

        // 输出克隆对象的值,注意原型对象的值不应受到影响
        System.out.println("Cloned Object after modification: " + clonedPrototype.getValue() + ", " + clonedPrototype.getList());
        System.out.println("Original Object after modification: " + prototype.getValue() + ", " + prototype.getList());
    }
}

可以看到不仅复制对象本身,还会复制对象引用的成员对象。所以修改克隆对象的引用类型的值,原型对象的值不应受到影响。

 

7.建造者模式

建造者模式是一种创建型设计模式,允许按照步骤构建复杂对象。与直接在客户端代码中实例化对象并设置其属性不同,建造者模式将对象的构建过程分解成多个步骤,以便更灵活地创建对象。

  • 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
  • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
  • 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

建造者(Builder)模式包含角色:

  • 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
  • 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法(抽象工厂模式、工厂方法模式等都行)。在构造过程完成后,提供产品的实例。
  • 产品类(Product):要创建的复杂对象。
  • 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
package com.dreams.pattern.builder;

// 产品类
class Product {
    private String part1;
    private String part2;
    private String part3;

    public void setPart1(String part1) {
        this.part1 = part1;
    }

    public void setPart2(String part2) {
        this.part2 = part2;
    }

    public void setPart3(String part3) {
        this.part3 = part3;
    }

    public void show() {
        System.out.println("Part 1: " + part1);
        System.out.println("Part 2: " + part2);
        System.out.println("Part 3: " + part3);
    }
}

// 抽象建造者
interface Builder {
    void buildPart1();
    void buildPart2();
    void buildPart3();
    Product getResult();
}

// 具体建造者
class ConcreteBuilder implements Builder {
    private Product product;

    public ConcreteBuilder() {
        this.product = new Product();
    }

    @Override
    public void buildPart1() {
        product.setPart1("Part 1");
    }

    @Override
    public void buildPart2() {
        product.setPart2("Part 2");
    }

    @Override
    public void buildPart3() {
        product.setPart3("Part 3");
    }

    @Override
    public Product getResult() {
        return product;
    }
}

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

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

    public Product construct() {
        builder.buildPart1();
        builder.buildPart2();
        builder.buildPart3();
        return builder.getResult();
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Builder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        Product product = director.construct();
        product.show();
    }
}

在这个示例中,有一个产品类 Product,它包含了三个部分。然后定义了一个抽象建造者接口 Builder,它规定了构建产品的步骤。具体的建造者类 ConcreteBuilder 实现了 Builder 接口,负责实际构建产品的过程。指挥者 Director 控制建造的流程,调用具体建造者的方法来构建产品。最后,在客户端代码中,创建一个具体建造者对象和一个指挥者对象,通过指挥者构建产品,并展示最终的产品。

链式调用允许在一个方法调用后直接调用另一个方法,而不必每次调用都引用同一个对象。这样可以将步骤交给客户选择。

package com.dreams.pattern.builder.demo;

public class Product {
    private String property1;
    private String property2;
    private String property3;

    public Product(ProductBuilder builder) {
        this.property1 = builder.property1;
        this.property2 = builder.property2;
        this.property3 = builder.property3;
    }

    // Getters and setters...
}

class ProductBuilder {
    // 可选参数
    public String property1;
    public String property2;
    public String property3;

    public ProductBuilder setProperty1(String property1) {
        this.property1 = property1;
        return this;
    }

    public ProductBuilder setProperty2(String property2) {
        this.property2 = property2;
        return this;
    }

    public ProductBuilder setProperty3(String property3) {
        this.property3 = property3;
        return this;
    }

    public Product build() {
        return new Product(this);
    }
}


class Client{
    public static void main(String[] args) {
        // 在客户端代码中使用链式调用来构建产品对象
        Product product = new ProductBuilder()
                .setProperty1("Value1")
                .setProperty2("Value2")
                .setProperty3("Value3")
                .build();
    }
}

 

优点:

  • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
  • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。

缺点:

造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

 

使用场景:

建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。

  • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
  • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

 

参考

黑马设计模式

大话设计模式

图解设计模式

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇