1、克隆羊问题
现在有一只羊tom
,姓名为:tom
,年龄为:1
,颜色为:白色
,请编写程序创建和tom
羊属性完全相同的10
只羊。
2、传统方式优缺点
public class Client {
public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 1, "白色");
for (int i = 0; i < 10; i++) {
System.out.println(new Sheep(sheep.name(), sheep.age(), sheep.color()));
}
}
}
优点是
- 比较好理解,简单易操作。
缺点
-
在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
-
总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活改进的思路分析
思路:
Java
中object
类是所有类的根类.Obiect
类提供了一个clone()
方法,- 该方法可以将一个
Java
对象复制一份,但是需要实现clone
的Java
类必须要实现一个接口Cloneable
- 该接口表示该类能够复制且具有复制的能力=>原型模式
3、基本介绍
-
原型模式(
Prototype
模式)是指:- 用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
-
原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
-
工作原理是:
- 通过将一个原型对象传给那个要发动创建的对象,
- 这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,
- 即对象.
clone()
-
形象的理解:
- 孙大圣拔出猴毛,变出其它孙大圣
4、结构图
Prototype
:
- 原型类,声明一个克隆自己的接口
ConcretePrototype
:
- 具体的原型类,实现一个克隆自己的操作
Client
:
- 让一个原型对象克隆自己从而创建一个新的对象(属性一样)
5、原型模式解决该案例
public class Sheep implements Cloneable{
private String name;
private int age;
private String color;
...
//克隆该实例,使用默认的clone方法来完成
@Override
// protected Object clone() throws CloneNotSupportedException {
protected Object clone(){
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
}
public class Client {
public static void main(String[] args) {
System.out.println("原型模式完成对象的克隆");
Sheep sheep = new Sheep("tom", 1, "白色");
for (int i = 0; i < 10; i++) {
//克隆
Sheep sheepClone = (Sheep) sheep.clone();
System.out.println(sheepClone.hashCode());//每个都不一样
}
}
}
6、在Spring框架中的应用
6.2、验证 prototype(浅拷贝)
User
public class User {
public User() {
}
public void setUser(User user) {
this.user = user;
}
public User getUser() {
return user;
}
}
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--userChild 默认为单例-->
<bean id="userChild" class="com.cjf.bean.User"/>
<!--user 为原型-->
<bean id="user" class="com.cjf.bean.User" scope="prototype" >
<property name="user" ref="userChild"/>
</bean>
</beans>
testPro
@Test
public void testPro() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println("user " + user);
System.out.println("user-userChild " + user.getUser());
User user2 = applicationContext.getBean("user", User.class);
System.out.println("user2 " + user2);
System.out.println("user2-userChild " + user2.getUser());
}
结果
user-hashCode com.cjf.bean.User@63a270c9
user-userChild-hashCode com.cjf.bean.User@37c7595
user2-hashCode com.cjf.bean.User@3ed242a4
user2-userChild-hashCode com.cjf.bean.User@37c7595
从结果我们得知为浅拷贝
7、浅拷贝
-
对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
-
对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,
也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。
- 因为实际上两个对象的该成员变量都指向同一个实例。
- 在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
-
前面我们的克隆羊就是浅拷贝
-
浅拷贝是使用默认的
clone()
方法来实现sheep = (Sheep) super.clone();
创建一个新的对象,然后将原对象的成员变量复制过来,
- 对于基本数据类型的变量,直接复制值。
- 对于引用类型的变量,直接复制其引用值(内存地址)
public class Client {
public static void main(String[] args) {
System.out.println("原型模式完成对象的克隆");
Sheep sheep = new Sheep("tom", 1, "白色");
sheep.setFriendSheep(new Sheep("jack",2,"黑色"));
for (int i = 0; i < 10; i++) {
Sheep sheepClone = (Sheep) sheep.clone();
System.out.println(sheepClone.friendSheep().hashCode());
}
}
}
8、深拷贝
-
复制对象的所有基本数据类型的成员变量值
-
为所有引且数据类型的成员变量申请存储空间并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。
- 也就是说,对象进行深拷贝要对整个对象进行拷贝
-
深拷贝实现方式1:
- 重写
clone
方法来实现深拷贝
- 重写
-
深拷贝实现方式2:
- 通过
对象序列化
实现深拷贝
- 通过
8.1、方式一 clone方法
public class Sheep implements Cloneable, Serializable {
public static final long serialVersionUID = 1L;
private String name;
private int age;
private String color;
private Sheep friendSheep;// 引用类型
...
//克隆该实例,使用默认的clone方法来完成
@Override
protected Object clone() throws CloneNotSupportedException {
Sheep sheep = null;
//这里完成对基本数据类型(属性)和String的克隆
sheep = (Sheep) super.clone();
//对引用类型的属性进行单独处理,递归的拷贝
if(sheep.friendSheep != null){
sheep.friendSheep = (Sheep) sheep.friendSheep.clone();
}
return sheep;
}
}
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Sheep sheep = new Sheep("tom", 1, "白色");
Sheep friendSheep = new Sheep("jack", 2, "黑色");
friendSheep.setFriendSheep(new Sheep("zs", 3, "粉色"));
sheep.setFriendSheep(friendSheep);
//方式一 完成深拷贝
Sheep sheepClone = (Sheep) sheep.clone();
System.out.println("sheepClone.friendSheep().hashCode():" +
sheepClone.friendSheep().hashCode() +
"\nsheepClone.friendSheep().friendSheep().hashCode():" +
sheepClone.friendSheep().friendSheep().hashCode());
System.out.println("sheep.friendSheep().hashCode():" +
sheep.friendSheep().hashCode() +
"\nsheep.friendSheep().friendSheep().hashCode():" +
sheep.friendSheep().friendSheep().hashCode());
}
}
8.2、方式二 对象序列化(推荐)
public class Client {
//深拷贝-方式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);
Sheep sheepDeep = (Sheep) ois.readObject();
return sheepDeep;
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}finally {
//关闭流
try {
Objects.requireNonNull(bos).close();
Objects.requireNonNull(oos).close();
Objects.requireNonNull(bis).close();
Objects.requireNonNull(ois).close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Sheep sheep = new Sheep("tom", 1, "白色");
Sheep friendSheep = new Sheep("jack", 2, "黑色");
friendSheep.setFriendSheep(new Sheep("zs", 3, "粉色"));
sheep.setFriendSheep(friendSheep);
//方式二 完成深拷贝
Sheep sheepClone = (Sheep) sheep.deepClone();
System.out.println("sheepClone.friendSheep().hashCode():" +
sheepClone.friendSheep().hashCode() +
"\nsheepClone.friendSheep().friendSheep().hashCode():" +
sheepClone.friendSheep().friendSheep().hashCode());
System.out.println("sheep.friendSheep().hashCode():" +
sheep.friendSheep().hashCode() +
"\nsheep.friendSheep().friendSheep().hashCode():" +
sheep.friendSheep().friendSheep().hashCode());
}
}
9、注意事项与细节说明
-
创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
-
不用重新初始化对象,而是动态地获得对象运行时的状态
-
如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
-
在实现深克隆的时候可能需要比较复杂的代码
缺点:
- 需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,
- 但对已有的类进行改造时,需要修改其源代码,违背了
ocp
原则,这点请同学们注意.