您现在的位置是:首页 >技术交流 >肝一肝设计模式【三】-- 原型模式网站首页技术交流

肝一肝设计模式【三】-- 原型模式

老六说编程 2023-06-26 00:00:02
简介肝一肝设计模式【三】-- 原型模式

系列文章目录

肝一肝设计模式【一】-- 单例模式 传送门
肝一肝设计模式【二】-- 工厂模式 传送门



前言

前文中我们知道设计模式可以分为三大类:创建型模式、结构型模式、行为型模式。创建型模式中有5种,前两节里我们分析了单例模式、工厂方法模式、抽象工厂模式,那么还有两种模式:原型模式和建造者模式,它们也属于创建型模式,这一节中我们分析一下原型模式。


一、什么是原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
原型模式要求对象实现一个可以克隆自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样的话通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再通过new去创建。

是的,我又要举例子了,兔子和老虎同一天生日(嗯,女鹅说它们同一天生日┐(・o・)┌ ),让我给它们发祝福邮件。
在这里插入图片描述
我呢比较懒,除了署名其他内容都是一样的,这时我就可以先写好祝福语,然后克隆这个祝福语,最后根据不同的署名进行发送。

写下代码:

public class PrototypeClient {
	static class Mail implements Cloneable {
		private String name;
		private String message;
		
		public Mail() {}
        
		public String getName() {
	        return name;
	    }
	
	    public void setName(String name) {
	        this.name = name;
	    }
	    
	    public String getMessage() {
	        return message;
	    }
	
	    public void setMessage(String message) {
	        this.message = message;
	    }
	    
	    @Override
        protected Mail clone() {
            Mail cloneMail = null;
            try {
                cloneMail = (Mail) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return cloneMail;
        }

        @Override
        public String toString() {
            return name + ":" + message;
        }
	}
	public static void main(String[] args) {
        Mail mail = new Mail();
        mail.setMessage("祝你生日快乐~~");
        Mail mail2 = mail.clone();
        mail.setName("兔子");
        mail2.setName("老虎");
        System.out.println(mail.toString());
        System.out.println(mail2.toString());
    }
}

我们可以看到Mail类实现了Cloneable接口,并重写clone()方法即可完成本类的复制。

我们再看下源码,发现Cloneable只是一个空接口,JDK之所以这么干只是为了在运行时通知JVM可以在该类上使用clone()方法,如果该类没有实现 Cloneable接口,则调用clone()方法就会抛出 CloneNotSupportedException异常。

日常开发中,super.clone()方法并不能满足所有需求,如果Mail类中存在引用对象属性,那么我们克隆出来的对象的该属性就会和原型对象的该属性指向同一内存地址,这就引申出浅克隆和深克隆的问题

二、浅克隆

public class PrototypeClient {
	@Data
	static class Mail implements Cloneable {
		private String name;
		private String message;
		// 我们加一个礼物List
		private List<String> gifts;
	    
	    @Override
        protected Mail clone() {
            Mail cloneMail = null;
            try {
                cloneMail = (Mail) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return cloneMail;
        }

        @Override
        public String toString() {
            return name + ":" + message + ",gifts:" + gifts;
        }
	}
	public static void main(String[] args) {
        Mail mail = new Mail();
        mail.setName("兔子");
        mail.setMessage("祝你生日快乐~~");
        List<String> gifts = new ArrayList<String>();
        gifts.add("糖果");
        gifts.add("积木");
        mail.setGifts(gifts);
        
        Mail cloneMail = mail.clone();
        cloneMail.setName("老虎");
        cloneMail.getGifts().add("吉他");
        
        System.out.println(mail.toString());
        System.out.println(cloneMail.toString());
    }
}

我们新增一个属性gifts(礼物),然后给克隆对象的gifts新增一个元素,然后我们发现原型对象也发生了变化,我们期望克隆出来的对象应该与原型对象互相独立,不应该有联系,所以这显然不符合预期。

所以通过以上代码我们可以看到Java自带的clone()方法进行的是浅克隆,所有的引用对象共用了一个内存地址,那如何解决这个问题呢,也就是如何进行深克隆呢?
在Java中,如果想完成原型对象的深克隆,通常使用序列化(Serializable)的方式,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。

三、深克隆

继续修改代码,增加一个deepClone()方法

public class PrototypeClient {
	@Data
	static class Mail implements Cloneable, Serializable {
		private String name;
		private String message;
		// 我们加一个礼物List
		private List<String> gifts;
	    
	    public Mail deepClone(){
	        try {
	            ByteArrayOutputStream bos = new ByteArrayOutputStream();
	            ObjectOutputStream oos = new ObjectOutputStream(bos);
	            oos.writeObject(this);
	
	            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
	            ObjectInputStream ois = new ObjectInputStream(bis);
	
	            return (Mail)ois.readObject();
	        }catch (Exception e){
	            e.printStackTrace();
	            return null;
	        }
	    }
	    
	    @Override
        protected Mail clone() {
            Mail cloneMail = null;
            try {
                cloneMail = (Mail) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return cloneMail;
        }

        @Override
        public String toString() {
            return name + ":" + message + ",gifts:" + gifts;
        }
	}
	public static void main(String[] args) {
        Mail mail = new Mail();
        mail.setName("兔子");
        mail.setMessage("祝你生日快乐~~");
        List<String> gifts = new ArrayList<String>();
        gifts.add("糖果");
        gifts.add("积木");
        mail.setGifts(gifts);
        
        // 深克隆
        Mail deepCloneMail = mail.deepClone();
        deepCloneMail.setName("老虎");
        deepCloneMail.getGifts().add("吉他");

        System.out.println(mail.toString());
        System.out.println(deepCloneMail.toString());
        System.out.println(mail == deepCloneMail);
        System.out.println(mail.getGifts() == deepCloneMail.getGifts());
    }
}

运行结果:
在这里插入图片描述
通过运行结果,我们成功完成了深克隆。


写在最后

原型模式的使用场景:

  • 类初始化的时候需要消耗大量资源的时候;
  • 获取数据库连接繁琐的时候;
  • 一个对象,有很多个修改者的时候;

原型模式的优点:

  • 可以提升性能;

原型模式的缺点:

  • 因为必须实现Cloneable 接口,所以用起来可能不太方便。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。