原型模式
原型模式的注意事项和细节
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能提高效率
- 不用重新初始化对象,而是动态的获得对象运行时的状态
- 如果原始对象发生变化(增加、减少属性),其他克隆对象也会响应的变化,无需修改代码
缺点:需要为每个类配备一个克隆方法,对全新类来说不难,但是对已有的类进行改造时,需要修改源代码。违背了 OCP(开闭原则)
一、不使用原型模式
在不使用原型模式时,我们要实例化新的对象,需要不停的 new 才能得到新的对象
下面我们看一个案例
public class Sheep {
private String name;
private int age;
private String color;
private List<String> data;
public Sheep(String name, int age, String color) {
System.out.println("开始实例化小羊....");
long start = System.currentTimeMillis();
this.name = name;
this.age = age;
this.color = color;
System.out.println("获取小羊的科研数据");
this.data = this.request();
System.out.println("小羊的科研数据获取成功");
long end = System.currentTimeMillis();
System.out.println("小羊实例化成功。" + "用时: " + ((end - start) / 1000) + "秒");
}
private long random(int max, int min) {
return (long) (Math.random() * (max - min) + min);
}
/**
* 获取克隆小羊基本数据
*/
private List<String> request() {
List<String> data = new ArrayList<>();
data.add("眼睛: 深棕色");
data.add("身高: 166");
data.add("身体健康");
try {
Thread.sleep(random(5000, 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return data;
}
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 String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public List<String> getData() {
return data;
}
public void setData(List<String> data) {
this.data = data;
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
long start = System.currentTimeMillis();
int a = 5;
for (int i = 0; i < a; i++) {
Sheep sheep = new Sheep("小羊", 18, "white");
System.out.println(sheep.getName());
System.out.println(sheep.toString());
System.out.println("########################################");
}
long end = System.currentTimeMillis();
System.out.println("本次创建小羊。" + a + "只,用时: " + ((end - start) / 1000) + "秒");
}
}
console
开始实例化小羊....
获取小羊的科研数据
小羊的科研数据获取成功
小羊实例化成功。用时: 2秒
小羊
org.example.prototype.example1.Sheep@4554617c
########################################
开始实例化小羊....
获取小羊的科研数据
小羊的科研数据获取成功
小羊实例化成功。用时: 4秒
小羊
org.example.prototype.example1.Sheep@74a14482
########################################
开始实例化小羊....
获取小羊的科研数据
小羊的科研数据获取成功
小羊实例化成功。用时: 4秒
小羊
org.example.prototype.example1.Sheep@1540e19d
########################################
开始实例化小羊....
获取小羊的科研数据
小羊的科研数据获取成功
小羊实例化成功。用时: 1秒
小羊
org.example.prototype.example1.Sheep@677327b6
########################################
开始实例化小羊....
获取小羊的科研数据
小羊的科研数据获取成功
小羊实例化成功。用时: 3秒
小羊
org.example.prototype.example1.Sheep@14ae5a5
########################################
本次创建小羊。5只,用时: 16秒Process finished with exit code 0
二、使用原型模式 (浅拷贝)
实现
Cloneable
接口-
提供一个 clone 方法
@Override protected Object clone() throws CloneNotSupportedException { return super.clone(); }
当我们每次创建小羊都要去获取小羊时,我们每次都需要去获取小羊的基本信息。这将会非常的耗时。我们一共创建了 5 只羊,耗时了16秒。下面我们使用克隆的方式来直接克隆小羊
/**
* 小羊需要实现 Cloneable 接口
* Cloneable 接口并没有任何的方法
* <p> public interface Cloneable {} </p>
* 实现的目的仅仅是标注 Sheep 类可以通过 clone 来获得新的小羊
*/
public class Sheep implements Cloneable{
private String name;
private int age;
private String color;
private List<String> data;
public Sheep(String name, int age, String color) {
System.out.println("开始实例化小羊....");
long start = System.currentTimeMillis();
this.name = name;
this.age = age;
this.color = color;
System.out.println("获取小羊的科研数据");
this.data = this.request();
System.out.println("小羊的科研数据获取成功");
long end = System.currentTimeMillis();
System.out.println("小羊实例化成功。" + "用时: " + ((end - start) / 1000) + "秒");
}
private long random(int max, int min) {
return (long) (Math.random() * (max - min) + min);
}
/**
* 获取克隆小羊基本数据
*/
private List<String> request() {
List<String> data = new ArrayList<>();
data.add("眼睛: 深棕色");
data.add("身高: 166");
data.add("身体健康");
try {
Thread.sleep(random(5000, 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return data;
}
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 String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public List<String> getData() {
return data;
}
public void setData(List<String> data) {
this.data = data;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
long start = System.currentTimeMillis();
int a = 5;
Sheep sheep = new Sheep("小羊", 18, "white");
for (int i = 0; i < a; i++) {
Sheep clone = (Sheep) sheep.clone();
System.out.println(clone.getName());
System.out.println(clone.toString());
System.out.println("########################################");
}
long end = System.currentTimeMillis();
System.out.println("本次克隆小羊。" + a + "只,用时: " + ((end - start) / 1000) + "秒");
}
}
console
开始实例化小羊....
获取小羊的科研数据
小羊的科研数据获取成功
小羊实例化成功。用时: 1秒
小羊
org.example.prototype.example1.Sheep@4554617c
########################################
小羊
org.example.prototype.example1.Sheep@74a14482
########################################
小羊
org.example.prototype.example1.Sheep@1540e19d
########################################
小羊
org.example.prototype.example1.Sheep@677327b6
########################################
小羊
org.example.prototype.example1.Sheep@14ae5a5
########################################
本次创建小羊。5只,用时: 1秒Process finished with exit code 0
通过 console 可以观察到,我们通过 clone 出的羊都是不同的羊。他们都不需要重新去走构造方法,他们也都有名字。
不过随后我们会发现,我们的 clone 方法只能 clone 最基本的属性,如果属性变成了一个引用类型。那这个引用类型被修改的时候将会影响到其他的小羊,这不是我们想要的
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
long start = System.currentTimeMillis();
int a = 5;
Sheep sheep = new Sheep("小羊", 18, "white");
Sheep clone = (Sheep) sheep.clone();
clone.setName("小羊羊");
List<String> data = clone.getData();
data.add("小羊羊拟人形态");
data.add("肤白貌美大长腿");
System.out.println(clone.getName());
System.out.println(String.join(",", clone.getData()));
System.out.println("########################################");
Sheep clone2 = (Sheep) sheep.clone();
System.out.println(clone2.getName());
System.out.println(String.join(",", clone2.getData()));
System.out.println("########################################");
long end = System.currentTimeMillis();
System.out.println("本次创建小羊。" + a + "只,用时: " + ((end - start) / 1000) + "秒");
}
}
console
开始实例化小羊....
获取小羊的科研数据
小羊的科研数据获取成功
小羊实例化成功。用时: 2秒
小羊羊
眼睛: 深棕色,身高: 166,身体健康,小羊羊拟人形态,肤白貌美大长腿
########################################
小羊
眼睛: 深棕色,身高: 166,身体健康,小羊羊拟人形态,肤白貌美大长腿
########################################
本次创建小羊。5只,用时: 2秒Process finished with exit code 0
我们更改了第一只小羊的名字,它没有影响到第二只小羊。但是我们更改它的引用对象数据的时。第二只小羊也被更改了,这不是我们想要的
三、使用原型模式(深拷贝)
在这里,我们将要解决小羊的数据修改会影响其他小羊的问题
/**
* 通过序列化实现深度克隆小羊。深度克隆方法不止一种,但是我只用了这一种简洁的方式实现
* 小羊需要实现 Serializable, Cloneable 接口
* Serializable, Cloneable 接口并没有任何的方法
* <p> public interface Serializable {} </p>
* <p> public interface Cloneable {} </p>
* 实现的目的仅仅是标注 Sheep 类可以通过 clone 来获得新的小羊
*/
public class Sheep implements Serializable, Cloneable{
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String color;
private List<String> data;
public Sheep(String name, int age, String color) {
System.out.println("开始实例化小羊....");
long start = System.currentTimeMillis();
this.name = name;
this.age = age;
this.color = color;
System.out.println("获取小羊的科研数据");
this.data = this.request();
System.out.println("小羊的科研数据获取成功");
long end = System.currentTimeMillis();
System.out.println("小羊实例化成功。" + "用时: " + ((end - start) / 1000) + "秒");
}
private long random(int max, int min) {
return (long) (Math.random() * (max - min) + min);
}
/**
* 获取克隆小羊基本数据
*/
private List<String> request() {
List<String> data = new ArrayList<>();
data.add("眼睛: 深棕色");
data.add("身高: 166");
data.add("身体健康");
try {
Thread.sleep(random(5000, 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return data;
}
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 String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public List<String> getData() {
return data;
}
public void setData(List<String> data) {
this.data = data;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* 通过对象序列化实现深克隆
*/
public Object deepClone() {
/* 创建流对象*/
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
// 序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); // 当前对象一对象流的方式进行输出
// 反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (bos != null ) bos.close();
if (oos != null ) oos.close();
if (bis != null ) bis.close();
if (ois != null ) ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
long start = System.currentTimeMillis();
int a = 5;
Sheep sheep = new Sheep("小羊", 18, "white");
Sheep clone = (Sheep) sheep.deepClone();
clone.setName("小羊羊");
List<String> data = clone.getData();
data.add("小羊羊拟人形态");
data.add("肤白貌美大长腿");
System.out.println(clone.getName());
System.out.println(String.join(",", clone.getData()));
System.out.println("########################################");
Sheep clone2 = (Sheep) sheep.deepClone();
System.out.println(clone2.getName());
System.out.println(String.join(",", clone2.getData()));
System.out.println("########################################");
long end = System.currentTimeMillis();
System.out.println("本次创建小羊。" + a + "只,用时: " + ((end - start) / 1000) + "秒");
}
}
console
开始实例化小羊....
获取小羊的科研数据
小羊的科研数据获取成功
小羊实例化成功。用时: 2秒
小羊羊
眼睛: 深棕色,身高: 166,身体健康,小羊羊拟人形态,肤白貌美大长腿
########################################
小羊
眼睛: 深棕色,身高: 166,身体健康
########################################
本次创建小羊。5只,用时: 3秒Process finished with exit code 0