设计模式之原型模式
设计模式之原型模式
1.克隆羊问题
现在有一只羊 tom,姓名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和 tom羊属性完全相同的 5只羊。
传统方式
1.1 绘制UML类图
1.2编写代码
public class Sheep {
private Integer age;
private String name;
private String color;
public Sheep(Integer age, String name, String color) {
this.age = age;
this.name = name;
this.color = color;
}
//因为篇幅限制,这里省略getter和setter、有参构造方法以及toString的编写
}
public class Client {
public static void main(String[] args) {
Sheep sheep = new Sheep(1, "tom", "白色");
Sheep sheep1 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
Sheep sheep2 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
Sheep sheep3 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
Sheep sheep4 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
System.out.println(sheep);
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(sheep4);
}
}
1.3优缺点
- 优点是比较好理解,简单易操作。
- 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
- 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活
改进的思路分析:
思路:Java 中 Object 类是所有类的根类,Object 类提供了一个 clone()方法,该方法可以将一个 Java 对象复制一份,但是需要实现
clone 的 Java 类必须要实现一个接口 Cloneable,该接口表示该类能够复制且具有复制的能力 =>原型模式
2.原型模式
2.1 概述
- 原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道创建的细节。
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
2.2 绘制UML类图
UML类图说明:
-
Protptype:抽象原型类,实现Cloneable接口,也就是我们用户使用复制粘贴的功能。
-
ConcretePrototype: 具体的原型类, 实现一个克隆自己的操作,说明一下它有被克隆复制的功能。
-
Client:客户类提出创建对象的请求。
2.3 代码编写
//prototype抽象原型类
public abstract class Sheep implements Cloneable{
private Integer age;
private String name;
private String color;
private Sheep friend;//如果成员变量为对象,默认为浅拷贝
public Sheep(Integer age, String name, String color) {
this.age = age;
this.name = name;
this.color = color;
}
//重写clone方法
@Override
protected Sheep clone() throws CloneNotSupportedException {
Sheep sheep =null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
//具体原型角色ConcretePrototype
public class ConcretePrototype extends Sheep {
public ConcretePrototype(Integer age, String name, String color) {
super(age, name, color);
}
}
//Client
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
ConcretePrototype concretePrototype = new ConcretePrototype(20,"汤姆","蓝色");
concretePrototype.setFriend(new Sheep(21,"朋友","白色"));
ConcretePrototype clone = (ConcretePrototype) concretePrototype.clone();
ConcretePrototype clone1 = (ConcretePrototype) concretePrototype.clone();
ConcretePrototype clone2 = (ConcretePrototype) concretePrototype.clone();
ConcretePrototype clone3 = (ConcretePrototype) concretePrototype.clone();
System.out.println(concretePrototype+"@@@"+concretePrototype.hashCode()+"@@@"+concretePrototype.getFriend().hashCode());
System.out.println(clone+"@@@"+clone.hashCode()+"@@@"+clone.getFriend().hashCode());
System.out.println(clone1+"@@@"+clone1.hashCode()+"@@@"+clone1.getFriend().hashCode());
System.out.println(clone2+"@@@"+clone2.hashCode()+"@@@"+clone2.getFriend().hashCode());
System.out.println(clone3+"@@@"+clone3.hashCode()+"@@@"+clone3.getFriend().hashCode());
}
}
我们可以看到,克隆出来的对象属性与之前的文件属性是一样的,但两个对象的HashCode是不一样的,也就是说创建了新的对象。
但是ConcretePrototype内的friend对象拷贝的是引用。因为Object.clone()方法默认是浅拷贝。
3.浅拷贝与深拷贝
浅拷贝
-
对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
-
对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是
将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在
一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
-
我们上述案例就是浅拷贝,浅拷贝是使用默认的clone()方法来实现
深拷贝
-
复制对象的所有基本数据类型的成员变量值
-
为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就
是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝。
实现方式:
-
重写clone方法来实现深拷贝
2. 通过对象序列化方式实现深拷贝(推荐)
public class A implements Cloneable, Serializable {
//必须显式重写
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Sheep implements Cloneable,Serializable{
private Integer age;
private String name;
private String color;
private A a;//如果成员变量为对象,默认为浅拷贝
public Sheep(Integer age, String name, String color) {
this.age = age;
this.name = name;
this.color = color;
}
//方式一:重写clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Sheep sheep =null;
try {
//这里完成对基本数据类型和String的拷贝
sheep = (Sheep) super.clone();
//对引用类型的属性,进行单独处理
A a1 = (A) a.clone();
sheep.setA(a1);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
//方式二:通过对象的序列化方式实现(推荐)
public Object deepClone(){
//创建流对象
ByteArrayOutputStream bos =null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
Sheep obj = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
//将当前这个对象以对象流的方式输出
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
obj = (Sheep)ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
finally {
try {
if(bos!=null){
bos.close();
}
if(oos!=null){
oos.close();
}
if(bis!=null){
bis.close();
}
if(ois!=null){
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return obj;
}
//省略getter、setter和toString
}
public class ConcretePrototype extends Sheep implements Cloneable , Serializable {
public ConcretePrototype(Integer age, String name, String color) {
super(age, name, color);
}
}
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
//方式一:重写clone方法
/*ConcretePrototype concretePrototype = new ConcretePrototype(20,"汤姆","蓝色");
concretePrototype.setA(new A());
ConcretePrototype clone = (ConcretePrototype) concretePrototype.clone();
ConcretePrototype clone1 = (ConcretePrototype) concretePrototype.clone();
ConcretePrototype clone2 = (ConcretePrototype) concretePrototype.clone();
ConcretePrototype clone3 = (ConcretePrototype) concretePrototype.clone();
System.out.println(concretePrototype+"@@@"+concretePrototype.hashCode());
System.out.println(clone+"@@@"+clone.hashCode());
System.out.println(clone1+"@@@"+clone1.hashCode());
System.out.println(clone2+"@@@"+clone2.hashCode());
System.out.println(clone3+"@@@"+clone3.hashCode());*/
//方式二:序列化
ConcretePrototype concretePrototype = new ConcretePrototype(20,"汤姆","蓝色");
concretePrototype.setA(new A());
ConcretePrototype clone = (ConcretePrototype) concretePrototype.deepClone();
ConcretePrototype clone1 = (ConcretePrototype) concretePrototype.deepClone();
ConcretePrototype clone2 = (ConcretePrototype) concretePrototype.deepClone();
ConcretePrototype clone3 = (ConcretePrototype) concretePrototype.deepClone();
System.out.println(concretePrototype+"@@@"+concretePrototype.hashCode());
System.out.println(clone+"@@@"+clone.hashCode());
System.out.println(clone1+"@@@"+clone1.hashCode());
System.out.println(clone2+"@@@"+clone2.hashCode());
System.out.println(clone3+"@@@"+clone3.hashCode());
}
}
运行结果:
方式一:
方式二:
注意:
- A类,Sheep类,ConcretePrototype类都需要实现Cloneable和Serializable接口
- 需要显式重写A类的clone方法,因为Object中的clone方法是protected修饰的,而protected修饰的方法只允许同包下的类和子类访问,Sheep类就调用不了A的clone方法,所以必须显示重写A类的方法。
4.注意事项
-
创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
-
不用重新初始化对象,而是动态地获得对象运行时的状态
-
如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
-
在实现深克隆的时候可能需要比较复杂的代码
-
缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则,