目录
-
原型模式概述(Prototype Pattern)
用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创 建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
介绍
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除原型。
何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码: 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。
使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
实现
我们将创建一个抽象类 Shape 和扩展了 Shape 类的实体类。下一步是定义类 ShapeCache,该类把 shape 对象存储在一个 Hashtable 中,并在请求的时候返回它们的克隆。
PrototypePatternDemo 类使用 ShapeCache 类来获取 Shape 对象。
开发中的陷阱
public class Demo1 {
// public static void main(String[] args) {
// HashMap hm1 = new HashMap();
// hm1.put("name","zs");
// hm1.put("sex","女");
// HashMap hm2 = hm1;
// hm1.put("age","18");
// hm2.put("like","男");
// System.out.println(hm1);
// System.out.println(hm2);
// }
public static void main(String[] args) {
HashMap hm1 = new HashMap();
hm1.put("name","zs");
hm1.put("sex","女");
HashMap hm2 = (HashMap) hm1.clone();
hm1.put("age","18");
hm2.put("like","男");
System.out.println(hm1);
System.out.println(hm2);
}
}
这两个代码显示的效果不相同
第一种达不到你想想要的效果 详细见效果图
图1.
图2.
原因是因为这个两个hm1和hm2的在栈中的地址是一样的,而在对hm1和hm2的操作,其实是对同一个对象进行操作
想要我们理想中的值就得使用第二串代码
案例
需求:将一只名字为杰克、性别为母的绵羊克隆10份;
要求每只绵羊的属性、性别都一致
-
public class Sheep { private String name; private String sex; public Sheep(String name, String sex) { this.name = name; this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Sheep{" + "name='" + name + '\'' + ", sex='" + sex + '\'' + '}'; } }
使用前
我们需要手动创建十个对象,再进行赋值操作
/** 将一只名字为杰克、性别为母的绵羊克隆10份; * 要求每只绵羊的属性、性别都一致; * * 弊端:无法将当前的状态进行复制 */ public class Client { public static void main(String[] args) { Sheep sheep1 = new Sheep("杰西", "母"); Sheep sheep2 = new Sheep("杰西", "母"); Sheep sheep3 = new Sheep("杰西", "母"); Sheep sheep4 = new Sheep("杰西", "母"); Sheep sheep5 = new Sheep("杰西", "母"); Sheep sheep6 = new Sheep("杰西", "母"); Sheep sheep7 = new Sheep("杰西", "母"); Sheep sheep8 = new Sheep("杰西", "母"); Sheep sheep9 = new Sheep("杰西", "母"); Sheep sheep10 = new Sheep("杰西", "母"); // 此时我要一只名为杰瑞的绵羊,其它绵羊属性与杰西一致; // 那么按照这种设计,只能这么创建所需的绵羊 // 这种方式创建,目前只有两个属性问题不大,如果绵羊类有十几二十甚至更多的属性,那么是非常不方便的 Sheep sheep11 = new Sheep("杰瑞", "母"); } }
使用后
在实体类中实现Cloneable类并写一个拷贝方法(方法名自定义)
-
public class Sheep implements Cloneable{ private String name; private String sex; public Sheep(String name, String sex) { this.name = name; this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Sheep{" + "name='" + name + '\'' + ", sex='" + sex + '\'' + '}'; } @Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone(); System.out.println("被克隆了..."); return obj; } }
/** 将一只名字为杰克、性别为母的绵羊克隆10份; * 要求每只绵羊的属性、性别都一致; * * 使用原型设计模式进行设计后的测试 */ public class Client { public static void main(String[] args) throws Exception{ Sheep sheep1 = new Sheep("杰西", "母"); Sheep sheep2 = (Sheep) sheep1.clone(); Sheep sheep3 = (Sheep) sheep1.clone(); Sheep sheep4 = (Sheep) sheep1.clone(); Sheep sheep5 = (Sheep) sheep1.clone(); Sheep sheep6 = (Sheep) sheep1.clone(); Sheep sheep7 = (Sheep) sheep1.clone(); Sheep sheep8 = (Sheep) sheep1.clone(); Sheep sheep9 = (Sheep) sheep1.clone(); Sheep sheep10 = (Sheep) sheep1.clone(); System.out.println(sheep1); System.out.println(sheep2); // 此时我要一只名为杰瑞的绵羊,其它绵羊属性与杰西一致; // 按照原型设计模式,调用方Client类无需查找杰西相同部分的属性,只需变动差异部分属性进行克隆即可; // 这种设计,目前只有两个属性使用起来感觉没多大区别,如果绵羊类有十几二十甚至更多的属性,那么感觉非常明显 sheep1.setName("杰瑞");//其它的属性不需要去关注 Sheep sheep11 = (Sheep) sheep1.clone(); System.out.println(sheep11); } }
这样一对比,明显我们创建对象的次数减少很多,而且方便我们对原型对象的维护,也不干扰克隆对象。从对象创建的角度上来说,原型模式设计让相似的类实例创建更加的便捷
-
案例2
需求:将一只名字为杰克、性别为母的绵羊克隆10份;
要求每只绵羊的属性、性别都一致;
使用前
public class Sheep {
private String name;
private String sex;
public Sheep(String name, String sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
// 将一只名字为杰克、性别为母的绵羊克隆10份;
// • 要求每只绵羊的属性、性别都一致;
//
// 弊端:无法将当前的状态进行复制
public class Client {
public static void main(String[] args) {
Sheep sheep1 = new Sheep("杰西", "母");
Sheep sheep2 = new Sheep("杰西", "母");
Sheep sheep3 = new Sheep("杰西", "母");
Sheep sheep4 = new Sheep("杰西", "母");
Sheep sheep5 = new Sheep("杰西", "母");
Sheep sheep6 = new Sheep("杰西", "母");
Sheep sheep7 = new Sheep("杰西", "母");
Sheep sheep8 = new Sheep("杰西", "母");
Sheep sheep9 = new Sheep("杰西", "母");
Sheep sheep10 = new Sheep("杰西", "母");
// 此时我要一只名为杰瑞的绵羊,其它绵羊属性与杰西一致;
// 那么按照这种设计,只能这么创建所需的绵羊
// 这种方式创建,目前只有两个属性问题不大,如果绵羊类有十几二十甚至更多的属性,那么是非常不方便的
Sheep sheep11 = new Sheep("杰瑞", "母");
}
}
使用后
// 使用原型设计模式进行设计
public class Sheep implements Cloneable{
private String name;
private String sex;
public Sheep(String name, String sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
System.out.println("被克隆了...");
return obj;
}
}
// 将一只名字为杰克、性别为母的绵羊克隆10份;
// 要求每只绵羊的属性、性别都一致;
//
// 使用原型设计模式进行设计后的测试
//
public class Client {
public static void main(String[] args) throws Exception{
Sheep sheep1 = new Sheep("杰西", "母");
Sheep sheep2 = (Sheep) sheep1.clone();
Sheep sheep3 = (Sheep) sheep1.clone();
Sheep sheep4 = (Sheep) sheep1.clone();
Sheep sheep5 = (Sheep) sheep1.clone();
Sheep sheep6 = (Sheep) sheep1.clone();
Sheep sheep7 = (Sheep) sheep1.clone();
Sheep sheep8 = (Sheep) sheep1.clone();
Sheep sheep9 = (Sheep) sheep1.clone();
Sheep sheep10 = (Sheep) sheep1.clone();
System.out.println(sheep1);
System.out.println(sheep2);
// 此时我要一只名为杰瑞的绵羊,其它绵羊属性与杰西一致;
// 按照原型设计模式,调用方Client类无需查找杰西相同部分的属性,只需变动差异部分属性进行克隆即可;
// 这种设计,目前只有两个属性使用起来感觉没多大区别,如果绵羊类有十几二十甚至更多的属性,那么感觉非常明显
sheep1.setName("杰瑞");//其它的属性不需要去关注
Sheep sheep11 = (Sheep) sheep1.clone();
System.out.println(sheep11);
}
}
从对象创建的角度上来说,原型模式设计让相似的类实例创建更加的便捷
深层次的使用
-
浅拷贝
// 使用原型设计模式进行设计
public class Sheep implements Cloneable{
private String name;
private String sex;
private Sheep friend;
public Sheep(String name, String sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Sheep getFriend() {
return friend;
}
public void setFriend(Sheep friend) {
this.friend = friend;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", friend=" + friend +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
System.out.println("被克隆了...");
return obj;
}
}
浅拷贝特点:对象中实例变量,如果是引用变量,不会重新开辟空间
- 深拷贝
- 方式一
-
// 使用原型设计模式进行设计 public class Sheep implements Cloneable { private String name; private String sex; private Sheep friend; private Sheep boyFriend; public Sheep(String name, String sex) { this.name = name; this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Sheep getFriend() { return friend; } public void setFriend(Sheep friend) { this.friend = friend; } public Sheep getBoyFriend() { return boyFriend; } public void setBoyFriend(Sheep boyFriend) { this.boyFriend = boyFriend; } @Override public String toString() { return "Sheep{" + "name='" + name + '\'' + ", sex='" + sex + '\'' + ", friend=" + friend + '}'; } @Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone(); if (obj == null) { return obj; } else { // 被克隆出来的绵羊(目前这只绵羊的朋友还是本体的朋友) Sheep sheep = (Sheep) obj; // 将本体的朋友也克隆一份出来,给克隆羊 Sheep friend = this.getFriend(); if (friend !=null){ sheep.setFriend((Sheep) friend.clone()); } return sheep; } } } //将一只名字为杰克、性别为母的绵羊克隆10份; //• 要求每只绵羊的属性、性别都一致; // 使用原型设计模式进行设计后的测试 public class Client { public static void main(String[] args) throws Exception { Sheep sheep1 = new Sheep("杰西", "母"); Sheep friend_jiexi = new Sheep("杰西的朋友", "公"); Sheep boyFriend_jiexi = new Sheep("杰西的男朋友", "公"); sheep1.setFriend(friend_jiexi); sheep1.setBoyFriend(boyFriend_jiexi); Sheep sheep2 = (Sheep) sheep1.clone(); System.out.println("第1只叫杰西的绵羊的朋友:" + sheep1.getFriend().hashCode()); System.out.println("第2只叫杰西的绵羊的朋友:" + sheep2.getFriend().hashCode()); System.out.println("第1只叫杰西的绵羊的男朋友:" + sheep1.getBoyFriend().hashCode()); System.out.println("第2只叫杰西的绵羊的男朋友:" + sheep2.getBoyFriend().hashCode()); } }
-
方式二深拷贝的第二种方式,无论被拷贝的对象中有多少引用变量,对象序列化的方式,都可以拷贝,对应申请新的内存空间,比第一中拷贝灵活
-
注意事项和细节
-
创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
-
不用重新初始化对象,而是动态地获得对象运行时的状态
-
如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代
-
缺点
1、需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则 2、实现深克隆的时候可能需要比较复杂的代码