Java设计模式—— 原型模式
Posted 小小印z
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java设计模式—— 原型模式相关的知识,希望对你有一定的参考价值。
目录
原型模式是指用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。适合原型模式的情景如下:
- 程序需要从一个对象出发,得到若干个和其状态相同,并可独立变化其状态的对象时;
- 对象创建需要独立于它的构造方法和表示时
- 以原型对象为基础,克隆新的对象,并完善对象实例变量时
一、问题的提出
在计算机程序开发过程中,有时会遇到为一个类创建多个实例的情况,这些实例内部成员往往完全相同或有细微的差异,而且实力的创建开销比较大或者需要输入较多参数。如果能通过复制一个已创建的对象实例来重复创建多个相同的对象,这就可以大大减少创建对象的开销,这个时候就需要原型模式。
二、原型模式
原型模式是指使用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象,原型模式是一种对象创建型模式。
原型模式复制功能分为浅复制和深复制:
(1)浅复制
浅复制(Shallow Copy)是指在复制一个对象时,仅复制对象本身及其所有基本数据类型的成员变量。而对于非基本数据类型的成员变量,则只是将其引用复制过来,因此,原对象和复制后的对象会共享同一个引用对象。因此,如果原对象的引用对象发生改变,复制后的对象也会受到影响。
(2)深复制
深复制(Deep Copy)是指在复制一个对象时,不仅复制对象本身及其所有基本数据类型的成员变量,而且会对所有的非基本数据类型的成员变量进行递归复制。因此,原对象和复制后的对象不会共享任何引用对象。
三、原型模式具体实现方法
student类:
student类包含两个基本数据类型name、age、一个引用类型变量add。
package shejimoshi.yuanxing;
public class Student
String name;
int age;
Address add;
public Student(String name, int age, Address add)
this.name = name;
this.age = age;
this.add = add; //籍贯
public String getName()
return name;
public void setName(String name)
this.name = name;
public int getAge()
return age;
public void setAge(int age)
this.age = age;
public Address getAdd()
return add;
public void setAdd(Address add)
this.add = add;
Address类:
package shejimoshi.yuanxing;
class Address
String pro; //出生省
String city; //出生市
String zip; //出生地邮编
public Address(String pro, String city, String zip)
this.pro = pro;
this.city = city;
this.zip = zip;
public String getPro()
return pro;
public void setPro(String pro)
this.pro = pro;
public String getCity()
return city;
public void setCity(String city)
this.city = city;
public String getZip()
return zip;
public void setZip(String zip)
this.zip = zip;
原型复制常用方法有三种:利用构造函数方法、利用Cloneable接口方法、利用Serializable序列化接口方法。
(1)利用构造函数方法
浅复制
所需类代码如下所示:
这里写了一个构造方法,传入一个Student对象进行复制。利用已经创建好的学生对象s,构建复制对象copy_s。
package shejimoshi.yuanxing;
public class Student
String name;
int age;
Address add;
public Student(String name, int age, Address add)
this.name = name;
this.age = age;
this.add = add; //籍贯
Student(Student s)
name = s.getName();
age = s.getAge();
add = s.getAdd(); //引用地址拷贝
测试类:
public class Test
public static void main(String[] args)
Address address = new Address("辽宁","大连","1618020202");
Student s = new Student("zhangyin",20,address);
//在这里复制出一个对象
Student copy_s = new Student(s);
System.out.println(copy_s.getName());
System.out.println(copy_s.getAge());
System.out.println(copy_s.getAdd().city);
深复制
在深复制中其实就是复制引用类型对象时创建了一个新的
public class Student
String name;
int age;
Address add;
public Student(String name, int age, Address add)
this.name = name;
this.age = age;
this.add = add; //籍贯
Student(Student s)
name = s.getName();
age = s.getAge();
add = new Address(s.getAdd());
class Address
String pro; //出生省
String city; //出生市
String zip; //出生地邮编
public Address(String pro, String city, String zip)
this.pro = pro;
this.city = city;
this.zip = zip;
public Address(Address address)
pro = address.getPro();
city = address.getCity();
zip = address.getZip();
//省略setter getter
(2)利用Cloneable接口方法
Java类都继承自Object类。事实上,Object类提供了一个clone() 方法,可以将一个Java对象复制一份,因此在Java中可以直接用Object提供clone()方法来实现对象的克隆。但是clone是一个protected的方法,外部类不能直接调用。在此Java规定了对象复制规范:能够实现复制的Java类必须实现一个标识接口Cloneable。
该接口中没有定义任何方法,因此它仅是起到一个标识作用,表达的语义是:该类用到了对象复制功能,因此抛开本模式而言,空接口有时也是非常有意义的。
浅复制
public class Student implements Cloneable
String name;
int age;
Address add;
public Student(String name, int age, Address add)
this.name = name;
this.age = age;
this.add = add; //籍贯
protected Object clone() throws CloneNotSupportedException
return super.clone();
测试类:
public class Test
public static void main(String[] args) throws CloneNotSupportedException
Address address = new Address("辽宁","大连","1618020202");
Student zy = new Student("zhangyin",20,address);
//深复制
Student copy_zy = (Student) zy.clone();
System.out.println(copy_zy.getName());
System.out.println(copy_zy.getAge());
System.out.println(copy_zy.getAdd().city);
深复制
public class Student implements Cloneable
String name;
int age;
Address add;
public Student(String name, int age, Address add)
this.name = name;
this.age = age;
this.add = add; //籍贯
protected Object clone() throws CloneNotSupportedException
Student s = (Student)super.clone();
s.setAdd((Address) add.clone());
return s;
class Address implements Cloneable
String pro; //出生省
String city; //出生市
String zip; //出生地邮编
public Address(String pro, String city, String zip)
this.pro = pro;
this.city = city;
this.zip = zip;
protected Object clone() throws CloneNotSupportedException
return (Address)super.clone();
(3)利用Serializable序列化接口方法
利用构造方法、Cloneable接口方法实现对象深复制都稍显复杂,而利用Serializable序列化接口方法实现深复制要简单的多。Serializable接口同样是一个空接口,表示该对象支持序列化技术。
这是一个 Java 中的 clone() 方法的实现,它创建了一个与原始对象状态相同的新对象实例。该方法创建了一个新的 ByteArrayOutputStream 和 ObjectOutputStream 对象,用于将原始对象序列化为字节流。然后,使用 ByteArrayInputStream 和 ObjectInputStream 对字节流进行反序列化,并将结果对象作为克隆返回。
需要注意的是,该实现仅适用于被克隆的类可序列化,这意味着它实现了 java.io.Serializable 接口。如果类不可序列化,则会抛出异常。
此外,在序列化或反序列化过程中可能发生任何异常,需要进行异常处理。在此实现中,任何异常都会被捕获并使用 printStackTrace() 方法打印到标准错误输出。但这并不是一个好习惯,因为它会使错误诊断变得困难。更好的做法是抛出一个 CloneNotSupportedException 异常,并提供有意义的错误消息。
public class Student implements Cloneable, Serializable
String name;
int age;
Address add;
public Student(String name, int age, Address add)
this.name = name;
this.age = age;
this.add = add; //籍贯
protected Object clone() throws CloneNotSupportedException
//在这一行,我们创建了一个 obj 对象,用于存储克隆后的对象。
Object obj = null;
try
/*
* 这三行代码使用 Java 的序列化机制将当前对象 this 序列化为字节数组并存储在 bos 中。
* 首先,我们创建了一个 ByteArrayOutputStream 对象 bos,用于存储序列化后的字节数组。
* 接着,我们创建了一个 ObjectOutputStream 对象 oos,它的作用是将对象序列化为字节流。
* 最后,我们将当前对象 this 写入 oos 对象中,从而将其序列化为字节数组并存储在 bos 中。
* */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//从流里读回来
/*
* 这三行代码将 bos 中的字节数组反序列化为对象。
* 我们首先使用 ByteArrayInputStream 对象 bis 将字节数组包装为输入流。
* 接着,我们使用 ObjectInputStream 对象 ois,它的作用是将字节流反序列化为对象。
* 最后,我们使用 ois 对象的 readObject() 方法从输入流中读取对象并将其存储在 obj 中。
* */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
obj = ois.readObject();
catch (Exception e)
e.printStackTrace();
return obj;
class Address implements Serializable
String pro; //出生省
String city; //出生市
String zip; //出生地邮编
public Address(String pro, String city, String zip)
this.pro = pro;
this.city = city;
this.zip = zip;
Serializable接口是Java中的一个标记接口,它不包含任何方法或字段,仅仅是用于标识一个类可以被序列化。序列化是指将对象转换为可存储或传输的格式的过程,反序列化则是将这种格式的数据还原成原始的对象。
要利用Serializable接口进行序列化和反序列化,需要完成以下步骤:
-
让类实现Serializable接口 需要让需要序列化的类实现Serializable接口,这样就可以将其对象序列化为字节流并传输或存储。
-
创建ObjectOutputStream对象 在进行序列化时,需要将对象序列化为字节流并传输或存储,这需要使用ObjectOutputStream对象来实现。创建ObjectOutputStream对象时需要传入一个OutputStream对象,OutputStream对象可以是文件输出流或网络输出流。
-
调用ObjectOutputStream的writeObject方法 使用ObjectOutputStream对象的writeObject方法将需要序列化的对象写入到输出流中。
-
创建ObjectInputStream对象 在进行反序列化时,需要将字节流还原为原始对象,这需要使用ObjectInputStream对象来实现。创建ObjectInputStream对象时需要传入一个InputStream对象,InputStream对象可以是文件输入流或网络输入流。
-
调用ObjectInputStream的readObject方法 使用ObjectInputStream对象的readObject方法将从输入流中读取序列化的对象,并返回原始的对象。
下面是一个使用Serializable接口进行序列化和反序列化的示例代码:
import java.io.*;
public class SerializableExample
public static void main(String[] args) throws IOException, ClassNotFoundException
// 创建一个需要序列化的对象
Person person = new Person("Tom", 20);
// 创建ObjectOutputStream对象,将对象序列化到文件中
FileOutputStream fileOutputStream = new FileOutputStream("person.dat");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(person);
objectOutputStream.close();
// 创建ObjectInputStream对象,将文件中的字节流反序列化为对象
FileInputStream fileInputStream = new FileInputStream("person.dat");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Person person2 = (Person) objectInputStream.readObject();
objectInputStream.close();
// 输出反序列化后的对象信息
System.out.println(person2.getName()); // Tom
System.out.println(person2.getAge()); // 20
class Person implements Serializable
private String name;
private int age;
public Person(String name, int age)
this.name = name;
this.age = age;
public String getName()
return name;
public int getAge()
return age;
Java设计模式----原型模式
细胞的有丝分裂会产生两个基因相同的细胞,基因相同就表示它们一模一样。这一过程很像代码设计中的一个设计模式,即原型模式。
1.原型模式
原型模式(Prototype Pattern),就是以一个现有的对象为原型,克隆出一个或多个一模一样的对象。
原型模式的UML类图:
- Prototype(抽象原型类): 具体原型的父类,也可以是具体原型对象,提供克隆方法的接口;
- ConcretePrototype(具体原型类): 实现父类中声明的克隆方法,并返回自己的一个克隆对象;
- Client(客户端):让一个原型对象克隆自身以创建一个新的对象。
2.代码实现
2.1 通用形式
Java中所有类都继承自Object, Object类中提供了Object clone()方法,Java中,只要实现了Cloneable接口的对象就可以通过clone()方法克隆自身,一般地,原型模式如下代码所示:
abstract class Prototype implements Cloneable { public Prototype clone() throws CloneNotSupportedException { return (Prototype) super.clone(); } } class ConcretePrototype1 extends Prototype { @Override public Prototype clone() throws CloneNotSupportedException { return (ConcretePrototype1)super.clone(); } } class ConcretePrototype2 extends Prototype { @Override public Prototype clone() throws CloneNotSupportedException { return (ConcretePrototype2)super.clone(); } }
客户端调用Prototype的克隆方法:
public class ProtoTypeDemo1 { public static void main(String[] args) throws CloneNotSupportedException { Prototype proto = new ConcretePrototype1(); Prototype clone1 = proto.clone(); Prototype clone2 = proto.clone(); System.out.println(proto); System.out.println(clone1); System.out.println(clone2); } }
输出
blog.design.prototype.ConcretePrototype1@610455d6
blog.design.prototype.ConcretePrototype1@511d50c0
blog.design.prototype.ConcretePrototype1@60e53b93
可以看到,我以proto对象为原型,克隆出了两个新的对象,它们在内存中的地址不同。
2.2 浅拷贝和深拷贝
尽管Java中,clone()方法使用起来十分简便,但是需要注意的是,Object.clone()方法,默认的是浅拷贝(Shallow copy),即不会克隆原型对象中的引用变量所指向的对象;相反地,深拷贝(Deep copy),就是可以克隆原型对象中的引用变量所指向的对象。
浅拷贝与深拷贝图示:
浅拷贝就是直接把原型中的引用复制给了克隆对象,克隆对象和原型中引用类型的成员变量指向相同的堆内存地址;
深拷贝则是将原型的引用类型成员也克隆一份放到新的内存中。
通常,深拷贝需要程序员自己实现,原型的成员变量所指向的对象,可能还包含其它的引用类型的成员变量,不可能将这一连串的对象都拷贝,一般来说,只需要拷贝到业务所需要的层数即可。
3.总结
总之, 使用原型模式可以:
- 减少使用new关键字而产生的大量开销,
- 提升创建对象的效率。
特别是对于需要大量准备工作才能调用new来创建对象的场景下,使用原型模式能更加高效。
通常,原型模式也可以和工厂模式和单例模式结合起来使用,工厂根据单例对象,来克隆出一模一样的新的对象,客户端通过工厂方法获取克隆对象。
此外,组合模式、装饰器模式也可以应用原型模式使结构够高效,因为它们都涉及到创建多个大体上一模一样的对象。
以上是关于Java设计模式—— 原型模式的主要内容,如果未能解决你的问题,请参考以下文章