设计模式(五):利用原型模式复制几个葫芦娃
前面的文章介绍了工厂模式和抽象工厂模式、建造者模式,它们都属于创建型设计模式,今天介绍另外一种创建性设计模式,原型模式(prototype pattern)。
什么时候用原型模式?
我个人一直有种观点,就是骨子里懒惰的程序员都喜欢用设计模式,因为懒所以得想尽办法让自己更高效。
原型模式就是一种懒操作,用于频繁创建对象的场所。
日常生活中,我们都说两个人很像,简直就是一个模子里引出来的。
那么,代码中原型模式就是这样基于一个对象快速复制另外一个对象,而无需重头全新创建。
比如,代码要产生 7 个葫芦娃,怎么用原型模式做呢?
葫芦娃长这样:
每个葫芦娃长得都差不多,可能就名字不同,衣服颜色不同。
我们可以定义一个葫芦娃类,然后先创建第一个,然后通过原型模式快速复制更多个。
原型模式实现
非常简单,先定义一个 Cloneable 接口,然后定义一个实现类 CalabashBrother。
因为 Java 语言就支持 clone 操作,所以很容易写出原型设计模式代码。
Java 例子
public class CalabashBrother implements Cloneable{
String name;
String color;
public CalabashBrother(String name,String color){
System.out.println("---Construct obj---");
this.name = name;
this.color = color;
}
@Override
public String toString(){
return "ID:"+hashCode()+" My name is "+this.name+" my skin is " + this.color;
}
@Override
protected Object clone(){
CalabashBrother brother = null;
try {
brother = (CalabashBrother) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return brother;
}
}
定义了 CalabashBrother 代表葫芦娃。
复写了 Clone 方法。
另外复写了 toString() 方法,方便调试时查看对象信息,这里用 hashcode 指示不同的对象。
然后可以写 Demo 测试了。
public class PrototypeDemo {
public static void main(String[] args) {
String colorlist[] = {"red","green","yellow","crown","pink","blue","purple"};
CalabashBrother brother1 = new CalabashBrother("brother1",colorlist[0]);
System.out.println(brother1);
for (int i = 1;i < 7;i++) {
CalabashBrother brother = (CalabashBrother)brother1.clone();
brother.name = "brother"+(i+1);
brother.color = colorlist[i];
System.out.println(brother);
}
}
}
运行程序,结果如下:
---Construct obj---
ID:2129789493 My name is brother1 my skin is red
ID:668386784 My name is brother2 my skin is green
ID:1329552164 My name is brother3 my skin is yellow
ID:363771819 My name is brother4 my skin is crown
ID:2065951873 My name is brother5 my skin is pink
ID:1791741888 My name is brother6 my skin is blue
ID:1595428806 My name is brother7 my skin is purple
大家仔细观看输出可以发现 2 个特征:
- 只调用了一次构造函数,后面通过 clone() 的对象没有再调用类的构造函数。
- clone 出来的对象 ID 不一样,这样保证了是新的的对象。
浅拷贝和深拷贝
讲原型模式的时候离不开这个话题。
浅拷贝的时候只考虑值传递,如果一个类中有引用其它对象,那么只会复制对象的引用。
深拷贝除了值传递外,引用的内容同样也会复制。
public class CalabashBrother implements Cloneable{
String name;
String color;
Calabash calabash = new Calabash();
public CalabashBrother(String name,String color){
System.out.println("---Construct obj---");
this.name = name;
this.color = color;
}
}
假设 CalabashBrother 中有个对象是 calabash,如果直接调用 super.clone() 假设 clone 出来的对象是 obj,那么 obj.calabash 其实和原型对象是同一份内容。
这就是**浅拷贝**。
如果要实现**深拷贝**也很简单,只用一个个对象手动调用 clone() 就好了。
```java
@Override
protected Object clone(){
CalabashBrother brother = null;
try {
brother = (CalabashBrother) super.clone();
brother.calabash = (Calabash) calabash.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return brother;
}
Android 源码中的原型模式
Android 源码中的原型模式多不胜数,这里摘抄一段。
package android.animation;
public abstract class Keyframe implements Cloneable {
boolean mHasValue;
private TimeInterpolator mInterpolator = null;
@Override
public abstract Keyframe clone();
/**
* This internal subclass is used for all types which are not int or float.
*/
static class ObjectKeyframe extends Keyframe {
Object mValue;
ObjectKeyframe(float fraction, Object value) {
mFraction = fraction;
mValue = value;
mHasValue = (value != null);
mValueType = mHasValue ? value.getClass() : Object.class;
}
@Override
public ObjectKeyframe clone() {
ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), hasValue() ? mValue : null);
kfClone.mValueWasSetOnStart = mValueWasSetOnStart;
kfClone.setInterpolator(getInterpolator());
return kfClone;
}
}
}
Keyframe 是 Android 动画中的关键帧,我们都知道动画是连续的一帧帧图片指定时间播放形成的视觉效果,Android 中的 SDK 有很方便的动画定义 API,一段动画中有许多关键帧,关键帧之间通过数学插值的方法自动生成其它帧,因为类型一样,所以关键帧可以通过原型模式来实现。
总结
原型模式非常有用,对于需要频繁创建对象的场景,用原型模式再合适不过了,关键原因在于拷贝时是直接从内存中获取而不是重新再执行一次构造函数。
实际上,不单单是 Java 语言会用到这个模式,其它开发语言只要是频繁创建同类型不同对象都可以用到,比如,我做 OpenCV 的时候,处理视频流的时候就经常要对 mat 进行原型拷贝。
- [WCF安全系列]谈谈WCF的客户端认证[X.509证书认证]
- Openstack Trove概要
- [WCF安全系列]实例演示:TLS/SSL在WCF中的应用[SSL over TCP]
- [WCF安全系列]谈谈WCF的客户端认证[用户名/密码认证]
- [WCF安全系列]绑定、安全模式与客户端凭证类型:BasicHttpBinding
- [WCF安全系列]服务凭证(Service Credential)与服务身份(Service Identity)
- 如何正确的对安卓手机进行数据恢复?
- [WCF安全系列]绑定、安全模式与客户端凭证类型:WSHttpBinding与WSDualHttpBinding
- Python中list的遍历
- Python中的参数传递与解析
- [WCF安全系列]实例演示:TLS/SSL在WCF中的应用[HTTPS]
- QEMU3 - 使用ceph来存储QEMU镜像
- Redis错误配置详解
- 顺序存储线性表的实现
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- SQL | SQL 必知必会笔记 (一 )
- 如何在树莓派4B上安装EMQ X Broker
- SQL | SQL 必知必会笔记 (二)
- 基于桶子法实现的两种排序算法
- Notes | 微观经济学课堂笔记(一)
- 将终结点图添加到你的ASP.NET Core应用程序中
- Stata | 爬取 CFPS 文献传送门并制作成 Markdown
- 委托的好处
- Elasticsearch安装和配置
- Notes | QUAIDS 模型
- Stata | 520,听说你也想快点找到...
- Stata | 批量替换变量值的小技巧
- 手把手教你完成课设作业使用Pandas对海平面温度异常进行分析,小白也能看的懂
- 谈谈自学 Stata 的体会
- Latex修改字体字号的大小