1 Prototype Pattern 原型模式
目的:在运行期通过“复制和粘贴”来创建新对象 ;
实现:创建一个原型对象,再通过Java 语言中常用的克隆实现方法复制这个原型对象来创建更多同类型的对象。
2 实现
代码场景:十月革命成功后,强大的苏联让周围仍处于战乱的国家找到了奋斗的方向,众多政党纷纷参照苏联模式进行革命、制定国家发展方针政策。
2.1 代码实现
共产主义思想接口:需要有志之士来实现
public interface Communism extends Cloneable {
// 可以被模仿 从未被超越
public Communism getClone();
// 走中国特色社会主义道路
public Communism deepClone();
// 你的追求
public String getBelief();
// 报上你的情况 为了演示 浅克隆 深克隆
public NationalSituation getNationalSituation();
}
苏联大哥:为了实现深克隆,实现了Serializable
public class Soviet implements Communism ,Serializable{
private static final long serialVersionUID = 5344691963719733036L;
private NationalSituation ns = new NationalSituation();
private String belief;
public Soviet(String belief, String nationalArea, String nationalPopulation, String technicalLevel) {
ns.setNationalArea(nationalArea);
ns.setNationalPopulation(nationalPopulation);
ns.setTechnicalLevel(technicalLevel);
this.belief = belief;
}
@Override
public Communism getClone() {
Communism c = null;
try {
c = (Communism) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("不支持复制!");
return null;
}
return c;
}
@Override
public String getBelief() {
System.out.println("我是国土面积:[" + ns.getNationalArea()
+ "]"+ ",人口:[" + ns.getNationalPopulation()
+ "],我的信仰是:实现[" + belief+"]");
return belief;
}
@Override
public NationalSituation getNationalSituation() {
return ns;
}
// 使用序列化技术实现深克隆
public Communism deepClone() {
// 将对象写入流中
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos;
ByteArrayInputStream bis;
try {
oos = new ObjectOutputStream(bao);
oos.writeObject(this);
// 将对象从流中取出
bis = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Communism) ois.readObject();
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
return null;
}
}
}
私有属性:国情,为了实现深克隆实现了Serializable
public class NationalSituation implements Serializable{
private static final long serialVersionUID = 1864667007956857078L;
// 人口
private String NationalPopulation;
// 国土面积
private String NationalArea;
// 技术水平
private String TechnicalLevel;
public String getNationalPopulation() {
return NationalPopulation;
}
public void setNationalPopulation(String nationalPopulation) {
NationalPopulation = nationalPopulation;
}
public String getNationalArea() {
return NationalArea;
}
public void setNationalArea(String nationalArea) {
NationalArea = nationalArea;
}
public String getTechnicalLevel() {
return TechnicalLevel;
}
public void setTechnicalLevel(String technicalLevel) {
TechnicalLevel = technicalLevel;
}
}
2.2 涉及角色
在原型模式结构图中包含如下几个角色:
Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。
ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
Client(客户类):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类 Prototype 编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。
2.3 调用
public class Client {
public static void main(String[] args) {
// 苏联
Communism soviet = new Soviet("共产主义", "很大很大", "2亿", "牛逼");
// 中国
Communism china = soviet.deepClone();
// 朝鲜
Communism Korea = soviet.getClone();
// 越南
Communism vietnam = soviet.getClone();
System.out.println("-------------苏联--------------");
String sovietBelief = soviet.getBelief();
System.out.println("-------------中国--------------");
String chinaBelief = china.getBelief();
System.out.println("-------------朝鲜--------------");
String koreaBelief = Korea.getBelief();
System.out.println("-------------浅克隆--------------");
// 查看是否指向一个地址
if (soviet == Korea) {
System.out.println("soviet == Korea");
} else {
System.out.println("soviet != Korea");
}
// 查看内部属性是否指向一个地址
if (sovietBelief == koreaBelief) {
System.out.println("sovietBelief == koreaBelief");
} else {
System.out.println("sovietBelief != koreaBelief");
}
// 查看内部属性是否指向一个地址
if (soviet.getNationalSituation() == Korea.getNationalSituation()) {
System.out.println("soviet.getNationalSituation == Korea.getNationalSituation");
} else {
System.out.println("soviet.getNationalSituation != Korea.getNationalSituation");
}
System.out.println("-------------深克隆--------------");
System.out.println("-及时修正路线:农村包围城市,中国特色社会主义-");
// 查看是否指向一个地址
if (soviet == china) {
System.out.println("soviet == china");
} else {
System.out.println("soviet != china");
}
// 查看内部属性是否指向一个地址
if (sovietBelief == chinaBelief) {
System.out.println("sovietBelief == chinaBelief");
} else {
System.out.println("sovietBelief != chinaBelief");
}
// 查看内部属性是否指向一个地址
if (soviet.getNationalSituation() == china.getNationalSituation()) {
System.out.println("soviet.getNationalSituation == china.getNationalSituation");
} else {
System.out.println("soviet.getNationalSituation != china.getNationalSituation");
}
}
}
结果:
-------------苏联--------------
我是国土面积:[很大很大],人口:[2亿],我的信仰是:实现[共产主义]
-------------中国--------------
我是国土面积:[很大很大],人口:[2亿],我的信仰是:实现[共产主义]
-------------朝鲜--------------
我是国土面积:[很大很大],人口:[2亿],我的信仰是:实现[共产主义]
-------------浅克隆--------------
soviet != Korea
sovietBelief == koreaBelief
soviet.getNationalSituation == Korea.getNationalSituation
-------------深克隆--------------
-及时修正路线:农村包围城市,中国特色社会主义-
soviet != china
sovietBelief != chinaBelief
soviet.getNationalSituation != china.getNationalSituation
朝鲜执行的是浅克隆,中国执行的是深克隆。
3 浅克隆&深克隆
3.1 浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制,如图所示:
在 Java 语言中,通过覆盖 Object 类的 clone() 方法可以实现浅克隆。
一般而言,Java 语言中的 clone() 方法满足:
(1) 对任何对象 x,都有 x.clone() != x,即克隆对象与原型对象不是同一个对象;
(2) 对任何对象 x,都有 x.clone().getClass() == x.getClass(),即克隆对象与原型对象的类型一样;
(3) 如果对象 x 的 equals() 方法定义恰当,那么 x.clone().equals(x) 应该成立。为了获取对象的一份拷贝,我们可以直接利用 Object 类的 clone() 方法,具体步骤如下:
(1) 在派生类中覆盖基类的 clone() 方法,并声明为 public;
(2) 在派生类的 clone() 方法中,调用 super.clone();
(3)派生类需实现 Cloneable 接口。
此时,Object 类相当于抽象原型类,所有实现了 Cloneable 接口的类相当于具体原型类。
3.2 深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制,如图所示:
在 Java 语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现 Serializable 接口,否则无法实现序列化操作。
参考文献:
[ 1 ] 图解设计模式/(日)结城浩著;杨文轩译。–北京:人民邮电出版社,2017.1.
[ 2 ] 维基百科 设计模式
[ 3 ] 极客学院WIKI–设计模式.
[ 4 ] 菜鸟教程–设计模式.