Java 23种设计模式之原型模式
一:简介
设计模式分为三大类:
创建型模式,共五种:工厂方法模式(已讲过)、抽象工厂模式(已讲过)、单例模式(已讲过)、建造者模式(已讲过)、原型模式。
结构型模式,共七种:适配器模式(已讲过)、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式(已讲过)、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:
二:原型模式
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象
1.关键的是需要拷贝的原型类(上图的Prototype)必须实现"java.lang.Cloneable"接口,然后重写Object类中的clone方法,从而实现类的拷贝
Cloneable是一个“标记接口”,所谓的标记接口就是该接口中没有任何内容。标记接口的作用就是为了给所有实现了该接口的类赋予一种特殊的标志。
使用
原型类:
/**
* 定义一个原型模式的原型类,实现Cloneable接口,在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
*/
public class Prototype implements Cloneable {
@NonNull
@Override
protected Prototype clone() {
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
}
实现类:
public class ConcretePrototype extends Prototype {
//姓名
private String name;
//年龄
private int age;
public ConcretePrototype(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "Prototype{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
调用:
//建立一个真实数据
ConcretePrototype concretePrototype=new ConcretePrototype("Rocky",19);
//获取一个拷贝数据
ConcretePrototype cloneP= (ConcretePrototype) concretePrototype.clone();
System.out.println("concretePrototype"+concretePrototype);
System.out.println("cloneP"+cloneP);
//结果
System.out: concretePrototypePrototype{name='Rocky', age=19}
System.out: clonePPrototype{name='Rocky', age=19}
原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。
其实我们也可以把原型类和实现类融合在一起
三:浅拷贝和深拷贝
原型模式中的拷贝对象可以分为:“浅拷贝”和“深拷贝”
浅拷贝:
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
注:浅拷贝影响的引用类型,只是拷贝一个引用类型地址,修改一个对象的会影响到这个拷贝对象
特点:
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。
(2) 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响
引用对象不实现cloneable接口,就不会实现深拷贝,对于引用对象来说就是浅拷贝
public class Subject {
private String name;
public Subject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "[Subject: " + this.hashCode() + ",name:" + name + "]";
}
}
学生对象实现了拷贝,但是里面的引用对象没有实现拷贝,这样属于浅拷贝
public class Student implements Cloneable {
//引用类型
private Subject subject;
//基础数据类型
private String name;
private int age;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
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;
}
/**
* 重写clone()方法
* @return
*/
@Override
public Object clone() {
//浅拷贝
try {
// 直接调用父类的clone()方法
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String toString() {
return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
}
}
深拷贝:
深拷贝不仅会复制成员变量为基本数据类型的值,给新对象。还会给是引用数据类型的成员变量申请储存空间,并复制引用数据类型成员变量的对象。这样拷贝出的新对象就不怕修改了引用数据类型的成员变量后,对其它拷贝出的对象造成影响了。
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
(2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
(3) 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
(4) 深拷贝相比于浅拷贝速度较慢并且花销较大。
对于深拷贝来说,对于 Student 的引用类型的成员变量 Subject ,需要实现 Cloneable 并重写 clone() 方法。
public class Subject implements Cloneable {
private String name;
public Subject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//Subject 如果也有引用类型的成员属性,也应该和 Student 类一样实现
return super.clone();
}
@Override
public String toString() {
return "[Subject: " + this.hashCode() + ",name:" + name + "]";
}
}
Student实现了拷贝,引用类型Subject学科也实现了拷贝,我们通过subject.clone(),赋值给学生Student.subject,这样拷贝出来新的的对象
public class Student implements Cloneable {
//引用类型
private Subject subject;
//基础数据类型
private String name;
private int age;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
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;
}
/**
* 重写clone()方法
* @return
*/
@Override
public Object clone() {
//深拷贝
try {
// 直接调用父类的clone()方法
Student student = (Student) super.clone();
student.subject = (Subject) subject.clone();
return student;
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String toString() {
return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
}
}
深拷贝后,不管是基础数据类型还是引用类型的成员变量,修改其值都不会相互造成影响。
END:不经清贫难成人,不经世事总天真。