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: 不经贫寒难成人,不经世事总天真。