5.原型模式
1. 克隆羊问题提出
现在有一只🐏,名字:tom,年龄:1,颜色:白色,编写程序创建和tom🐏 属性完全相同的10只🐏。
2. 传统方式代码实现
public class Sheep {
private String name;
private int age;
private String Color;
//1.选择三个参数实现构造方法
//2.重写toString()方法
//3.重写getter 和 setter方法
}
public class Client {
public static void main(String[] args) {
Sheep sheep1 = new Sheep("tom", 1, "白色");
Sheep sheep2 = new Sheep("tom", 1, "白色");
Sheep sheep3 = new Sheep("tom", 1, "白色");
Sheep sheep4 = new Sheep("tom", 1, "白色");
Sheep sheep5 = new Sheep("tom", 1, "白色");
//...
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(sheep4);
System.out.println(sheep5);
}
}
5.1 传统方式优缺点
1.优点是比较好理解,简易操作
2.在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂,效率较低。
3.总是需要重新初始化对象,而不是动态获取对象运行时的状态,不够灵活
4.补充加强理解:案例中若需要修改🐏的属性,那么就需要修改10只🐏,若有100只工作量将会非常大,效率极低
5.2 改进思路
Object类提供一个clone()方法,该方法可以将java对象复制一份,但是需要实现clone的java类必须实现Cloneable接口,该接口表示该类能够复制且具有复制的能力。
5.3原型模式(Prototype模式)概念
1.用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
2.允许一个对象再创建另一个可定制的对象,无需知道如何创建的细节
3.原理:通过将一个原型对象传给那个需发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
1. 原理结构图UML类图
原理结构说明:
Prototype:原型类,声明一个克隆自己的接口
ConcretePrototype:具体的原型类,实现一个克隆自己的操作
Client:让一个原型对象克隆自己,从而创建一个新的对象(属性一样)
2. 优化代码
实体类
public class Sheep implements Cloneable{
private String name;
private int age;
private String Color;
public Sheep(String name, int age, String color) {
this.name = name;
this.age = age;
Color = color;
}
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;
}
public String getColor() {
return Color;
}
public void setColor(String color) {
Color = color;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", Color='" + Color + '\'' +
'}';
}
//克隆该实例,使用默认的clone方法来完成
@Override
protected Object clone() {
Sheep sheep = null;
try {
//捕捉在克隆时可能出现的异常
sheep = (Sheep)super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.getMessage());
}
return sheep;
}
}
客户端
public class Client {
public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 1, "白色");
System.out.println("原型模式");
Sheep sheep1 = (Sheep) sheep.clone();//克隆
Sheep sheep2 = (Sheep) sheep.clone();//克隆
Sheep sheep3 = (Sheep) sheep.clone();//克隆
Sheep sheep4 = (Sheep) sheep.clone();//克隆
//...
System.out.println(sheep1);
System.out.println(sheep2);
}
}
3.原型模式优点:
1.提高创建对象的效率
2.不用重新初始化对象,而是动态地获取对象运行时的状态
3.加强理解:现在如果要修改羊的属性只需要修改第一只原型羊就可以了
4. 缺点:
需要为每一个类配一个克隆方法,但对已经有的类进行改造时,需要修改源码,违背了ocp(开闭原则)
5.4 实战Spring中使用原型模式
配置文件中以下引入bean时设置原型模式prototype
<bean id="student1" class="tech.Student" scope="prototype"> </bean>
在getBean方法中,通过getBeanFactory工厂获取bean,再往getBean方法中追踪会发现核心是doGetBean方法,在该方法中找到对应else if(mbd.isProtoType())判断为原型时(注意里面还有匹配单例的),使用createBean(beanName,mbd,args)方法获取bean原型实例。
5.5 浅拷贝
1.对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
2.对于引用类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例,在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
克隆羊就是浅拷贝
//1.Sheep类新增一个Sheep对象
public Sheep friend;//是对象,克隆是会如何处理,使用public方便此处调用
//2.客户端
public class Client {
public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 1, "白色");
sheep.friend = new Sheep("jack",2,"黑色");
Sheep sheep1 = (Sheep) sheep.clone();//克隆
Sheep sheep2 = (Sheep) sheep.clone();//克隆
Sheep sheep3 = (Sheep) sheep.clone();//克隆
Sheep sheep4 = (Sheep) sheep.clone();//克隆
//...
System.out.println("sheep1:"+sheep1+"hashCode:"+sheep1.hashCode()+"--sheep1.friend:"+sheep1.friend.hashCode());
System.out.println("sheep2:"+sheep2+"hashCode:"+sheep2.hashCode()+"--sheep2.friend:"+sheep2.friend.hashCode());
}
}
结果
sheep1:Sheep{name='tom', age=1, Color='白色'}hashCode:356573597--sheep1.friend:1735600054
sheep2:Sheep{name='tom', age=1, Color='白色'}hashCode:21685669--sheep2.friend:1735600054
从上面结果可以看出Sheep类内部的引用对象friend没有拷贝一个新对象,而是进行地址引用
5.6深拷贝
1.复制对象的所有基本数据类型的成员变量值
2.为所有引用数据类型的成员变量值申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象,也就是说,深拷贝要对整个对象进行拷贝
1.深拷贝实现方式1:重写clone方法来实现深拷贝
2.深拷贝实现方式2:通过对象序列化实现深拷贝(推荐)
深拷贝引用类型对象:
public class DeepCloneableTarget implements Serializable, Cloneable{
private static final long serialVersionUID = 1L;
private String cloneName;
private String cloneClass;
//构造器
public DeepCloneableTarget(String cloneName, String cloneClass) {
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}
//因为该类的属性,都是String , 因此我们这里使用默认的clone完成即可
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
深拷贝两种实现方式
public class DeepProtoType implements Serializable,Cloneable{
public String name;//String类型
public DeepCloneableTarget deepCloneableTarget;//引用类型
public DeepProtoType() {
super();
}
//深拷贝 - 方式1 使用clone方法 (重写clone方法)
//假如新增引用类型变量,需要添加处理引用类型变量
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
//这里完成对基本数据类型(属性)和String的克隆
deep = super.clone();
//对引用类型进行单独处理
DeepProtoType deepProtoType = (DeepProtoType) deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
return deep;
}
//深拷贝 -方式2 通过对象的序列化实现(推荐)
//假如这个类还有其他引用类型,序列化直接就可以实现不能改代码
public Object deepClone(){
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {//核心代码:序列化(相当于先把关联引用的这个对象输出出去;再读回来时就会拷贝一份;读回来的这个对象就是我们需要的)
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);//当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copyOcj = (DeepProtoType) ois.readObject();
return copyOcj;
} catch (Exception e) {
e.printStackTrace();
return null;
}finally {
//关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
客户端使用
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
DeepProtoType dp = new DeepProtoType();
dp.name = "B0";
dp.deepCloneableTarget = new DeepCloneableTarget("大牛","大牛的类");
//方式1 完成深拷贝
// DeepProtoType clone = (DeepProtoType) dp.clone();
// System.out.println(dp.name+"==="+dp.deepCloneableTarget.hashCode());
// System.out.println(clone.name+"==="+clone.deepCloneableTarget.hashCode());
//方式2 完成深拷贝
DeepProtoType clone = (DeepProtoType) dp.deepClone();
System.out.println(dp.name+"==="+dp.deepCloneableTarget.hashCode());
System.out.println(clone.name+"==="+clone.deepCloneableTarget.hashCode());
}
}
5.7 原型模式注意事项
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率不用重新初始化对象,而是动态地获得对象运行时的状态
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码在实现深克隆的时候可能需要比较复杂的代码
- 缺点:需要为每一个类配备一 个克隆方法, 这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则,这点请注意