【Java设计模式003原型模式

2023年 8月 7日 22.2k 0

概述

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

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

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() 方法来复制新的对象。

原型模式的优点在于:

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

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

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

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

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

    public class Realizetype implements Cloneable {
        public Realizetype() {
            System.out.println("具体原型创建完成");
        }
    
        @Override
        protected Realizetype clone() throws CloneNotSupportedException {
            System.out.println("具体原型复制成功");
            return (Realizetype) super.clone();
        }
    }
    

    测试类:

    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);
        }
    }
    

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

    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();
            }
        }
    }
    

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

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

  • 如果你需要复制一些对象,同时又希望代码独立于这些对象所属的具体类,可以使用原型模式;
  • 如果子类的区别仅在于其对象的初始化方式,那么你可以使用该模式来减少子类的数量,这么做的话客户端不必根据子类进行实例化,只需要找到合适的原型然后进行克隆即可;
  • 当一个对象的构建代价过高时。例如某个对象里面的数据需要访问数据库才能拿到,而我们却要多次构建这样的对象;
  • 当构建的多个对象,均需要处于某种原始状态时,就可以先构建一个拥有此状态的原型对象,其他对象基于原型对象来修改。
  • 个人认为原型模式的一个重要应用在于减少保护性拷贝的代码量,保护性拷贝是指为了防止客户端对类的约束条件产生破坏,传递对象的时候要进行拷贝,一个简单的示例如下:

    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() 方法的返回值即可,这样大大减少了代码的书写量。

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

    往期回顾

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

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论