原型模式
介绍
原型模式是指定类型,通过克隆创建大量的同类型对象的一种设计模式。原型模式的核心是原型类(Prototype),原型类实现了Cloneable接口并重写了Object类中的clone方法。
一般我们可以将原型类定义为一个接口,实现Cloneable接口,然后将需要大量克隆对象的类实现原型类,并重写克隆方法,这样该类new的对象就可以通过克隆方法克隆大量对象。相比直接new出来的对象,使用克隆方法性能更好。
相关代码可查看GitHub
细节说明
Cloneable接口的作用
程序运行时,虚拟机会检查对象是否实现Cloneable接口,实现该接口的对象可以使用Object的clone()方法,返回一个对象的拷贝。如果没有实现该接口,调用clone方法就会抛出CloneNotSupportedException异常。
那为什么所有的对象都可以调用clone方法呢?因为Java中所有对象都是继承的Object,clone方法在Object中已经实现。
如果你去java.lang包下面找到Cloneable接口,会发现这个接口是一个空接口。我们有理由怀疑这个接口在代码编译时是不起作用的,在虚拟机运行时发光发热。
原型模式类型
简单模式
简单模式包含如下角色:
- Prototype:原型接口,实现了Cloneable接口
- Concrete Prototype:具体原型,被复制的对象,实现了原型接口并重写了clone方法。
- client:使用原型的客户,说白了就是调用者。
代码示例
//原型接口
public interface Prototype extends Cloneable {
Prototype clone();
}
//具体实现类
public class ConcretePrototype implements Prototype {
// get set方法已省略
private String type;
public void show(){
System.out.println("原型模式实现类");
}
@Override
public Prototype clone() {
Prototype prototype = null;
try {
prototype = (Prototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
/**
* 判断克隆方法是否调用构造创建对象
*/
public ConcretePrototype() {
System.out.println("构造方法被调用");
}
public static void main(String[] args) {
ConcretePrototype concretePrototype = new ConcretePrototype();
ConcretePrototype clone = (ConcretePrototype)concretePrototype.clone();
clone.show();
}
}
代码优化
public class ConcretePrototype2 implements Cloneable{
@Override
public ConcretePrototype2 clone() {
try {
return (ConcretePrototype2) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
优缺点
优点
- 使用Object的clone方法创建对象的性能比new出来的对象性能好
- new出来的对象是一个空对象,克隆出来的对象同原有对象的字段值是一模一样的,不需要像new出来的对象那样设置字段值
缺点
- 如果不重写克隆方法的话,被克隆的类中有对象但是该对象没有实现克隆方法的话,那么这个对象就不会被克隆,而是使用原有对象的地址,这样就是浅克隆。关于浅克隆、深克隆我们下面讲。
使用场景
需要大量相同对象创建的时候
登记模式
登记模式跟简单模式相比多了一个原型管理器,使用原型管理器管理对象的克隆。对象既然要管理,那就肯定不是一个对象,使用Map管理多个对象。
代码示例
public class PrototypeManager {
/**
* 用来记录原型的编号同原型实例的对象关系
*/
private static Map<String, Prototype> PROTOTYPE_MAP = new HashMap<>();
/**
* 私有化构造方法,避免从外部创建实例
*/
private PrototypeManager() {
}
/**
* 向原型管理器里面添加或者修改原型实例
*
* @param prototypeId 原型编号
* @param prototype 原型实例
*/
public static void setProtoType(String prototypeId, Prototype prototype) {
PROTOTYPE_MAP.put(prototypeId, prototype);
}
/**
* 根据原型编号从原型管理器里面移除原型实例
*
* @param prototypeId 原型编号
*/
public static void removePrototype(String prototypeId) {
PROTOTYPE_MAP.remove(prototypeId);
}
/**
* 根据原型编号获取原型实例
*
* @param prototypeId 原型编号
* @return 原型实例对象
* @throws Exception 如果根据原型编号无法获取对应实例,则提示异常“您希望获取的原型还没有注册或已被销毁”
*/
public static Prototype getPrototype(String prototypeId) throws Exception {
Prototype prototype = PROTOTYPE_MAP.get(prototypeId);
if (prototype == null) {
throw new Exception("您希望获取的原型还没有注册或已被销毁");
}
return prototype;
}
}
// 测试方法
@Test
public void registerTest() throws Exception {
PrototypeManager.setProtoType("1",new ConcretePrototype());
PrototypeManager.setProtoType("2",new ConcretePrototype2());
Prototype prototype = PrototypeManager.getPrototype("1").clone();
Prototype prototype2 = PrototypeManager.getPrototype("2").clone();
System.out.println(prototype);
System.out.println(prototype2);
}
克隆方式
深克隆是递归复制被克隆对象所有的层级从而克隆出来一个新对象,浅克隆是只复制被克隆对象的的这一层级。被克隆对象中字段有对象的话,就是克隆对象的地址,实际上克隆出来的对象中的对象字段同被克隆对象使用的是同一对象。
浅克隆深克隆都有相关的工具类,浅克隆可以使用spring-beans中的BeanUtils工具类,深克隆可以使用common-lang3里面的SerializationUtils工具类。
浅克隆
只克隆被克隆对象的第一层级,实现方法很简单,只需要将被克隆对象实现Cloneable方法,重写clone方法即可
举一个类似场景,一个员工管理平台,里面有一个员工类和一个部门类,员工类里面组合了部门类,这个时候我们对员工类进行克隆,看一下克隆出来的对象的部门信息是否共用
代码示例
// 员工类
public class Person implements Cloneable{
private String name;
private Integer age;
private Department department;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
// 省略 get set
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", department=" + department +
'}';
}
}
// 部门类
public class Department {
private String name;
private String desc;
// 省略get set
}
// 测试方法
@Test
public void shallowCloning() throws CloneNotSupportedException {
Department department = new Department();
department.setName("互联网部门");
department.setDesc("互联网部门说明");
Person person = new Person();
person.setName("小明");
person.setAge(22);
person.setDepartment(department);
Person personClone = (Person) person.clone();
System.out.println(person);
System.out.println(personClone);
}
打印结果
Person{name='小明', age=22, department=com.hello.designPattern.PrototypePattern.model.Department@78e03bb5}
Person{name='小明', age=22, department=com.hello.designPattern.PrototypePattern.model.Department@78e03bb5}
可以看到,部门类的地址相同,说明克隆对象和被克隆对象是同一个。接下来我们使用spring提供的BeanUtils工具类查看克隆结果
代码示例
@Test
public void shallowCloning() throws CloneNotSupportedException {
Department department = new Department();
department.setName("互联网部门");
department.setDesc("互联网部门说明");
Person person = new Person();
person.setName("小明");
person.setAge(22);
person.setDepartment(department);
// Person person1 = (Person) person.clone();
Person person1 = new Person();
BeanUtils.copyProperties(person,person1);
System.out.println(person);
System.out.println(person1);
}
打印结果
Person{name='小明', age=22, department=com.hello.designPattern.PrototypePattern.model.Department@17d99928}
Person{name='小明', age=22, department=com.hello.designPattern.PrototypePattern.model.Department@17d99928}
可以看到相同的结果
深克隆
递归克隆所有的信息,有两种方法,一种是类实现Cloneable 和Serializeable接口,使用ObjectInputStream和ObjectOutputStream重写clone方法,另一种就是直接使用工具类
代码示例
// person类clone方法重写
@Override
public Object clone() throws CloneNotSupportedException {
// 浅克隆
// return super.clone();
// 深克隆
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return super.clone();
}
@Test
public void deepCloning() throws CloneNotSupportedException {
Department department = new Department();
department.setName("互联网部门");
department.setDesc("互联网部门说明");
Person person = new Person();
person.setName("小明");
person.setAge(22);
person.setDepartment(department);
System.out.println(person);
Person personClone = (Person) person.clone();
System.out.println(personClone);
byte[] serialize = SerializationUtils.serialize(person);
Person deserialize = (Person) SerializationUtils.deserialize(serialize);
System.out.println(deserialize);
}
结果打印
Person{name='小明', age=22, department=com.hello.designPattern.PrototypePattern.model.Department@78e03bb5}
Person{name='小明', age=22, department=com.hello.designPattern.PrototypePattern.model.Department@311d617d}
Person{name='小明', age=22, department=com.hello.designPattern.PrototypePattern.model.Department@4cc77c2e}
三个类的地址都不一样,深克隆陈工