一、引言
欲言又止,
二、克隆羊
假设现在我们有一个对象,需要拷贝新的对象出来,以下代码是最简单粗暴的方式了。 但是如果这个对象有很多属性呢? 那岂不是太麻烦了,针对这种情况就可以使用我们的原型模式来实现。
原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝原型,从而创建新的对象。
原型模式是一种创建型的设计模式,允许一个对象在创建另外一个可定制的对象,无需知道创建的细节。其实也就是说白了把拷贝的具体实现封装在类里面,外部只需要使用对应的方法则可以实现拷贝的效果。
public static void main(String[] args) {
// 创建一个对象
Sheep sheep = new Sheep("Tom", 1);
// 根据上面那个对象,创建新的对象
Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge());
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge());
Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge());
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
}
三、使用原型模式实现克隆羊
针对上面刚刚那个案例,使用原型模式进行改进。
步骤一:在Sheep这个类需要实现Cloneable的接口,其实这个Cloneable是一个标记接口,然后在类中需要重写Object的clone方法,然后通过类调用这个clone方法,从而达到克隆的效果。
如果不实现这个接口,则会抛出CloneNotSupportedException克隆不被支持的异常。
步骤二:重写Object的clone方法,使用时直接调用即可。
/**
* @Auther: IT贱男
* @Date: 2019/8/5 15:21
* @Description: 克隆羊 - 原型模式
*/
@Data
public class Sheep implements Cloneable {
private String name;
private int age;
public Sheep(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Sheep clone() {
try {
// 这里默认调用父类的实现方法即可,默认是浅拷贝
return (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public static void main(String[] args) {
Sheep sheep = new Sheep("Tom", 1);
// 这里直接调用clone方法即可
Sheep sheep1 = sheep.clone();
Sheep sheep2 = sheep.clone();
System.out.println(sheep == sheep1);
System.out.println(sheep2 == sheep1);
}
四、理解深拷贝和浅拷贝
浅拷贝:
对于基本数据类型的成员变量,浅拷贝会进行值传递,也就是将属性值赋值给新对象。
对于成员是引用类型,比如数组、对象等,那么浅拷贝会进行引用传递。也就是将成员等引用值复制一个给新对象,实际上两个对象都是指向同一个实例,在这种情况下修改了一个对象中得值,另外一个对象也会随之而改变。
深拷贝:
深拷贝不仅仅复制了所有的基本数据类型,为所有的引用数据类型的成员变量申请存储空间。 也就是说引用成员不再拷贝引用地址,而是整个对象进行拷贝。
五、深拷贝两种实现方式
现在我们在Sheep类中新增一个对象叫Friend,并且添加对应的构造方法。
/**
* @Auther: IT贱男
* @Date: 2019/8/5 15:21
* @Description: 克隆羊 - 原型模式
*/
@Data
public class Sheep implements Cloneable {
private String name;
private int age;
private Friend friend;
public Sheep(String name, int age, Friend friend) {
this.name = name;
this.age = age;
this.friend = friend;
}
@Override
protected Sheep clone() {
try {
return (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public static void main(String[] args) {
Sheep sheep = new Sheep("Tom", 1,new Friend("Miqi"));
// 这里采用的是默认浅拷贝,所以拷贝出来的friend对象是同一个
Sheep sheep1 = sheep.clone();
System.out.println(sheep.getFriend() == sheep1.getFriend());
}
实现方式一:首先我们需要将Friend这个类,实现默认的clone方法,然后重写Sheep中的clone方法。
这种方式虽然可以实现,如果引用对象很多,是不推荐使用的
/**
* @Auther: IT贱男
* @Date: 2019/8/20 11:25
* @Description:
*/
public class Friend implements Cloneable {
public Friend(String name) {
this.name = name;
}
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/**
* 深拷贝实现方式一
* @return
*/
@Override
protected Sheep clone() {
try {
Sheep sheep = (Sheep) super.clone();
// 这里需要调用friend的clone方法,然后复制给新克隆出来的对象
sheep.setFriend((Friend) friend.clone());
return sheep;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
实现方式二:方式一虽然说可以实现,但是大家也看出来了,引用对象需要重新赋值,那如果引用对象很多,那也很麻烦,所以来看看第二种方式,这种方式比较省事。
首先需要将引用对象实现序列化接口,然后采用序列化的方式来实现深拷贝。
/**
* @Auther: IT贱男
* @Date: 2019/8/20 11:25
* @Description:
*/
public class Friend implements Serializable{
private static final long serialVersionUID = -3019656355622657141L;
public Friend(String name) {
this.name = name;
}
private String name;
}
/**
* @Auther: IT贱男
* @Date: 2019/8/5 15:21
* @Description: 克隆羊 - 原型模式
*/
@Data
public class Sheep implements Cloneable, Serializable {
private static final long serialVersionUID = -270095030076915889L;
private String name;
private int age;
private Friend friend;
public Sheep(String name, int age, Friend friend) {
this.name = name;
this.age = age;
this.friend = friend;
}
/**
* 使用序列化的方式实现深拷贝
* @return
*/
@Override
protected Sheep clone() {
Sheep sheep = null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
sheep = (Sheep) ois.readObject();
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return sheep;
}
}
六、原型模式优缺点
优点:如果需要复制一个新的对象时,可以利用原型模式简化对象的创建过程,同时也可以提高效率。
缺点:需要为每一个类配备一个克隆方法,如果对已有的代码类进行改造,需要修改源码,违背了OCP原则。