3分钟学设计模式(创建型):5、原型模式
前言
设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
经过汇总的23种设计模式它是总结了面向对象设计当中最有价值的经验。对之前来讲可能是对其中部分设计模式还是相对来说熟悉的但仔细琢磨还是会有些疑问,正好在目前相对来说有更多的业余时间,可以来一次重新学习设计模式!
本篇内容关于原型模式。包含原型模式的设计与实现。
定义
原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的。——维基百科
原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据。
我们需要一系列对象,这些对象都是具有相同的特征,且它们都是独立的。就像马里奥里很多蘑菇人,它们是独立的不会踩一个全部都挂掉。但它们是相同特征也就是深拷贝。
避免手动去创建相同内容的对象因为第一复杂繁琐效率不高,第二在外面真不一定能创建一模一样的对象很多属性是私有的且没有公开方法方法。
对于一个马里奥怪物类,我们可以去手动创建蘑菇人对象,也可以创建乌龟对象。但我们再创建一个蘑菇人或者一百个就是通过原型模式的方法。只有定制化创建时去手动设计,之后再需要一模一样的对象就通过原型方法获取。达到定制一个批量复制。
自定义实现
先画个结构图
首先是一个抽象接口,第二就是一个具体的原型类,去实现clone方法。具体原型类的实例通过使用clone方法就能产生一个一模一样的新对象
interface Prototype{
Object clone();
}
class Guaiwu{
private String name;
private String icon;
public Guaiwu(String name,String icon){
this.name = name;
this.icon = icon;
}
public Guaiwu(Guaiwu obj){
this.name = obj.name;
this.icon = obj.icon;
}
@Override
public object clone(){
return new Guaiwu(this);
}
}
指定一个对象
Guaiwu wugui = new Guaiwu("小乌龟","乌龟样子");
批量复制
Guaiwu wugui1 = wugui.clone();
Guaiwu wugui2 = wugui.clone();
Guaiwu wugui3 = wugui.clone();
...
以上呢就是一个简单的原型模式的一个自定义实现。
Object clone方法
不过其实在Java当中已经提供了clone方法,它是一个Object的本地方法
也就是说每个对象都已经具备了clone方法,不需要像上面定义抽象标准再在每个具体原型类里面去实现。
class Noodle{
String name;
String size;
public Noodle(String name,String size){
this.name = name;
this.size = name;
}
}
class Client{
public static void main(String[] args){
// 指定原型对象
Noodle noodle = new Noodle("长寿面","超大份");
// 批量复制
Noodle n1 = noodle.clone();
Noodle n2 = noodle.clone();
// 比较
System.out.println(n1);
System.out.println(noodle == n1);
System.out.println(n1 == n2);
}
}
结构也是符合预期的,确实n1与n2的内容是和noodle一样。且它三个确实是存在三个对象而不是一个。
但这个Object提供的clone对于我们的需要来说其实是存在问题的,它虽然是新建了对象和我们上面自己实现的clone()一样,但新建对象的属性内容是直接赋值过去。也就是说如果属性是一个非基本类型。那么clone出来的对象里的这个属性和原型的对象里的这个属性是指向同一个对象。也就是说它是浅拷贝。
Object的这个clone,其实就是和上面完全自定义写的那个一样,虽然是新建了对象n1,但n1的属性内容只是把noodle的内容挨个的赋值进去。导致非基本类型是引用相同对象而不是新对象。
我们需要的是完完全全的深拷贝
能怎么办,重写实现,针对当前原型类的属性,完善clone逻辑
// 佐料
class Spice implements Cloneable{
String name;
String type;
...
}
// 面条(原型类)
class Noodle implements Cloneable{
String name;
String size;
Spice spice;
@Override
public Noodle clone(){
// 先调用原来的clone,也就是得到属性都是直接复制到新对象
Noode noodle = (Noodle)super.clone();
// 再把spice复制个新对象,设置进去
noodle.spice = this.spice.clone();
return noodle;
}
}
这样就完成了clone方法对当前Noodle类型对象是完全拷贝
要注意的是访问clone方法是protected因此只能在java.lang访问。因此在外部非子类访问需要重写(开头的代码没有写所以提示下),且重写必须得实现Cloneable接口,说明如下:
这样写出来就比较复杂,为了进行深拷贝里面有复杂类型的属性,而属性里面可能还牵连很多其他的非基本类型。导致在原型对象类里面不停的嵌套去实现直到将所有属性的值都创建新对象。对于复杂类型复杂的组合依赖关系,上面的clone方法的实现就会复杂很多,要递归到只有基本类型为止,从最底层一个一个对象进行创建
序列化
不过还好有序列化,可以利用序列化的方式来达到深拷贝
@Override
public Object clone() {
try{
// 写出
ByteArrayOutputStream aos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(aos);
oos.writeObject(this);
// 读取
ByteArrayInputStream ais = new ByteArrayInputStream(aos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ais);
return ois.readObject();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
最终还是可以一步到位的,里面的嵌套的非基本类型的属性,也都是新对象。使用序列化的方式就避免了手动去递归挖掘所有关联非基本类型的属性,进行新建拷贝内容再赋值的过程。
但怎么说还是得改写所有关联的属性的类以及下面不停嵌套的类,都要去实现序列化接口。只是对于clone方法的重写可以一步到位。
总结
相对之前来说这个原型模式思想是比较简单的,它可能会用在需要复用很多对象,且要求保证独立安全。优点在于:第一客户代码可以直接通过模板对象调用clone就能得到新对象,免去对使用类的了解程度的门槛。第二有些情况下就是需要进行复制,在这样的情况下也就比直接创建效率要高。缺点也就是在于类的成员属性的类型的复杂程度,这时候复制的逻辑就比较复杂,或者说需要很注意。但无论怎么说其实都要去修改代码,不符合开闭原则。还得看具体场景吧实际使用的不多。
往期推荐
扫码二维码
获取更多精彩
Jasper小笔记