创建型设计模式
创建型设计模式的核心就是给我们提供了一系列全新的创建对象的方式方法
创建型设计模式
创建型设计模式:核心目的就是给我们提供了一系列全新的创建对象的方式方法
一、单例设计模式
1、介绍
单例设计模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点以供其他对象使用。单例模式常用于需要在整个系统中共享一个共享资源或管理共享状态的情况下。
2、核心思想
单例设计模式的核心思想是确保一个类只有一个实例,并提供一个全局访问点来获取该实例。通过使用单例模式,可以控制对象的创建过程,并确保在整个系统中只存在一个实例,提供对该实例的全局访问。
3、组成和基本流程
单例设计模式通常由以下组成部分构成:
- 单例类(Singleton Class):表示只能有一个实例的类。该类负责创建自己的唯一实例,并提供对该实例的全局访问。
- 全局访问点(Global Access Point):提供对单例类实例的全局访问。其他对象可以通过该访问点获取单例类的实例。
单例设计模式的基本流程如下:
- 将类的构造函数设置为私有,以防止其他对象直接实例化该类。
- 在类内部创建一个私有静态成员变量,用于保存类的唯一实例。
- 提供一个公共的静态方法作为全局访问点,用于获取类的实例。在该方法内部,如果实例不存在,则创建一个新实例并返回;如果实例已存在,则直接返回现有实例。
4、使用场景
单例设计模式适用于以下场景:
- 当需要在整个系统中共享一个共享资源或管理共享状态时 ➡️ Spring容器的单例
- 当某个对象只能有一个实例,而且需要对该实例进行严格的控制和管理时 ➡️ JDK中的Runtime类,该类封装了运行时的环境
- 当需要对资源进行集中管理,以便于协调和控制时 ➡️ Mybaits中的org.apache.ibatis.io.VFS使用到了单例模式
例如:
- jdk中有一个类的实现是一个标准单例模式->Runtime类,该类封装了运行时的环境。每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。 一般不能实例化一个Runtime对象,应用程序也不能创建自己的 Runtime 类实例,但可以通过 getRuntime 方法获取当前Runtime运行时对象的引用。
- Mybaits中的org.apache.ibatis.io.VFS使用到了单例模式。VFS就是Virtual File System的意思,mybatis通过VFS来查找指定路径下的资源。查看VFS以及它的实现类,不难发现,VFS的角色就是对更“底层”的查找指定资源的方法的封装,将复杂的 “底层”操作封装到易于使用的高层模块中,方便使用者使用。
5、具体案例
单例设计模式-饿汉式:
饿汉式是一种简单直接的单例模式实现方式。**在类加载的时候就创建了唯一的实例对象,并在整个生命周期中保持不变。**因此,它也被称为“急切”创建实例的方式。
public class HgySingleton {
private static final HgySingleton INSTANCE = new HgySingleton();
private HgySingleton() {
}
public static HgySingleton getInstance() {
return INSTANCE;
}
}
单例设计模式-懒汉式:
懒汉式是一种延迟实例化的方式,在第一次访问获取实例的时候才会创建对象。这种方式在实例创建之前没有额外的开销,但需要考虑多线程环境下的线程安全问题。
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
单例设计模式-双重检查锁:
双重检查锁是对懒汉式的改进,通过在获取实例时进行双重检查,确保只在实例未创建时才进行同步操作。这样在多线程环境下可以保持高性能并且线程安全。
public class DclSingleton {
private static volatile DclSingleton INSTANCE;
private DclSingleton() {
}
public static DclSingleton getInstance() {
if (INSTANCE == null) {
synchronized (DclSingleton.class) {
if (INSTANCE == null) {
INSTANCE = new DclSingleton();
}
}
}
return INSTANCE;
}
}
单例设计模式-静态内部类:
静态内部类方式是一种延迟加载实例的方式,它利用了Java类加载机制中的静态内部类不会在外部类加载时被加载的特性。当第一次获取实例时,静态内部类才会被加载并创建实例。
public class InnerSingleton {
private InnerSingleton() {
}
public static InnerSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final InnerSingleton INSTANCE = new InnerSingleton();
}
}
单例设计模式-枚举类:
枚举类方式是Java中最简洁、高效且线程安全的单例模式实现方式。枚举类的实例是在枚举常量被第一次访问时创建的,而且它天生就是线程安全的。
public class EnumSingleton {
private EnumSingleton() {
// 私有构造函数
}
public static EnumSingleton getInstance() {
return SingletonEnum.INSTANCE.getInstance();
}
private static enum SingletonEnum {
INSTANCE;
private EnumSingleton instant;
// 在枚举常量中初始化单例实例
private SingletonEnum() {
instant = new EnumSingleton();
}
public EnumSingleton getInstance() {
return instant;
}
}
}
枚举可以抵挡常规的反射攻击是因为Java语言规范对枚举类进行了特殊处理。当使用反射尝试访问枚举类的私有构造函数时,会抛出IllegalAccessException
异常,阻止了对枚举类进行实例化的尝试。
这种反射抵御是通过Java语言规范的设计来实现的。在枚举类中,构造函数被默认为私有的,不允许外部代码直接访问。当使用反射时,如果尝试通过Constructor.newInstance()
方法来调用枚举类的私有构造函数,Java会在内部检查是否为枚举类,并抛出异常。
此外,枚举常量在枚举类被加载时就被实例化,并且是在枚举类初始化阶段完成的。这意味着在第一次访问枚举类时,所有的枚举常量都会被创建,而且无法再通过反射来创建新的枚举常量实例。
综上所述,由于枚举类的特殊处理和枚举常量的提前实例化,枚举可以有效地抵挡常规的反射攻击,确保枚举类的单例特性和唯一性。这也是为什么使用枚举实现单例模式是一种安全可靠的方式。然而,需要注意的是,反射仍然可以访问枚举类的其他成员变量和方法,只是无法通过反射创建新的枚举常量实例。
单例设计模式-反射和序列化:
使用常规的单例模式实现可能会受到反射和序列化的影响而产生多个实例。为了防止这种情况,需要在单例类中做特殊处理,确保在使用反射和序列化时依然保持单例特性。
public class ReflectSerializableSingleton implements Serializable {
private static volatile ReflectSerializableSingleton INSTANCE;
private ReflectSerializableSingleton() {
if (INSTANCE != null) {
throw new RuntimeException("实例:【" + this.getClass().getName() + "】已经存在,该实例只允许实例化一次");
}
}
public static ReflectSerializableSingleton getInstance() {
if (INSTANCE == null) {
synchronized (ReflectSerializableSingleton.class) {
if (INSTANCE == null) {
INSTANCE = new ReflectSerializableSingleton();
}
}
}
return INSTANCE;
}
// readResolve()方法可以用于替换从流中读取的对象,在进行反序列化时,会尝试执行readResolve方法,并将返回值作为反序列化的结果,而不会克隆一个新的实例,保证jvm中仅仅有一个实例存在
public Object readResolve(){
return singleton;
}
}
6、总结
单例设计模式通过确保一个类只有一个实例,提供了全局访问点来获取该实例。它在需要共享资源或管理共享状态的情况下非常有用。使用单例模式可以控制对象的创建和访问,提供对实例的全局访问,并确保在整个系统中只有一个实例存在。
单例设计模式的优点和缺点:
优点:
- 提供了对唯一实例的全局访问点,方便其他对象使用。
- 控制对象的创建过程,确保在整个系统中只存在一个实例。
- 提供了对共享资源和共享状态的集中管理。
缺点:
- 可能引入全局状态和全局访问点,增加了对象之间的耦合性。
- 可能导致单例类的职责过重,违反了单一职责原则。
总体而言,单例设计模式在需要全局访问点和唯一实例的情况下非常有用。它提供了一种简单而有效的方式来管理共享资源和共享状态。然而,在使用单例模式时,需要权衡好全局状态的管理和对象职责的合理划分,以确保系统的可维护性和灵活性。
单例存在的其它问题
单例存在的其它问题:
无法支持面向对象编程:单例模式的构造方法被私有化,这导致无法将单例类作为其他类的父类,限制了继承和多态的特性。这意味着在面对未来的需求变化时,扩展性受到限制。如果需要创建类似但略有差异的单例,就需要重新创建一个相似且大部分功能相同的单例类,造成代码冗余。
极难的横向扩展:单例模式只允许存在一个实例对象,如果未来需要创建更多实例以满足不同需求,就必须修改源代码。这违反了开闭原则,增加新的实例需要对现有代码进行修改,导致扩展困难。例如,在数据库连接池中,可能从一个连接变成需要多个连接。
不同作用范围的单例:
- 线程级别的单例:每个线程都拥有自己的单例实例,线程之间互不干扰。
- 容器范围的单例:在容器中管理单例实例,容器可以管理多个单例对象并控制其生命周期。
- 日志中的多例:在日志记录中,可能需要根据不同的上下文创建多个实例来记录不同的日志信息。
请注意,尽管单例模式存在一些缺点,但在某些情况下仍然是有用的设计模式。在使用单例模式时,需要仔细考虑其适用性和潜在的问题,并根据具体情况做出权衡决策。
二、工厂设计模式
当没有使用工厂设计模式的情况下,代码可能会像下面这样实现:
public class Resource {
private String url;
public Resource(String url) {
this.url = url;
}
}
public class ResourceLoader {
private Resource load(String url) {
// 根据前缀匹配
String prefix = getPrefix(url);
if (prefix.equals("http")) {
// 很复杂的操作,可能此处会执行很久
return new Resource(url);
} else if (prefix.equals("file")) {
// 很复杂的操作,可能此处会执行很久
return new Resource(url);
} else if (prefix.equals("classpath")) {
// 很复杂的操作,可能此处会执行很久
return new Resource(url);
} else {
return new Resource("default");
}
}
private String getPrefix(String url) {
if (url == null || "".equals(url) || url.contains(":")) {
throw new ResourceLoadException("url不合法");
}
String[] split = url.split(":");
return split[0];
}
}
代码详细解析:
上述代码包含两个类:Resource
和 ResourceLoader
。
Resource
类表示资源对象,具有一个私有的url
字段和一个公共的构造函数。它通过构造函数接收一个url
参数,并将其赋值给url
字段。ResourceLoader
类是一个资源加载器,它有一个私有的load
方法用于加载资源。该方法接收一个url
参数,根据其前缀进行匹配。根据前缀的不同,它执行不同的操作来创建并返回一个Resource
对象。在
load
方法中,它通过调用私有的getPrefix
方法来获取url
的前缀。然后,使用条件语句判断前缀是什么,并在每个分支中执行复杂的操作(可能需要执行很久)来创建一个对应的Resource
对象。如果前缀不匹配任何条件,则创建一个默认的Resource
对象。getPrefix
方法用于从url
中提取前缀。它首先检查url
是否为空、空字符串或包含冒号。如果是,则抛出一个ResourceLoadException
异常,表示url
不合法。否则,它通过将url
使用冒号进行分割,并返回分割后的第一个元素作为前缀。
总结:以上代码展示了一个简单的资源加载器,根据给定的 url
和其前缀,创建并返回对应的 Resource
对象。它通过条件语句进行判断和创建,没有使用工厂设计模式。然而,这种实现方式存在代码冗余和可扩展性差的问题。工厂设计模式可以用来改善这些问题。
在这个没有使用工厂设计模式的例子中,ResourceLoader
类负责根据 URL 的前缀来加载资源。根据不同的前缀,它执行不同的操作来创建 Resource
对象。这导致了以下问题:
- 代码冗余:在每个分支中,都有相似的复杂操作用于创建
Resource
对象,这造成了代码的重复。 - 可扩展性差:当需要添加新的资源类型时,需要修改
ResourceLoader
类的代码,并增加对应的分支。这违反了开闭原则,使代码难以扩展和维护。 - 低内聚性:
ResourceLoader
类的职责既包括根据前缀选择创建资源对象的逻辑,又包括资源对象的创建过程,导致职责不够单一。
没有使用工厂设计模式的代码实现不够灵活,代码复用性较低,并且随着业务需求的变化,代码的维护和扩展会变得困难。相比之下,使用工厂设计模式可以将对象的创建逻辑封装到工厂类中,提高了代码的可扩展性和可维护性,同时使得代码更加清晰和易于理解。
1、简单工厂
1.1 介绍
简单工厂设计模式(Simple Factory Pattern)是一种创建型设计模式,它提供了一种统一的方式来创建对象,而无需直接实例化具体的产品类。简单工厂模式通过将对象的创建逻辑封装在一个工厂类中,根据不同的参数返回相应的对象实例。
1.2 核心思想
简单工厂设计模式的核心思想是通过一个工厂类来封装对象的创建过程。客户端通过传递不同的参数给工厂类,工厂类根据参数的不同来创建对应的具体产品对象。这样可以将对象的创建和使用分离,降低了客户端代码的耦合性。
1.3 组成
简单工厂设计模式由以下组成部分构成:
- 工厂类(Factory Class):负责创建具体产品对象的工厂类。它包含一个静态方法,根据传入的参数返回相应的产品对象。
1.4 基本流程
简单工厂设计模式的基本流程如下:
- 定义一个工厂类,包含一个静态方法用于创建产品对象。
- 在工厂方法中根据不同的参数,实例化相应的具体产品对象。
- 客户端通过调用工厂方法并传递参数,获取所需的产品对象。
1.5 使用场景
简单工厂设计模式适用于以下场景:
- 当需要根据不同参数创建不同的对象时。
- 当需要将对象的创建和使用分离,降低客户端代码的耦合性时。
1.6 具体案例
在下列案例中,ResourceLoader
类是一个资源加载器,负责根据URL加载对应的资源。为了将资源的创建过程与资源加载器解耦,使用了简单工厂模式。ResourceLoadFactory
类是工厂类,根据URL的前缀来创建不同类型的资源对象。在ResourceLoader
类中,通过调用工厂类的静态方法create()
来获取相应的资源实例,而不需要直接实例化具体的资源类。
public class ResourceLoader {
private Resource load(String url) {
//1.根据前缀匹配
String prefix = getPrefix(url);
//使用简单工厂模式,不将创建资源的过程耦合到此处代码
return ResourceLoadFactory.create(prefix, url);
}
private String getPrefix(String url) {
if (url == null || "".equals(url) || url.contains(":")) {
throw new ResourceLoadException("url不合法");
}
String[] split = url.split(":");
return split[0];
}
}
public class ResourceLoadFactory {
public static Resource create(String type, String url) {
if (type.equals("http")) {
//很复杂的操作,可能此处会执行很久
return new Resource(url);
} else if (type.equals("file")) {
//很复杂的操作,可能此处会执行很久
return new Resource(url);
} else if (type.equals("classpath")) {
//很复杂的操作,可能此处会执行很久
return new Resource(url);
} else {
return new Resource("default");
}
}
}
1.7 总结
简单工厂设计模式通过一个工厂类来封装对象的创建过程,根据不同的参数返回相应的产品对象。它提供了一种简单而统一的方式来创建对象,将对象的创建和使用分离,降低了客户端代码的耦合性。
优点和缺点:
优点:
- 将对象的创建逻辑集中在一个工厂类中,方便维护和管理。
- 客户端通过调用工厂方法获取所需的产品对象,无需直接实例化具体产品类。
- 可以通过改变工厂方法的实现,轻松扩展新的产品类。
缺点:
- 工厂类的职责较重,可能随着产品种类的增加而变得臃肿。
- 新增产品需要修改工厂类的代码,违背了开闭原则。
总体而言,简单工厂设计模式提供了一种简单而统一的方式来创建对象,将对象的创建和使用分离。它适用于需要根据不同参数创建不同对象的场景,并可以提供一定程度的灵活性和扩展性。然而,在使用简单工厂模式时,需要注意工厂类的职责和代码的扩展性,以确保系统的可维护性和可扩展性。
简单工厂设计模式(Simple Factory Pattern)是一种创建型设计模式,旨在通过一个工厂类来封装对象的创建过程。它属于最基本的工厂模式,虽然并不是一种正式的设计模式,但在实际开发中被广泛应用。
- 在简单工厂模式中,存在一个具体的工厂类,负责根据客户端的需求创建不同类型的产品对象。客户端只需要提供一个参数或条件给工厂类,工厂类根据参数或条件的不同,返回相应类型的产品对象。这样,客户端与具体产品类之间解耦,不需要直接依赖具体产品的创建过程。
- 总结就是:一个工厂负责生产多个产品
2、工厂方法
2.1 介绍
工厂方法设计模式(Factory Method Design Pattern)是一种创建型设计模式,旨在解决对象的创建过程。它定义了一个用于创建对象的接口,但将具体对象的创建延迟到子类中实现。工厂方法模式通过将对象的创建和使用分离,实现了开闭原则,使得系统更加灵活可扩展。
2.2 核心思想
工厂方法设计模式的核心思想是将对象的创建委托给子类来实现。定义一个创建对象的接口,具体的对象创建由子类来完成。这样可以在不修改客户端代码的情况下,通过切换具体工厂类来创建不同的产品对象。
2.3 组成和基本流程
工厂方法设计模式通常由以下组成部分构成:
- 抽象工厂类(Abstract Factory Class):定义了创建对象的接口,包含一个抽象的工厂方法。
- 具体工厂类(Concrete Factory Class):实现了抽象工厂类,负责创建具体的产品对象。
- 抽象产品类(Abstract Product Class):定义了产品对象的接口,描述了产品的公共方法。
- 具体产品类(Concrete Product Class):实现了抽象产品类,表示具体的产品对象。
工厂方法设计模式的基本流程如下:
- 定义抽象工厂类,包含一个抽象的工厂方法。
- 定义抽象产品类,描述产品对象的公共接口。
- 创建具体产品类,实现抽象产品类的接口。
- 创建具体工厂类,实现抽象工厂类,负责创建具体的产品对象。
- 在客户端中,通过抽象工厂类创建产品对象,而无需直接实例化具体的产品类。
2.4 使用场景
工厂方法设计模式适用于以下场景:
- 当需要创建一组相关的对象时,可以使用工厂方法模式来统一创建这些对象。
- 当对象的创建逻辑比较复杂,或者需要根据不同的条件创建不同的对象时,可以使用工厂方法模式。
2.5 具体案例
下列这段代码展示了使用抽象工厂设计模式的资源加载器实现。
// 抽象产品和具体产品
@Data
public abstract class AbstractResource {
private String url;
public AbstractResource() {
}
public AbstractResource(String url) {
this.url = url;
}
// 子类都要实现的方法
public abstract InputStream getInputStream();
}
public class FileResource extends AbstractResource {
public FileResource() {
super();
}
public FileResource(String url) {
super(url);
}
@Override
public InputStream getInputStream() {
return null;
}
}
// 抽象工厂和具体工厂
public interface IResourceFactory {
public AbstractResource create(String url);
}
public class FileResourceFactory implements IResourceFactory {
@Override
public AbstractResource create(String url) {
// 很复杂的逻辑...
// 很复杂的逻辑...
// 很复杂的逻辑...
// 很复杂的逻辑...
// 很复杂的逻辑...
return new FileResource(url);
}
}
// 测试
@Slf4j
public class ResourceLoaderMethod {
private IResourceFactory resourceFactory;
private static Map<String, IResourceFactory> resourceFactoryCache = new HashMap<>(8);
static {
// 在类加载的时候/起一个定时器,定时读取配置文件
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("resourceFactory.properties");
Properties properties = null;
try {
properties = new Properties();
properties.load(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
Set<Map.Entry<Object, Object>> entries = properties.entrySet();
Iterator<Map.Entry<Object, Object>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<Object, Object> next = iterator.next();
String prefixKey = next.getKey().toString();
String className = next.getValue().toString();
Class<?> clazz = null;
try {
clazz = Class.forName(className);
IResourceFactory instance = (IResourceFactory) clazz.getConstructor().newInstance();
resourceFactoryCache.put(prefixKey, instance);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
private AbstractResource load(String url) {
String prefix = getPrefix(url);
return resourceFactoryCache.get(prefix).create(url);
}
private String getPrefix(String url) {
if (url == null || "".equals(url) || url.contains(":")) {
return "default";
}
String[] split = url.split(":");
return split[0];
}
public static void main(String[] args) {
ResourceLoaderMethod rlm = new ResourceLoaderMethod();
AbstractResource load = rlm.load("file:user/inter");
System.out.println("load.getUrl() = " + load.getUrl());
}
}
代码详细解析:
抽象产品和具体产品部分:
AbstractResource
是抽象产品类,它包含了一个url
字段,并定义了一个抽象方法getInputStream()
。FileResource
是具体产品类,它继承自AbstractResource
,实现了抽象方法getInputStream()
。
抽象工厂和具体工厂部分:
IResourceFactory
是抽象工厂接口,它定义了一个方法create()
,用于创建抽象产品AbstractResource
对象。FileResourceFactory
是具体工厂类,实现了IResourceFactory
接口。在create()
方法中,它通过复杂的逻辑创建一个FileResource
对象,并将其返回。
测试部分:
ResourceLoaderMethod
类中包含了一个静态的resourceFactoryCache
缓存,用于存储不同前缀对应的具体工厂对象。- 在静态代码块中,通过读取配置文件
resourceFactory.properties
,获取了具体工厂类的映射关系,并将其实例化放入缓存中。 load()
方法根据传入的 URL 获取前缀,并从resourceFactoryCache
缓存中获取对应的具体工厂对象,然后调用其create()
方法创建抽象产品对象。getPrefix()
方法用于从 URL 中提取前缀,如果 URL 为空、空字符串或包含冒号,则返回默认前缀。- 在
main()
方法中,创建ResourceLoaderMethod
对象,并通过调用load()
方法加载资源。最后打印出加载的资源的 URL。
通过这段代码,资源加载器实现了抽象工厂设计模式。客户端只需与抽象工厂接口 IResourceFactory
进行交互,通过调用其 create()
方法,由具体工厂类根据传入的 URL 创建相应的具体产品对象。这样可以实现客户端与具体产品类的解耦,提高了代码的灵活性和可维护性。
2.6 总结
工厂方法设计模式的优点包括:
- 实现了对象的创建和使用的解耦,客户端只需要与抽象工厂接口交互。
- 提供了可扩展性,可以通过添加新的具体工厂类和产品类来创建新的产品对象。
- 代码结构清晰,各个对象的职责明确。
然而,工厂方法设计模式也存在一些缺点:
- 需要定义抽象工厂和抽象产品接口,增加了代码的复杂度。
- 每新增一个具体产品,都需要新增相应的具体产品类和具体工厂类,导致类的数量增加。
总的来说,工厂方法设计模式是一种非常常用和灵活的设计模式,可以帮助我们实现对象的创建和使用的解耦,提高代码的可扩展性和维护性。
总结就是:一个工厂生产一个产品
3、抽象工厂
3.1 介绍
抽象工厂设计模式(Abstract Factory Pattern)是一种创建型设计模式,旨在提供一个接口来创建一系列相关或依赖对象的家族,而无需指定具体实现类。它将对象的创建与使用相分离,使得客户端代码与具体产品的实现解耦,提供了一种灵活的方式来创建对象。
3.2 核心思想
抽象工厂设计模式的核心思想是通过提供一个抽象工厂接口,定义一系列创建产品的方法,每个具体的工厂实现该接口,并负责创建特定的产品家族。客户端只与抽象工厂和抽象产品进行交互,而不直接依赖具体产品的实现。
3.3 组成和基本流程
抽象工厂设计模式包含以下组成:
- 抽象工厂(Abstract Factory):定义用于创建产品家族的接口,声明了一系列创建产品的方法。
- 具体工厂(Concrete Factory):实现抽象工厂接口,具体实现了创建产品的方法,每个具体工厂对应一个具体产品家族。
- 抽象产品(Abstract Product):定义产品的接口,声明产品的共性方法。
- 具体产品(Concrete Product):实现抽象产品接口,具体定义产品的具体实现。
抽象工厂设计模式的基本流程:
- 定义抽象工厂接口,声明一系列创建产品的方法。
- 创建具体产品类,实现抽象产品接口。
- 创建具体工厂类,实现抽象工厂接口,实现产品的创建方法。
- 在客户端中使用抽象工厂接口声明工厂对象,通过工厂对象创建具体产品。
3.4 使用场景
抽象工厂设计模式适用于以下情景:
- 系统需要创建一系列相关或依赖的产品家族。
- 客户端不关心或不知道如何创建产品的具体实现。
- 需要解耦客户端和具体产品的实现。
3.5 具体案例
代码详细解析:
下列代码实现了抽象工厂设计模式。抽象工厂设计模式旨在提供一个接口(抽象工厂),用于创建一系列相关或依赖的产品(抽象产品)的家族,而无需指定具体实现类。它将产品的创建与使用相分离,使得客户端代码与具体产品的实现解耦,提供了一种灵活的方式来创建对象。
在这段代码中:
- 抽象产品由接口
IResource
和具体实现类AbstractVideoResource
、AbstractTextResource
、AbstractPictureResource
组成。每个具体产品都实现了IResource
接口,并包含一些特定的方法。 - 抽象工厂由接口
IResourceFactory
和具体工厂类AbstractResourceFactory
、FileResourceFactory
、HttpResourceFactory
组成。抽象工厂接口定义了创建产品的抽象方法和加载具体产品的工厂方法。具体工厂类实现了抽象工厂接口,并负责实现具体产品的创建和加载。 - 在
ResourceLoaderMethod
类中,通过静态缓存resourceFactoryCache
存储不同前缀对应的具体工厂对象。在类加载时,通过读取配置文件,动态创建具体工厂对象,并将其放入缓存中。load()
方法根据传入的 URL 获取前缀,然后通过具体工厂对象调用create()
方法创建抽象产品对象。这样,客户端代码只需要与抽象工厂进行交互,而无需关心具体产品的创建和加载过程。
以下是整合后的代码,展示了抽象工厂设计模式的实现:
public interface IResource {//抽象产品
InputStream getInputStream();
}
//抽象产品
@Data
public abstract class AbstractVideoResource implements IResource {
private String url;
public AbstractVideoResource() {
}
public AbstractVideoResource(String url) {
this.url = url;
}
public void transformMp4() {
System.out.println("视频资源组共有方法-transformMp4()");
}
}
//抽象产品
@Data
public abstract class AbstractTextResource implements IResource {
private String url;
public AbstractTextResource() {
}
public AbstractTextResource(String url) {
this.url = url;
}
public void transformUtf8() {
System.out.println("文本资源组共有方法-transformUtf8()");
}
}
//抽象产品
@Data
public abstract class AbstractPictureResource implements IResource {
private String url;
public AbstractPictureResource() {
}
public AbstractPictureResource(String url) {
this.url = url;
}
public void transformJpg() {
System.out.println("图片资源组共有方法-transformJpg()");
}
}
//具体产品
public class FileTextResource extends AbstractTextResource {
@Override
public InputStream getInputStream() {
return null;
}
}
//具体产品
public class FileVideoResource extends AbstractVideoResource {
@Override
public InputStream getInputStream() {
return null;
}
}
//具体产品
public class FilePictureResource extends AbstractPictureResource {
public FilePictureResource() {
super();
}
public FilePictureResource(String url) {
super(url);
}
@Override
public InputStream getInputStream() {
return null;
}
}
//具体产品
public class HttpPictureResource extends AbstractPictureResource {
public HttpPictureResource() {
super();
}
public HttpPictureResource(String url) {
super(url);
}
@Override
public InputStream getInputStream() {
return null;
}
}
//具体产品
public class HttpTextResource extends AbstractPictureResource {
public HttpTextResource() {
super();
}
public HttpTextResource(String url) {
super(url);
}
@Override
public InputStream getInputStream() {
return null;
}
}
//具体产品
public class HttpVideoResource extends AbstractPictureResource {
public HttpVideoResource() {
super();
}
public HttpVideoResource(String url) {
super(url);
}
@Override
public InputStream getInputStream() {
return null;
}
}
//抽象工厂
public interface IResourceFactory {
AbstractPictureResource create(String url);
AbstractPictureResource loadPicture(String url);
AbstractVideoResource loadVideo(String url);
AbstractTextResource loadText(String url);
}
//抽象工厂
public abstract class AbstractResourceFactory implements IResourceFactory {
}
//具体工厂
public class FileResourceFactory extends AbstractResourceFactory {
@Override
public AbstractPictureResource create(String url) {
return new FilePictureResource(url);
}
@Override
public AbstractPictureResource loadPicture(String url) {
return null;
}
@Override
public AbstractVideoResource loadVideo(String url) {
return null;
}
@Override
public AbstractTextResource loadText(String url) {
return null;
}
}
//抽象工厂
public class HttpResourceFactory extends AbstractResourceFactory {
@Override
public AbstractPictureResource create(String url) {
return new HttpPictureResource(url);
}
@Override
public AbstractPictureResource loadPicture(String url) {
return null;
}
@Override
public AbstractVideoResource loadVideo(String url) {
return null;
}
@Override
public AbstractTextResource loadText(String url) {
return null;
}
}
//客户端测试
@Slf4j
public class ResourceLoaderMethod {
private IResourceFactory resourceFactory;
private static Map<String, IResourceFactory> resourceFactoryCache = new HashMap<>(8);
static {
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("resourceFactory.properties");
Properties properties = null;
try {
properties = new Properties();
properties.load(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
Set<Map.Entry<Object, Object>> entries = properties.entrySet();
Iterator<Map.Entry<Object, Object>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<Object, Object> next = iterator.next();
String prefixKey = next.getKey().toString();
String className = next.getValue().toString();
Class<?> clazz = null;
try {
clazz = Class.forName(className);
IResourceFactory instance = (IResourceFactory) clazz.getConstructor().newInstance();
resourceFactoryCache.put(prefixKey, instance);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
private AbstractPictureResource load(String url) {
String prefix = getPrefix(url);
return resourceFactoryCache.get(prefix).create(url);
}
private String getPrefix(String url) {
if (url == null || "".equals(url) || url.contains(":")) {
return "default";
}
String[] split = url.split(":");
return split[0];
}
public static void main(String[] args) {
ResourceLoaderMethod rlm = new ResourceLoaderMethod();
AbstractPictureResource load = rlm.load("file:user/inter");
System.out.println("load.getUrl() = " + load.getUrl());
}
}
3.6 总结
优点:
- 提供了一种灵活的方式来创建一系列相关或依赖的产品家族。
- 客户端与具体产品的实现解耦,可以方便地替换具体工厂和产品。
- 符合开闭原则,容易扩展新的产品家族。
缺点:
- 添加新的产品家族可能需要修改抽象工厂的接口和所有的具体工厂。
抽象工厂设计模式通过将产品的创建与使用相分离,提供了一种创建对象的灵活方式,同时也增加了系统的复杂度。在设计时需要权衡灵活性和复杂性之间的平衡。
总结就是:一个工厂生产一组产品线
三、建造者设计模式
1、简介
建造者设计模式(Builder Pattern)是一种创建型设计模式,它专注于逐步构建复杂对象。它将对象的构建过程与其表示分离,允许相同的构建过程创建不同的表示形式。该模式的目标是简化对象的构建过程,并提供灵活性和可扩展性。
2、核心思想
Builder设计模式的核心思想是将对象的构建过程从其实际表示中解耦。通常情况下,一个对象的构建过程是复杂且多步骤的,使用Builder模式可以将这些步骤分解为独立的方法,从而使得构建过程更加灵活和可控。
3、组成和基本流程
Builder设计模式包含以下几个主要组成部分:
- Director(指导者):负责控制对象的构建过程,按照一定的顺序调用Builder的方法来构建对象。
- Builder(构建者):定义构建对象的接口,包含各个构建步骤的方法。
- ConcreteBuilder(具体构建者):实现Builder接口,负责具体的构建逻辑,并返回构建完成的对象。
- Product(产品):表示最终构建完成的对象,通常具有复杂的内部结构。
详情
Builder设计模式的基本流程和简单示例如下:
- 定义一个Builder接口,其中包含各个构建步骤的方法。
- 创建具体的Builder类,实现Builder接口,并实现各个构建步骤的具体逻辑。
- 创建一个Director类,它包含一个Builder对象作为成员变量。通过调用Builder的方法,按照特定的顺序构建对象。
- 最终调用Builder的一个方法返回构建完成的对象。
下列是一个简单的建造者设计模式的例子,这段代码演示了如何使用建造者设计模式创建一个电脑对象。
首先,我们定义了一个Computer类,它具有一些属性(processor、memory、hardDisk、monitor)以及对应的设置方法。
然后,我们定义了一个ComputerBuilder接口,其中包含了构建电脑对象所需的方法(buildProcessor、buildMemory、buildHardDisk、buildMonitor)以及获取构建完成的电脑对象的方法getComputer()。
接着,我们实现了一个BasicComputerBuilder类,它是ComputerBuilder接口的具体实现。在BasicComputerBuilder类中,我们实例化了一个Computer对象,并实现了构建电脑各个部件的方法。
之后,我们创建了一个ComputerDirector类,它负责指导建造过程。通过调用ComputerBuilder的方法,按照一定的顺序构建电脑对象,并返回构建完成的对象。
最后,在Main类中,我们创建了一个BasicComputerBuilder对象作为建造者,创建了一个ComputerDirector对象作为指导者,然后通过指导者来构建电脑对象。最后,我们输出了构建完成的电脑对象的各个属性。
class Computer {
private String processor;
private String memory;
private String hardDisk;
private String monitor;
public void setProcessor(String processor) {
this.processor = processor;
}
public void setMemory(String memory) {
this.memory = memory;
}
public void setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
}
public void setMonitor(String monitor) {
this.monitor = monitor;
}
// 省略其他方法和属性的访问器
}
interface ComputerBuilder {
void buildProcessor();
void buildMemory();
void buildHardDisk();
void buildMonitor();
Computer getComputer();
}
class BasicComputerBuilder implements ComputerBuilder {
private Computer computer;
public BasicComputerBuilder() {
this.computer = new Computer();
}
public void buildProcessor() {
computer.setProcessor("Basic Processor");
}
public void buildMemory() {
computer.setMemory("4GB");
}
public void buildHardDisk() {
computer.setHardDisk("500GB");
}
public void buildMonitor() {
computer.setMonitor("15-inch");
}
public Computer getComputer() {
return computer;
}
}
class ComputerDirector {
public Computer buildComputer(ComputerBuilder builder) {
builder.buildProcessor();
builder.buildMemory();
builder.buildHardDisk();
builder.buildMonitor();
return builder.getComputer();
}
}
public class Main {
public static void main(String[] args) {
ComputerBuilder builder = new BasicComputerBuilder();
ComputerDirector director = new ComputerDirector();
Computer computer = director.buildComputer(builder);
System.out.println("Processor: " + computer.getProcessor());
System.out.println("Memory: " + computer.getMemory());
System.out.println("Hard Disk: " + computer.getHardDisk());
System.out.println("Monitor: " + computer.getMonitor());
}
}
在这个示例中,我们通过建造者模式逐步构建了一个电脑对象。指导者类控制了建造过程,而具体的建造者类负责实际构建对象。最终,我们可以获取到一个完整的电脑对象,并对其进行进一步操作。
4、使用场景
Builder设计模式适用于以下情况:
- 当需要创建具有复杂内部结构的对象时,可以使用Builder模式将构建过程分解为多个简单步骤。
- 当需要构建的对象存在不同的表示形式时,可以使用Builder模式来创建不同的表示。
- 当需要在构建过程中灵活地添加或修改构建步骤时,可以使用Builder模式。
- 实现不可变对象,可以使用Builder模式创建不可变对象。通过将建造者的构建方法设置为私有,并将需要的属性通过构造函数进行初始化,可以确保对象在构建后不可再修改。这样的对象可以提供更好的线程安全性和代码健壮性。
5、具体案例
在这个示例中,我们使用建造者设计模式创建了一个名为 ImmutablePerson
的不可变对象类。
在
ImmutablePerson
类中,属性name
、age
和address
被声明为final
,并且没有提供任何修改它们的方法。这样可以确保这些属性在对象创建后不可变。通过私有的构造方法
ImmutablePerson(Builder builder)
,我们将属性的值从Builder
对象传递给了ImmutablePerson
对象,并在构造方法内进行了赋值操作。这样,我们可以在构造对象时保证对象的属性值一致和不可变。Builder
类是一个嵌套类,它提供了链式调用的方法来设置ImmutablePerson
的属性值。每个方法都返回Builder
对象本身,以便可以连续调用多个方法。最后,通过调用build()
方法,我们可以创建并返回一个不可变的ImmutablePerson
对象。
使用该示例,我们可以这样创建一个不可变的 ImmutablePerson
对象:
public final class ImmutablePerson {
private final String name;
private final int age;
private final String address;
private ImmutablePerson(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.address = builder.address;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getAddress() {
return address;
}
public static class Builder {
private String name;
private int age;
private String address;
public Builder() {
}
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public Builder setAddress(String address) {
this.address = address;
return this;
}
public ImmutablePerson build() {
return new ImmutablePerson(this);
}
}
public static void main(String[] args) {
// Usage example:
ImmutablePerson person = new ImmutablePerson.Builder()
.setName("John")
.setAge(30)
.setAddress("123 Street")
.build();
}
}
在这个示例中,我们通过链式调用 Builder
的方法设置属性的值,并最后调用 build()
方法来创建 ImmutablePerson
对象。一旦对象创建完成,它的属性就是不可变的,无法再修改。
这种方式提供了不可变对象的安全性和线程安全性,因为对象的状态无法在创建后被修改。不可变对象还可以更容易地进行缓存、共享和使用。
lombok例子:
在lombok中,我们使用@Builder
注解会自动生成一个建造者模式的构建者类。下面是一个使用Lombok的@Builder
注解的示例代码:
非常抱歉前面的回答中有些遗漏,确实是多了一些代码。以下是编译前和编译后的代码:
编译前的代码:
@Builder
@Getter
@ToString
public class User {
private String name;
private Integer age;
private String bobby;
public static void main(String[] args) {
User.UserBuilder builder = new UserBuilder();
User user = builder.name("jack").age(18).bobby("rap").build();
System.out.println("user = " + user);
}
}
编译后的代码:
public class User {
private String name;
private Integer age;
private String bobby;
public static void main(String[] args) {
UserBuilder builder = new UserBuilder();
User user = builder.name("jack").age(18).bobby("rap").build();
System.out.println("user = " + user);
}
User(String name, Integer age, String bobby) {
this.name = name;
this.age = age;
this.bobby = bobby;
}
public static UserBuilder builder() {
return new UserBuilder();
}
public String getName() {
return this.name;
}
public Integer getAge() {
return this.age;
}
public String getBobby() {
return this.bobby;
}
public String toString() {
return "User(name=" + this.getName() + ", age=" + this.getAge() + ", bobby=" + this.getBobby() + ")";
}
public static class UserBuilder {
private String name;
private Integer age;
private String bobby;
UserBuilder() {
}
public UserBuilder name(String name) {
this.name = name;
return this;
}
public UserBuilder age(Integer age) {
this.age = age;
return this;
}
public UserBuilder bobby(String bobby) {
this.bobby = bobby;
return this;
}
public User build() {
return new User(this.name, this.age, this.bobby);
}
public String toString() {
return "User.UserBuilder(name=" + this.name + ", age=" + this.age + ", bobby=" + this.bobby + ")";
}
}
}
请注意,编译后的代码中添加了一些内容,其中包括:
- 在
User
类中,添加了私有构造函数以及对应的属性赋值操作。 - 在
User
类中,添加了getter方法和toString()
方法的具体实现。 - 在
User
类中,添加了静态的builder()
方法,用于创建UserBuilder
对象。 - 在
UserBuilder
类中,添加了构造函数、链式调用方法以及build()
方法的具体实现。
这些额外的代码是Lombok库根据注解自动生成的,它们帮助简化了建造者模式的使用,减少了手动编写重复代码的工作量。这样,您可以更方便地创建和操作User
对象。
6、总结
Builder设计模式的优点包括:
- 可以分步骤构建复杂对象,使构建过程更加灵活和可控。
- 可以创建不同的表示形式,根据复杂的配置项进行定制化构建,提供了更多对象构建的可能性。
- 隔离了构建过程和表示,使得扩展和修改更加方便。
然而,Builder设计模式也存在一些缺点:
- 会增加代码量,因为需要定义和实现多个类。
- 对于简单对象的构建,使用Builder模式可能会显得过于复杂。
总的来说,Builder设计模式在构建复杂对象时非常有用,并提供了灵活性和可扩展性。但在简单情况下,使用该模式可能会带来一定的开销。
7、和工厂设计模式的区别
建造者设计模式和工厂设计模式是两种常见的创建型设计模式,它们都用于创建对象,但在目的和使用方式上存在一些区别。
- 工厂设计模式旨在通过一个工厂类来创建对象,隐藏了具体对象的创建细节,并将客户端与具体对象的实例化过程解耦。客户端只需要通过工厂类来请求所需的对象,而不需要直接实例化对象。工厂模式通常适用于创建不同类型对象的场景,通过使用不同的工厂方法或参数,可以创建不同类型的对象。
- 与之不同,建造者设计模式关注的是逐步构建复杂对象,将对象的构建过程与其表示分离。它允许按照特定的步骤或顺序构建对象,并允许在构建过程中配置对象的各个部分。建造者模式通常适用于创建具有复杂结构或多个可选组件的对象,以及构建过程中涉及多个步骤或变化的对象。
现在,让我们结合生活中的一个例子来说明这两种设计模式的区别。
- 假设你要制作一份披萨。使用工厂模式,你可以有一个披萨工厂,通过向工厂提供披萨的类型(例如,奶酪披萨、素食披萨等),工厂将返回相应类型的披萨对象。这种情况下,你只需要告诉工厂你想要的披萨类型,而不需要知道具体如何制作披萨。
- 现在,假设你要定制一份复杂的披萨,它有多个可选组件,如酱料、配料和尺寸等。这时,建造者模式更适合。你可以有一个披萨建造者,它提供了设置酱料、配料和尺寸等属性的方法。你可以逐步调用这些方法来构建披萨对象,并在构建过程中根据你的喜好进行定制。
总结一下,工厂设计模式适用于创建不同类型的对象,而建造者设计模式适用于逐步构建复杂对象。工厂模式隐藏了对象的创建细节,而建造者模式允许按照特定的步骤或顺序构建对象,并允许在构建过程中进行配置和定制。
四、原型设计模式
原型设计模式(Prototype Design Pattern)是一种创建型设计模式,旨在通过复制现有对象来创建新对象,而不是通过使用构造函数进行创建。它允许我们通过克隆(复制)现有对象的实例来创建新的对象,而无需显式地依赖于特定类的构造函数。
主要分为:浅拷贝和深拷贝
1、浅拷贝
浅拷贝是原型设计模式中的一种复制方式,它复制对象内的所有基本数据类型和引用数据类型的地址。这意味着在浅拷贝中,原始对象和复制对象将共享相同的引用数据类型的实例。
当执行浅拷贝时,如果对象内有引用类型的成员变量,那么复制的对象将包含对原始对象引用数据类型成员变量的引用。这意味着两个对象的引用类型成员变量指向相同的对象,任何对引用类型的修改将会影响到两个对象。
好的!让我们使用Java来举一个例子,使用浅拷贝来创建歌曲和歌单对象。
首先,我们定义一个歌曲(Song)类:
public class Song {
private String title;
private String artist;
public Song(String title, String artist) {
this.title = title;
this.artist = artist;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
@Override
public String toString() {
return "Song: " + title + " - " + artist;
}
}
接下来,我们定义一个歌单(Playlist)类,它包含了多个歌曲对象:
public class PlayList {
private String name;
private List<Song> songs;
public PlayList(String name) {
this.name = name;
this.songs = new ArrayList<>();
}
public String getName() {
return name;
}
public List<Song> getSongs() {
return songs;
}
public void addSong(Song song) {
songs.add(song);
}
public void removeSong(Song song) {
songs.remove(song);
}
public PlayList shallowCopy(String newName) {
PlayList copy = new PlayList(newName);
copy.songs = new ArrayList<>(songs);
return copy;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PlayList: ").append(name).append("\n");
for (Song song : songs) {
sb.append("- ").append(song).append("\n");
}
return sb.toString();
}
public static void main(String[] args) {
PlayList playList = new PlayList("歌单-全是redvelet的歌");
playList.addSong(new Song("feel my rhythm", "redvelet"));
playList.addSong(new Song("psycho", "redvelet"));
playList.addSong(new Song("bad boy", "redvelet"));
//创建一个新歌单,歌单包含原歌单playList1的所有歌曲,自己在新增歌曲
PlayList newPlayList = playList.shallowCopy("歌单-kpop");
System.out.println("playList = " + playList);
System.out.println("newPlayList = " + newPlayList);
//修改新歌单内的歌曲bad boy为人员为wendy,旧歌单的原信息会改变吗?
Song song = newPlayList.getSongs().get(2);
song.setArtist("wendy");
System.out.println("修改新歌单内的歌曲bad boy");
System.out.println("playList = " + playList);
System.out.println("newPlayList = " + newPlayList);
}
}
在Playlist
类中,我们添加了一个shallowCopy()
方法来执行浅拷贝。该方法会创建一个新的Playlist
对象,并复制歌单中的歌曲列表。注意,我们使用了new ArrayList<>(songs)
来复制歌曲列表的引用。
通过实现cloneable接口实现浅拷贝:
public class PlayListByCloneableInterface implements Cloneable, Serializable {
private String name;
private List<Song> songs;
public PlayListByCloneableInterface(String name) {
this.name = name;
this.songs = new ArrayList<>();
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public List<Song> getSongs() {
return songs;
}
public void addSong(Song song) {
songs.add(song);
}
public void removeSong(Song song) {
songs.remove(song);
}
public PlayListByCloneableInterface shallowCopy(String newName) {
PlayListByCloneableInterface copy = new PlayListByCloneableInterface(newName);
copy.songs = new ArrayList<>(songs);
return copy;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PlayList: ").append(name).append("\n");
for (Song song : songs) {
sb.append("- ").append(song).append("\n");
}
return sb.toString();
}
public static void main(String[] args) throws CloneNotSupportedException {
PlayListByCloneableInterface playList = new PlayListByCloneableInterface("歌单-全是redvelet的歌");
playList.addSong(new Song("feel my rhythm", "redvelet"));
playList.addSong(new Song("psycho", "redvelet"));
playList.addSong(new Song("bad boy", "redvelet"));
//创建一个新歌单,歌单包含原歌单playList1的所有歌曲,自己在新增歌曲
PlayListByCloneableInterface newPlayList = (PlayListByCloneableInterface) playList.clone();
newPlayList.setName("歌单-全是kpop的歌");
System.out.println("playList = " + playList);
System.out.println("newPlayList = " + newPlayList);
//修改新歌单内的歌曲bad boy为人员为wendy,旧歌单的原信息会改变吗?
Song song = newPlayList.getSongs().get(2);
song.setArtist("wendy");
System.out.println("修改新歌单内的歌曲bad boy");
System.out.println("playList = " + playList);
System.out.println("newPlayList = " + newPlayList);
}
}
2、深拷贝
深拷贝是指在复制对象时,不仅复制对象本身,还复制对象所引用的所有子对象,使得复制后的对象与原始对象完全独立,互不影响。
在进行深拷贝时,需要递归地复制对象及其子对象,确保每个子对象都是独立的副本,而不是共享引用。
深拷贝可以解决对象拷贝过程中可能出现的共享引用和副作用问题。它确保了复制对象与原始对象之间的数据隔离,使得修改复制后的对象不会影响原始对象,从而提高代码的可靠性和安全性。
在Java中,可以通过几种方式实现深拷贝:
- 递归复制:对于复杂对象,通过递归遍历对象的每个属性,并针对引用类型的属性进行深度复制。
- 序列化和反序列化:将对象序列化为字节流,然后再反序列化为新的对象。这种方式需要确保对象及其所有子对象都实现了
Serializable
接口。
递归复制:
class Product implements Cloneable {
private String name;
private double price;
private int stock;
// 省略构造函数、getter和setter方法
@Override
public Product clone() {
try {
return (Product) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
// 促销规则
class PromotionRule implements Cloneable {
private String type;
private double discount;
private Product product;
// 省略构造函数、getter和setter方法
@Override
protected PromotionRule clone() {
try {
PromotionRule promotionRule = (PromotionRule) super.clone()
Product product = (Product)product.clone();promotionRule.setProduct(product);
return promotionRule;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
// 促销活动
class PromotionEvent implements Cloneable {
private String name;
private Date startDate;
private Date endDate;
private List<PromotionRule> rules;
// 省略构造函数、getter和setter方法
// 在促销活动中的clone方法需要克隆里边所有的非基础数据类型
@Override
protected PromotionEvent clone() {
try {
PromotionEvent clonedEvent = (PromotionEvent) super.clone();
clonedEvent.startDate = (Date) startDate.clone();
clonedEvent.endDate = (Date) endDate.clone();
clonedEvent.rules = new ArrayList<>();
for (PromotionRule rule : rules) {
clonedEvent.rules.add(rule.clone());
}
return clonedEvent;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
现在,我们已经为每个实体类实现了深拷贝方法。假设我们需要为不同的商品创建相似的促销活动,我们可以使用深拷贝来实现:
public class Main {
public static void main(String[] args) {
// 创建原始促销活动
PromotionEvent originalEvent = createSamplePromotionEvent();
// 创建新的促销活动
PromotionEvent newEvent = originalEvent.clone();
newEvent.setName("新的促销活动");
// 现在newEvent是originalEvent的一个深拷贝副本,我们可以对它进行修改而不会影响originalEvent
// 修改新促销活动的日期
newEvent.setStartDate(addDays(newEvent.getStartDate(), 7));
newEvent.setEndDate(addDays(newEvent.getEndDate(), 7));
// 修改新促销活动的部分规则
List<PromotionRule> newRules = newEvent.getRules();
newRules.get(0).setDiscount(newRules.get(0).getDiscount() * 1.1);
// 现在,我们已经成功地复制了一个与原始活动相似但具有不同日期和部分规则的新促销活动。
// 可以将新活动应用于其他商品,而原始活动保持不变。
}
private static PromotionEvent createSamplePromotionEvent() {
// 创建示例促销活动
List<PromotionRule> rules = Arrays.asList(
new PromotionRule("折扣", 0.9),
new PromotionRule("满减", 50)
);
PromotionEvent event = new PromotionEvent(
"原始促销活动",
new Date(),
addDays(new Date(), 7),
rules
);
return event;
}
private static Date addDays(Date date, int days) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);calendar.add(Calendar.DATE, days);
return calendar.getTime();
}
}
序列化方式:前提需要实现Serializable接口
深拷贝的通用做法就是使用对象想对原型对象进行序列化,再对序列化后的二进制流 执行反序列化操作,就可以得到一个完完全全相同的对象,这种序列化的方式有很多,比如先转为json,在转成内存模型的对象,也是可以的。
@Test
public void deepCopyTest() throws Exception{
User user = new User(12, "zhangsan");
user.setDog(new Dog(2));
// 将对象写到字节数组当中
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(user);
// 获取字节数组
byte[] bytes = outputStream.toByteArray();
// 用输入流读出来
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object object = objectInputStream.readObject();
User user1 = (User) object;
user.setAge(44);
user.getDog().setAge(11);
System.out.println(user);
System.out.println(user1);
}
应用场景:
在订单管理系统中,深拷贝可以用于创建新订单并复制现有订单的商品和客户信息。假设我们有一个名为
Order
的类,其中包含订单信息和关联的商品和客户对象。我们可以通过深拷贝来创建新订单,并复制原始订单中的商品和客户信息,但需要重新填写新订单的其他信息。这样,新订单和原始订单是相互独立的对象,对新订单的修改不会影响原始订单。就比如每个月需要进一批货,但是大部分货的信息都一样,只是日期不一样,就可以使用深拷贝。在前端开发中,重置按钮通常用于将表单或页面恢复到初始状态。当加载表单或页面时,可以进行深拷贝,复制一份初始内容作为参考。当用户点击重置按钮时,可以将深拷贝的对象作为引用,重新将其内容设置到表单或页面中,从而实现重置操作。通过深拷贝并使用引用转换,可以避免对原始对象的修改,确保每次重置都回到初始状态。