【设计模式】 - 创建型模式(Creational Patterns)

创建型模式是设计模式中的一类,它们关注如何更加灵活、高效地创建对象

单例模式

一个单一的类,负责创建自己的对象,同时确保系统中只有单个对象被创建。

单例模式特点:

  1. 某个类只能有一个实例;(构造器私有)
  2. 它必须自行创建这个实例;(自己编写实例化逻辑)
  3. 它必须自行向整个系统提供这个实例;(对外提供实例化方法)
    在这里插入图片描述

简单实例

public class User {

    // 对外提供的对象 (内存可见性)
    private volatile static User instance;

    // 饿汉模式
    // private final static User instance1 = new User();

    // 构造器私有
    private User() {
    }

    // 对外提供获取对象的接口
    public static User getInstance() {
        if (instance == null) {
            //双重检查锁 + 内存可见性  --粒度小,多线程二次检查快速释放
            synchronized (User.class) {
                if (instance == null) {
                    User user = new User();
                    instance = user;
                }
            }
        }
        return instance;
    }
}

应用场景

  • 多线程中的线程池
  • 数据库的连接池
  • 系统环境信息
  • 上下文(ServletContext)



原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。
本体给外部提供一个克隆体进行使用。


浅拷贝&深拷贝

浅拷贝

浅克隆是指只复制对象的基本数据类型和引用类型的地址,而不复制引用类型所指向的对象。这意味着,如果原型对象中的引用类型被修改了,那么克隆出来的对象中的相应引用类型也会被修改。

简单实例
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User implements Cloneable {

    private int id;
    private String name;
    private List<String> list;


    @Override
    public User clone() throws CloneNotSupportedException {
        return (User) super.clone();
    }
}

// 启动测
public class PrototypeStart {
    public static void main(String[] args) throws Exception {

        List<String> list = Stream.of("1", "2", "3").collect(Collectors.toList());
        User user = new User(18, "张三", list);

        // 改变克隆对象的list属性值
        User cloneUser = user.clone();
        cloneUser.getList().add("4");

        System.out.println("user list: " + user.getList());
        System.out.println("cloneUser list: " + cloneUser.getList());
    }
}

结果展示:

在这里插入图片描述

在上面的示例代码中,我们定义了一个 User 类,它包含一个基本数据类型 id、一个字符串类型 name 和一个引用类型 list。

在 Main 类中,我们创建了一个 User 对象,并通过 clone() 方法克隆了一个新的对象。然后,我们修改了克隆出来的对象中的引用类型 list,并打印出了原型对象和克隆出来的对象中的 list,可以看到它们都被修改了。

因此,我们需要注意在使用浅克隆时,如果原型对象中的引用类型被修改了,那么克隆出来的对象中的相应引用类型也会被修改。

如果需要避免这种情况,可以使用深克隆。

深拷贝

深克隆是指不仅复制对象的基本数据类型和引用类型的地址,还要复制引用类型所指向的对象。这意味着,如果原型对象中的引用类型被修改了,克隆出来的对象中的相应引用类型不会受到影响。

实现深克隆有两种方式:递归克隆 、 序列和化反序列化。

递归实现

通过递归实现深度遍历对象的所有引用类型,复制它们的值,并将它们设置到新的对象中,可以实现深克隆。这种方式比较复杂,但不需要被克隆的对象实现 Serializable 接口。

在浅拷贝的基础上进行修改

 @Override
 public User clone() throws CloneNotSupportedException {
     User clone = (User) super.clone();
     clone.list = new ArrayList<>(list);
     return clone;
 }

//启动测试
public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
    
  		List<String> list = Stream.of("1", "2", "3").collect(Collectors.toList());
        User prototype = new User (1, "Prototype", list);
        
        User clone = prototype.clone();
        System.out.println(clone.getId()); 		// 1
        System.out.println(clone.getName()); 	// "Prototype"
        System.out.println(clone.getList()); 	// ["A", "B", "C"]
        
        clone.getList().add("D");
        System.out.println(prototype.getList()); // ["A", "B", "C"]
        System.out.println(clone.getList()); 	// ["A", "B", "C", "D"]
    }
}

我们修改了克隆出来的对象中的引用类型 list,并打印出了原型对象和克隆出来的对象中的 list,可以看到它们没有被修改。

序列化和反序列化实现

通过将对象序列化成字节流,再通过反序列化得到一个新的对象,可以实现深克隆。这种方式要求被克隆的对象及其引用类型都必须实现 Serializable 接口。

import java.io.*;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private int id;
    private String name;
    private List<String> list;


    public User deepClone() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(this);

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (User) ois.readObject();
    }
}

PS:Java中有一些第三方库可以实现深克隆,例如Apache Commons库中的SerializationUtils.clone()方法、Spring框架中的SerializationUtils.clone()方法、Google的Guava库中的Object.clone(方法等。这些库通常都提供了对序列化和反序列化的支持,可以方便地实现深克隆。

应用场景

  • 资源优化
  • 性能和安全要求
  • 一个对象多个修改者的场景。
  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时可以考虑使用原型模式拷贝多个对象供调用者使用。


工厂模式

工厂模式(Factory Pattern)提供了一种创建对象的最佳方式。我们不必关心对象的创建细节,只需要根据不同情况获取不同产品即可。

简单工厂

三个角色
  • Product:抽象的产品
  • Factory:工厂
  • ConcreteProduct:产品的具体实现
    在这里插入图片描述
简单实例
// 抽象汽车
public abstract class Car {

    String engine;
    
    public void run() { }
    
}

// 具体实现1
public class Mercedes extends Car {

    public Mercedes() {
        this.engine = "创新梅赛德斯v8发动机";
    }

    @Override
    public void run() {
        System.out.println("梅赛德斯跑起来啦!使用" + this.engine);
    }

}

// 具体实现2
public class Porsche extends Car {

    public Porsche() {
        this.engine = "创新版保时捷v8发动机";
    }

    @Override
    public void run() {
        System.out.println("保时捷跑起来了!使用" + this.engine);
    }
}

// 定义工厂
public class SimpleCarFactory {

    public static Car getInstance(String carName) {
        if (carName.equals("Mercedes")) {
            return new Mercedes();
        } else if (carName.equals("Porsche")) {
            return new Porsche();
        }
        return null;
    }
}

违背开闭原则,新增产品就要修改工厂

工厂方法

四个角色
  • Product:抽象产品
  • ConcreteProduct:具体产品
  • Factory:抽象工厂
  • ConcreteFactory:具体工厂
    在这里插入图片描述
简单实例
// 抽象工厂
public abstract class AbstractCarFactory {
    public abstract Car getInstance();
}

// 抽象工厂实现1 -- 梅赛德斯
public class MercedesCarFactory extends AbstractCarFactory {
    @Override
    public Car getInstance() {
        return new Mercedes();
    }
}

// 抽象工厂实现2 -- 保时捷
public class PorscheCarFactory extends AbstractCarFactory {
    @Override
    public Car getInstance() {
        return new Porsche();
    }
}

// 实际使用 父类类型子类对象
public class FactoryStart {
    public static void main(String[] args) {
        // 工厂方法
        AbstractCarFactory factory = new MercedesCarFactory();
        Car car = factory.getInstance();
        car.run();

        factory = new PorscheCarFactory();
        Car car1 = factory.getInstance();
        car1.run();
    }
}

系统复杂度增加,产品单一
新增相应的产品的同时需要新增生产对象的工厂

抽象工厂

要求不仅要生产车,还需要临时生产口罩。
对工厂进一步抽象处理,方便拓展。
在这里插入图片描述

简单实例
// 顶层的抽象工厂
public abstract class AbstractFactory {

    public abstract Mask getMaskInstance();

    public abstract Car getCarInstance();
}

// 口罩的抽象工厂 继承顶层的抽象工厂 
public abstract class AbstractMaskFactory extends AbstractFactory {

    // 口罩工厂只负责口罩相关
    @Override
    public abstract Mask getMaskInstance();
	
    @Override
    public Car getCarInstance() {
        return null;
    }
}

// N95口罩实现
public class N95AbstractFactory extends AbstractMaskFactory {

    @Override
    public Mask getMaskInstance() {
        return new N95Mask();
    }
}

// 车的抽象工厂 继承顶层抽象工厂  
public abstract class AbstractCarFactory extends AbstractFactory {

    @Override
    public Mask getMaskInstance() {
        return null;
    }

    // 车工厂只实现车的相关
    @Override
    public abstract Car getCarInstance();
}

// 保时捷的实现
public class PorscheCarFactory extends AbstractCarFactory {
    @Override
    public Car getCarInstance() {
        return new Porsche();
    }
}

优点:新增需求后,只对类进行拓展即可。

应用场景

  • NumberFormat、SimpleDateFormat
  • LoggerFactory
  • SqlSessionFactory:MyBatis
  • BeanFactory:Spring


建造者模式

允许你创建复杂对象的不同表示,同时避免了构造函数的过度复杂化。它将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。

角色

  • 产品(Product):要创建的复杂对象,包含多个部分
  • 抽象建造者(Builder):定义创建产品的接口,包含多个建造方法
  • 具体建造者(ConcreteBuilder):实现抽象建造者接口,用于创建产品的具体部件
  • 指挥者(Director):调用建造者的建造方法来创建产品,负责控制建造过程

优点包括:

  • 可以分步创建复杂对象,使得创建过程更加灵活
  • 可以隔离复杂对象的创建和使用,使得系统更加易于维护
  • 可以控制复杂对象的创建过程,使得对象创建更加精细化

在这里插入图片描述

简单实例

// 产品 
@Getter
@ToString
public class Phone {

    String cpu;
    String men;
    String disk;
    String camera;

}

// 抽象建造者
public abstract class AbstractPhoneBuilder {

    Phone phone;

    abstract AbstractPhoneBuilder cpu(String cpu);
    abstract AbstractPhoneBuilder mem(String mem);
    abstract AbstractPhoneBuilder disk(String disk);
    abstract AbstractPhoneBuilder camera(String camera);

    abstract Phone getProduct();
}

// 实际建造者
public class PhoneBuilder extends AbstractPhoneBuilder {

    // 构造器初始化
    public PhoneBuilder() {
        phone = new Phone();
    }

    @Override
    PhoneBuilder cpu(String cpu) {
        this.phone.cpu = cpu;
        return this;
    }

    @Override
    PhoneBuilder mem(String mem) {
        this.phone.men = mem;
        return this;
    }

    @Override
    PhoneBuilder disk(String disk) {
        this.phone.disk = disk;
        return this;
    }

    @Override
    PhoneBuilder camera(String camera) {
        this.phone.camera = camera;
        return this;
    }

    @Override
    Phone getProduct() {
        return phone;
    }
}

// 测试使用
 Phone phone = new PhoneBuilder().cpu("A16").disk("1T").mem("2048M").camera("一亿").getProduct();
 System.out.println(phone.toString());  //Phone(cpu=A16, men=2048M, disk=1T, camera=一亿)

或者可以使用lombok注解@Builder

@Builder
@ToString
public class LombokPhone {
    String cpu;
    String men;
    String disk;
    String camera;
}

// 测试使用
LombokPhone lombokPhone = LombokPhone.builder().cpu("A16").disk("1T").men("2048M").camera("一亿").build();
System.out.println(lombokPhone.toString()); //LombokPhone(cpu=A16, men=2048M, disk=1T, camera=一亿)

应用场景

  • StringBuilder:append()
  • Swagger-ApiBuilder

小总结

总的来说,创建型模式关注如何更加灵活、高效地创建对象!

  1. 简单工厂模式(Simple Factory Pattern):通过一个工厂类来创建对象,将对象的创建与使用分离开来。

  2. 工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定实例化哪一个类。将对象的创建延迟到子类中进行。

  3. 抽象工厂模式(Abstract Factory Pattern):提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。

  4. 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。

  5. 原型模式(Prototype Pattern):通过复制现有对象来创建新对象,避免了大量的对象创建过程。

  6. 建造者模式(Builder Pattern):将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。

这些创建型模式在不同的场景下都有各自的优缺点,可以根据具体的需求选择适合的模式来进行开发。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

总在寒冷清秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值