【Java中23种面试常考的设计模式之原型模式(prototype)—创建型模式】
知识回顾:
之前我们讲过的设计模式在这里呦:
【面试最常见的设计模式之单例模式】
【面试最常见的设计模式之工厂模式】
【Java中23种面试常考的设计模式之备忘录模式(Memento)—行为型模式】
【Java中23种面试常考的设计模式之观察者模式(Observer)—行为型模式】
【Java中23种面试常考的设计模式之模板模式(Template)—行为型模式】
【Java中23种面试常考的设计模式之状态模式(State)—行为型模式】
【Java中23种面试常考的设计模式之策略模式(Strategy)—行为型模式】
【Java中23种面试常考的设计模式之迭代器模式(Iterator)—行为型模式】
【Java中23种面试常考的设计模式之访问者模式(Visitor)—行为型模式】
【Java中23种面试常考的设计模式之中介者模式(Mediator)—行为型模式】
【Java中23种面试常考的设计模式之解释器模式(Interpreter)—行为型模式】
【Java中23种面试常考的设计模式之命令模式(Command)—行为型模式】
【Java中23种面试常考的设计模式之责任链模式(Chain of Responsibility)—行为型模式】
【Java中23种面试常考的设计模式之适配器模式(Adapter)—结构型模式】
【Java中23种面试常考的设计模式之桥接模式(Bridge)—结构型模式】
【Java中23种面试常考的设计模式之组合模式(Composite)—结构型模式】
【Java中23种面试常考的设计模式之装饰器模式(Decorator)—结构型模式】
【Java中23种面试常考的设计模式之外观模式(Facade)—结构型模式】
【Java中23种面试常考的设计模式之享元模式(Flyweight)—结构型模式】
【Java中23种面试常考的设计模式之代理模式(Proxy)—结构型模式】
【Java中23种面试常考的设计模式之单例模式(Singleton)—创建型模式】
接下来我们要进行学习的是:【Java中23种面试常考的设计模式之原型模式(prototype)—创建型模式】。
原型模式
- 原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。
- 在java中我们知道通过new关键字创建的对象是非常繁琐的(类加载判断,内存分配,初始化等),在我们需要大量对象的情况下,原型模式就是我们可以考虑实现的方式。
浅拷贝和深拷贝
原型模式我们也称为克隆模式,即一个某个对象为原型克隆出来一个一模一样的对象,该对象的属性和原型对象一模一样。而且对于原型对象没有任何影响。原型模式的克隆方式有两种:浅克隆和深度克隆
原型模式 | 解释说明 |
---|---|
浅拷贝 | 只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址 |
深拷贝 | 深复制把要复制的对象所引用的对象都复制了一遍 |
解决的问题
- 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
生产开发中常用的使用场景
- 原型模式需要和工厂模式搭配起来,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
- Spring中bean的创建实际就是两种:单例模式和原型模式。
模式优点与缺点
优点
- 性能提高。
- 逃避构造函数的约束。
缺点
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
- 必须实现 Cloneable 接口。
核心角色
抽象接口的Prototype类
实现抽象接口的ConcretePrototype类
测试客户端类
UML类图
原型模式实现代码
浅克隆
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。 Object类提供的方法clone只是拷贝本对象 , 其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址
实现代码
被克隆的对象必须Cloneable,Serializable这两个接口
原型类
package com.prototype;
import java.io.Serializable;
import java.util.Date;
public class User implements Cloneable,Serializable{
private String name;
private Date birth;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 实现克隆的方法
*/
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
测试类
package com.prototype;
import java.util.Date;
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Date date=new Date();
User user = new User();
user.setName("Jack");
user.setAge(18);
user.setBirth(date);
System.out.println("----原型对象的属性------");
System.out.println(user);
System.out.println(user.getName());
System.out.println(user.getAge());
System.out.println(user.getBirth());
// 克隆对象
User user1 =(User) user.clone();
// 修改原型对象中的属性
user1.setName("ljw");
user1.setAge(19);
user1.setBirth(new Date());
System.out.println("-------克隆对象的属性-----");
System.out.println(user1);
System.out.println(user1.getName());
System.out.println(user1.getAge());
System.out.println(user1.getBirth());
}
}
运行结果:
浅克隆的问题:虽然产生了两个完全不同的对象,但是被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
深克隆
- 那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
- 深复制把要复制的对象所引用的对象都复制了一遍。
注意:深度克隆有两种实现方式,第一种是在浅克隆的基础上实现,第二种是通过序列化和反序列化实现,
实现代码1—浅克隆的基础上实现深拷贝
原型类
package com.prototype;
import java.io.Serializable;
import java.util.Date;
/**
* 原型类:被克隆的类型
* 深度克隆
*/
public class User implements Cloneable,Serializable{
private String name;
private Date birth;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 实现克隆的方法
* 深度克隆
*/
public Object clone() throws CloneNotSupportedException{
Object object = super.clone();
// 实现深度克隆(deep clone)
User user = (User)object;
user.birth = (Date) this.birth.clone();
return object;
}
}
测试类
package com.prototype;
import java.util.Date;
public class Main {
public static void main(String[] args) throws Exception {
Date date=new Date();
User user = new User();
user.setName("Jack");
user.setAge(18);
user.setBirth(date);
Thread.sleep(1000);
System.out.println("----原型对象的属性------");
System.out.println(user);
System.out.println(user.getName());
System.out.println(user.getAge());
System.out.println(user.getBirth());
// 克隆对象
User user1 =(User) user.clone();
// 修改原型对象中的属性
user1.setName("ljw");
user1.setAge(19);
user1.setBirth(new Date());
System.out.println("-------克隆对象的属性-----");
System.out.println(user1);
System.out.println(user1.getName());
System.out.println(user1.getAge());
System.out.println(user1.getBirth());
}
}
运行结果:
注意:我们发现克隆的对象的属性并没有随着我们对Date的修改而改变,说明克隆对象的Date属性和原型对象的Date属性引用的不是同一个对象,实现的深度复制。
实现代码2—序列化和反序列化基础上实现深拷贝
序列化与反序列化
类别 | 说明 |
---|---|
序列化 | 把对象转换为字节序列的过程。 |
反序列化 | 把字节序列恢复为对象的过程。 |
原型类—此时需要实现Serializable接口
package com.prototype;
import java.io.Serializable;
public class User implements Serializable{
private String name;
private Date birth;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
测试类
package com.prototype;
import java.util.Date;
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
Date date=new Date();
User user = new User();
user.setName("Jack");
user.setAge(18);
user.setBirth(date);
Thread.sleep(1000);
System.out.println("----原型对象的属性------");
System.out.println(user);
System.out.println(user.getName());
System.out.println(user.getAge());
System.out.println(user.getBirth());
// 克隆对象
//使用序列化和反序列化实现深复制
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(user);
byte[] bytes = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
//克隆好的对象!
User user1 = (User) ois.readObject();
// 修改原型对象中的属性
user1.setName("ljw");
user1.setAge(19);
user1.setBirth(new Date());
System.out.println("-------克隆对象的属性-----");
System.out.println(user1);
System.out.println(user1.getName());
System.out.println(user1.getAge());
System.out.println(user1.getBirth());
}
}
运行结果:(实现的结果和第一种实现的最后的结果一样,就是实现的方式不同)
原型模式创建对象 VS 直接new对象
package com.prototype;
import java.util.Date;
import java.io.*;
/**
* 测试普通new方式创建对象和clone方式创建对象的效率差异!
* 如果需要短时间创建大量对象,并且new的过程比较耗时。则可以考虑使用原型模式!
*/
public class Main {
public static void testNew(int size){
long start = System.currentTimeMillis();
for(int i=0;i<size;i++){
User t = new User();
}
long end = System.currentTimeMillis();
System.out.println("new的方式创建耗时:"+(end-start));
}
public static void testClone(int size) throws CloneNotSupportedException{
long start = System.currentTimeMillis();
User t = new User();
for(int i=0;i<size;i++){
User temp = (User) t.clone();
}
long end = System.currentTimeMillis();
System.out.println("clone的方式创建耗时:"+(end-start));
}
public static void main(String[] args) throws Exception {
testNew(1000);
testClone(1000);
}
}
class User implements Cloneable { //用户
public User() {
try {
Thread.sleep(10); //模拟创建对象耗时的过程!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); //直接调用object对象的clone()方法!
return obj;
}
}
运行结果
注意: 1.通过clone的方式在获取大量对象的时候性能开销基本没有什么影响,而new的方式随着实例的对象越来越多,性能会急剧下降,所以原型模式是一种比较重要的获取实例的方式。 2.通过上面的代码实验我们可以更直观的看到俩种方式创建对象的差异,当我们需要创建大量相同的是对象的时候可以选择原型模式
最后:感谢恩师,dpb,感兴趣的可以参考他的博客<波波烤鸭>
好了,到这里【Java中23种面试常考的设计模式之原型模式(prototype)—创建型模式】就结束了,23种设计模式持续更新汇总中。