【Java设计模式003】原型模式

概述

大家好,个人gzh是大猪和小猪的小家 ,我们的gzh是朝阳三只大明白,满满全是干货,分享近期的学习知识以及个人总结(包括读研和IT),跪求一波关注,希望和大家一起努力、进步!!

原型模式解决的主要问题是如何快速的复制一个已经存在的对象,一个普遍的做法是构建一个属于相同类的对象,然后遍历原始对象的所有属性值并复制到新对象中。这样的做法有一些问题,不是每一个对象都可以通过这种方式进行复制,且这么做的编程代价过高,比方说:

java 复制代码
class Main{
	public static void main(String[] args) {
        Sheep sheep = new Sheep("tom", 1, "red");
        new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
    }
}

这样做比较好理解,简单易于操作,但是复制对象的效率很低(现在只有三个参数需要处理)。而原型模式就可以解决这个问题,原型模式是用于创建重复的对象,同时又能保证性能的一种模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

原型模式的角色如下:

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

原型模式的优点在于:

  1. 你可以克隆对象,而无需与它们所属的具体类相耦合;
  2. 你可以克隆预生成原型,避免反复运行初始化代码;
  3. 你可以更方便地生成复杂对象;
  4. 你可以用继承以外的方式来处理复杂对象的不同配置;

原型模式的缺点在于:

  1. 克隆包含循环引用的复杂对象可能会非常麻烦。

实现

原型模式的克隆分为浅克隆和深克隆:

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

Java 中的 Object 类提供了 clone() 方法,该方法实现了浅克隆,在 Java 中 Cloneable 接口就是原型模式中的抽象原型类 ,而任何实现了 Cloneable 接口的类就是具体原型类,代码如下:

java 复制代码
public class Realizetype implements Cloneable {
    public Realizetype() {
        System.out.println("具体原型创建完成");
    }

    @Override
    protected Realizetype clone() throws CloneNotSupportedException {
        System.out.println("具体原型复制成功");
        return (Realizetype) super.clone();
    }
}

测试类:

java 复制代码
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Realizetype prototye = new Realizetype();
        Realizetype clonedObject = prototye.clone();
		// false
        System.out.println(prototye == clonedObject);
    }
}

上面的拷贝方式是浅拷贝,如果需要实现深拷贝,可以考虑使用序列化、反序列化的方式进行实现,示例代码如下:

java 复制代码
public class Sheep implements Cloneable, Serializable {
	...
    // 深克隆方法
    public Sheep deepClone() throws IOException {
        //创建对象流对象
        ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("a.txt")));
        ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("a.txt")));

        try {
            //序列化
            oos.writeObject(this);

            //反序列化
            Sheep newSheep = (Sheep) ois.readObject();

            return newSheep;
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        } finally {
            oos.close();
            ois.close();
        }
    }
}

小结一下,原型模式的实现思想如下:

  1. 创建抽象原型类,声明克隆方法;
  2. 创建具体原型类 ,实现抽象原型类,重写克隆方法;
  3. 客户端使用原型对象;

应用

  1. 如果你需要复制一些对象,同时又希望代码独立于这些对象所属的具体类,可以使用原型模式;
  2. 如果子类的区别仅在于其对象的初始化方式,那么你可以使用该模式来减少子类的数量,这么做的话客户端不必根据子类进行实例化,只需要找到合适的原型然后进行克隆即可;
  3. 当一个对象的构建代价过高时。例如某个对象里面的数据需要访问数据库才能拿到,而我们却要多次构建这样的对象;
  4. 当构建的多个对象,均需要处于某种原始状态时,就可以先构建一个拥有此状态的原型对象,其他对象基于原型对象来修改。

个人认为原型模式的一个重要应用在于减少保护性拷贝 的代码量,保护性拷贝是指为了防止客户端对类的约束条件产生破坏,传递对象的时候要进行拷贝,一个简单的示例如下:

java 复制代码
public class Period {
    private final Date start;
    private final Date end;

    public Period(Date start, Date end) {
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());

        if (this.start.compareTo(end) > 0){
            throw new IllegalArgumentException(this.start + "after" + this.end);
        }
    }

    public Date getStart() {
        // 直接return会破坏类
        return new Date(start.getTime());
    }

    public Date getEnd() {
        // 直接return会破坏类
        return new Date(end.getTime());
    }
}

类的约束条件是开始时间要小于结束时间,且这两个成员变量是私有的,但是如果 getter 方法中直接返回原始对象,那么就会破坏原本的约束,也就是说如果一个类从客户端得到或者返回一个可变组件,那么就必须进行保护性拷贝。如果使用了原型模式,直接返回要进行保护对象的 clone() 方法的返回值即可,这样大大减少了代码的书写量。

除此之外还可以创建一个中心化原型注册表,用于存储常用原型。可以新建一个工厂类来实现注册表,或者添加一个静态方法,不管是哪一种方式,这些方法必须能够根据客户端代码设定的条件进行搜索。搜索条件可以是简单的字符串,或者是一组复杂的搜索参数。找到合适的原型后,注册表应对原型进行克隆,并将复制生成的对象返回给客户端。

往期回顾

  1. 【Java设计模式002】工厂模式
  2. 【Java设计模式001】单例模式

文中难免会出现一些描述不当之处(尽管我已反复检查多次),欢迎在留言区指正,相关的知识点也可进行分享,希望大家都能有所收获!!如果觉得我的文章写得还行,不妨支持一下。你的每一个转发、关注、点赞、评论都是对我最大的支持!

相关推荐
许野平23 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
Aloudata35 分钟前
从Apache Atlas到Aloudata BIG,数据血缘解析有何改变?
大数据·apache·数据血缘·主动元数据·数据链路
水豚AI课代表41 分钟前
分析报告、调研报告、工作方案等的提示词
大数据·人工智能·学习·chatgpt·aigc
暗黑起源喵2 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod2 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
码农派大星。3 小时前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man3 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*3 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu3 小时前
Go语言结构体、方法与接口
开发语言·后端·golang