signed

QiShunwang

“诚信为本、客户至上”

设计模式(五)—— 原型模式(定义、案例分析、特点、缺点)

2021/5/14 20:55:24   来源:

文章目录

  • 前言
  • 正文
    • 一、定义
    • 二、模式结构及分析
      • (一) 模式结构
      • (二) 模式分析
    • 三、情景假设
    • 四、情景分析
      • (一) 浅克隆
      • (三) 深克隆
    • 五、模式优点、缺点
      • (一)模式优点
      • (二)模式缺点
    • 五、使用情景
    • 六、延申及拓展
  • 总结


前言

文章内容主要参考了刘伟主编的《设计模式(第2版)》,同时也结合了自己的一些思考和理解,希望能帮到大家。


本篇文章讲解的是原型模式。

正文

一、定义

原型模式(Prototype Pattern):原型模式是一种对象创建型模式,用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
原型模式允许通过一个原型对象创建一个或多个同类型的其他对象,而无须知道任何创建的细节;有些对象的创建过程较为复杂,而且需要频繁创建,这时候就可以利用原型模式根据原型进行创建。

二、模式结构及分析

因为模式比较简单,所以先讲解模式,然后再根据案例进一步细化拓展

(一) 模式结构

在这里插入图片描述

主要包含的角色

  • Prototype:抽象原型类
  • ConcretePrototype:具体原型类
  • Client:客户类

解释:
首先原型需要复制,很明显复制的方法应该是原型本身提供。所以可以在定义一个抽象原型类Prototype,然后具体实现类各自实现各自的内容,当然也包括克隆方法。之后再客户端client处,创建了一个具体原型,然后就可以调用克隆方法生成多一个克隆对象。

(二) 模式分析

  1. 所有的Java类都继承自java.lang.Object,而Object类提供一个clone()方法,可以将一个Java对象复制一份所以,我们在Java中可以直接使用Object提供的clone()方法来实现对象的克隆(浅克隆)。(这个时候Object类就是Prototype抽象类)
  2. 如果我们需要使用Object的clone()方法,那么Java类必须实现一个标识接口Cloneable,表示这个Java类支持复制,如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常
  3. 浅克隆是有问题的,深克隆是我们需要考虑和掌握的。以下案例会提及。
    • 浅克隆(Shallow Clone):当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制(可以理解为稍微复杂的对象,但是这个不完全对噢~)
    • 深克隆(Deep Clone):除了对象本身被复制外,对象所包含的所有成员变量也将被复制

三、情景假设

由于邮件对象包含的内容较多(如发送者、接收者、标题、内容、日期、附件等),某系统中现需要提供一个邮件复制功能,对于已经创建好的邮件对象,可以通过复制的方式创建一个新的邮件对象,如果需要改变某部分内容,无须修改原始的邮件对象,只需要修改复制后得到的邮件对象即可。使用原型模式设计该系统。
改案例是有两种情况讨论:复制邮件的同时不复制附件(浅克隆)和复制邮件的同时复制附件(深克隆)

四、情景分析

(一) 浅克隆

浅克隆类图
在这里插入图片描述

难度不大,Object就是Prototype抽象类,所以我们不用编写,甚至不用继承或实现,因为建立一个类默认就是继承Object类,直接编写Email具体类,重写clone方法(在这里,别以为clone有多难搞,其实本质就是调用父类自己的clone方法),其实。注意的是,要想client成功调用clone方法必须implements Clonable标识接口(标识接口意思是这个接口其实点开看源码里面是全空的,它只是起了一个标记的作用,类似有了一个声明,jvm执行clone方法就会知道它是支持的。)

//Email类
public class Email implements Clonable
{
	private Attachment attachment = null;//附件类,只是一个辅助类,表示是一个附件
	public Email(){
		this.attachment = new Attachment();
	}
	public Object clone(){
		Email clone = null;
		try{
			clone = (Email)super.clone();
		} catch (CloneNotSupportedException e){
			System.out.println("Clone failure");
		}
		return clone;
	}
	public Attachment getAttachment(){
		return this.attachment;
	}
	public void display(){
		System.out.println("查看邮件");
	}
	
}

接下来就是辅助类:

//附件类Attachment
public class Attachment
{
	
	public void download(){
		System.out.println("下载附件");
	}
}

接下来是客户端的代码:

```java
public class Client
{
	public static void main(String args[])
	{
         Email email,copyEmail;
         email = new Email();
         copyEmail = (Email)email.clone();
         System.out.println(emial==copyEmail);
         System.out.println(email.getAttachment()==copyEmail.getAttachment());
	}
}

在这里如果运行client,将会输出false和true。首先第一个false起码说明了我们确实创建了一个和之前Email不同的克隆Email,但是第二个true就说明了浅克隆的问题,Attachment是自建对象,在email对象中存储的是引用,而复制之后依旧是复制的是引用,而不是创建一个全新的Attachment对象,(至于为什么这个是存储对象的引用而不是对象本身,可以去搜索java对象的引用了解一下。其实可以这么认为,基本数据类型就是直接复制内容的,除此之外就是复制引用的,所以都会返回true)。
接下里就讲解深克隆(复制邮件的同时也会复制里面的所有附件):

(三) 深克隆

深克隆类图(其实只给了小部分)
在这里插入图片描述
注意上图:只给了重点几个类关系,重点就是邮件类和附件类都需要实现Serializable标识接口,接下来看实现:

//Email类
public class Email implements Serializable
{
	private Attachment attachment = null;//附件类,只是一个辅助类,表示是一个附件
	public Email(){
		this.attachment = new Attachment();
	}
	public Object deepClone() throws IOException,ClassNotFoundException,OptionalDataException{
		//将对象写入流中
		ByteArrayOutputStream bao = new ByteArrayOutputStream();//将流写出到字节数组
		ObjectOutputStream oos = new ObjectOutputStream(bao);//处理流封装,以对象的方式输出
		oos.writeObject(this);//处理之后,就可以将对象输出到字节数组
		
		//将对象从流中读取
		ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());//从字节数组中获取流,这里其实就相当于复制了
		ObjectInputStream ois = new ObjectInputStream(bis);//将获取的流处理封装,封装成对象
		return (ois.readObject());//读取对象并输出
	}
	public Attachment getAttachment(){
		return this.attachment;
	}
	public void display(){
		System.out.println("查看邮件");
	}
	
}

在这里,其实我们是利用序列化的方式,然后将对象写成流,复制,然后转成对象,这样就不是单纯的引用了。

接下来就是辅助类:

//附件类Attachment
public class Attachment
{
	public void download(){
		System.out.println("下载附件");
	}
}

接下来是客户端的代码:

public class Client
{
	public static void main(String args[])
	{
         Email email,copyEmail;
         email = new Email();
         copyEmail = (Email)email.clone();
         System.out.println(emial==copyEmail);
         System.out.println(email.getAttachment()==copyEmail.getAttachment());
	}
}

这里的输出就都是false了,因为Email里面的附件复制时我们也是用流的方式搞定的。特别注意,我们想要通过序列化的方式进行复制,就是将整个对象进行序列化,所以Email自己要实现Serializable表示接口(没错,还是标识的作用,并不需要实现具体方法),而我们序列化的过程中,attachment必然也要序列化所以,Attachment也要实现序列化接口。

五、模式优点、缺点

(一)模式优点

  • 简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
  • 扩展性较好
  • 简化创建结构,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
  • 可以使用深克隆的方式保存对象的状态,以便在需要的时候使用,可辅助实现撤销操作

(二)模式缺点

  • 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则
  • 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦(需要克隆的对象及其内部所有对象都要保证实现了Serializable接口!!)

五、使用情景

  • 创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
  • 系统要保存对象的状态,而对象的状态变化很小
  • 需要避免使用分层次的工厂类来创建分层次的对象

六、延申及拓展

  • 序列化及深克隆过程的理解可能需要知道java IO的知识,大家也可以搜索搜索,深克隆注释写在代码了,看看大家能不能读懂了

总结

本篇文章主要介绍设计模式的原型模式,同时附加了自己的一些小的思考,希望大家也能指出不足。