设计之禅——原型模式
前言
“万事万物皆对象!”,这是面向对象的宗旨,在Java中我们无时无刻不在创建对象,那创建对象有哪些方式呢?可以通过new或者反射,还有就是今天要讲的原型模式。那什么是原型模式,为什么又要通过原型模式去创建对象呢?
正文
无论是通过new还是反射,我们都免不了要去手动设置其属性,如果类结构非常复杂,同时又需要创建大量相似的对象时,如果还老老实的去new就非常麻烦易出错了,而原型模式则是使用clone方法直接拷贝一个对象,在Java中实现非常简单,只需要重写Object的clone方法就行了。
Coding
首先创建一个People类,每个人都有头和手:
public class People implements Cloneable {
public String name;
public Integer age;
public Head head;
public Arm[] arms;
public People() {
this.name = "Jack";
this.age = 10;
this.head = new Head();
Arm left = new Arm();
Arm right = new Arm();
arms = new Arm[2];
arms[0] = left;
arms[1] = right;
}
/**
* 重写clone方法,需要注意的是Object中
*/
@Override
public People clone() throws CloneNotSupportedException {
return (People) super.clone();
}
}
public class Head implements Cloneable {
double weight = 10;
@Override
public Head clone() throws CloneNotSupportedException {
return (Head) super.clone();
}
}
public class Arm implements Cloneable {
double length = 5;
@Override
public Arm clone() throws CloneNotSupportedException {
return (Arm) super.clone();
}
}
这样我们需要创建同样的对象时,只需要调用clone方法就行了,这个被拷贝的对象就被称为原型,不过这两个对象之间有什么异同呢?修改其中一个对象的属性对另外一个对象又有什么影响呢?思考一下下面这段代码的结果:
People people = new People();
People clone = people.clone();
System.out.println(people == clone);
System.out.println(people.name == clone.name);
System.out.println(people.age == clone.age);
System.out.println(people.head == clone.head);
System.out.println(people.arms == clone.arms);
你会发现除了第一个为false以外,其他的全是true,换言之,它们的属性所引用的对象为同一个对象,改变任何一个对象的属性都会影响到另外一个对象的属性,使用下面代码验证一下:
System.out.println("-------name-------");
people.name = "Harry";
System.out.println(people.name);
System.out.println(clone.name);
System.out.println("--------age------");
people.age = 555;
System.out.println(people.age);
System.out.println(clone.age);
System.out.println("-------head-------");
System.out.println("改变前people:" + people.head.weight);
System.out.println("改变前clone:" + clone.head.weight);
people.head.weight = 55;
System.out.println("改变后people:" + people.head.weight);
System.out.println("改变后clone:" + clone.head.weight);
System.out.println("-------arm-------");
// 先检验数组中对象
System.out.println("改变前people:" + people.arms[0]);
System.out.println("改变前clone:" + clone.arms[0]);
people.arms[0] = null;
System.out.println("改变后people:" + people.arms[0]);
System.out.println("改变后clone:" + clone.arms[0]);
打印结果:
-------name-------
Harry
Jack
--------age------
555
1111
-------head-------
改变前people:10.0
改变前clone:10.0
改变后people:55.0
改变后clone:55.0
-------arm-------
改变前people:cn.dark.Arm@1b6d3586
改变前clone:cn.dark.Arm@1b6d3586
改变后people:null
改变后clone:null
这是怎么回事?head和arm属性确实如我们所想并没有被完全拷贝,但name和age属性却是独立的。
出现这样的结果主要是因为Java中的clone方法是本地方法实现的一个浅拷贝,除基本类型以外的引用类型只会将引用进行拷贝(基本类型则是拷贝值),因此得到的是同一个对象。
等等,name(String)和age(Integer)不也是引用类型么?
你说的没错,但仔细思考一下不难理解,String和Integer都是final修饰的不可变对象,意思对其中任何一个对象修改该值,原先的引用值是不会改变的,只会将一个新的引用赋值给该属性,但另外一个对象的该属性仍然是引用的之前的对象,也就出现了这样的结果。
那该如何实现深拷贝呢?
很简单,只需要在clone方法中调用属性的clone方法或是重新new一个即可:
public People clone() throws CloneNotSupportedException {
People clone = (People) super.clone();
clone.head = head.clone();
clone.arms = arms.clone();
return clone;
}
也就是说属性对象也需要重写clone方法,到这我们不难看出原型模式一个致命的缺点:
如果拷贝的对象层次非常深,那么实现原型拷贝就会非常的复杂。
因此我们也需要根据具体情况考虑是否使用原型模式。
等等,还没有完,你应该注意到了上面的类都实现了一个Cloneable接口,这个接口是做什么的呢? 它是一个标记接口,表示这个类是可以被clone的,如果不实现该接口,那么在调用该类对象的clone方法时就会抛出CloneNotSupportedException异常,在使用时需要注意。
总结
原型模式非常的简单,但它有以下三点需要注意:
- 被克隆的类必须实现Cloneable接口;
- Java中的clone方法是浅拷贝,在需要完全复制一个全新对象时,需要自己实现。
- clone是本地方法实现,它直接操作内存当中的二进制流(性能也就远远超过new和反射),不会调用对象的构造方法,意味者若遇到需要控制类构造器的访问权限的情况时(如单例模式),就无法使用原型模式了,它会绕开访问权限控制。
- 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 数组属性和方法
- TF入门04-TF实现Word2Vec
- TF入门03-实现线性回归&逻辑回归
- TF入门02-TensorFlow Ops
- 前端|利用Verify插件实现前端图像验证码
- 3分钟短文 | PHP位运算和逻辑运算,一个符号写两遍这么简单?
- 打卡群刷题总结0721——搜索二维矩阵
- NumPy进阶80题完整版|附Notebook版本下载
- 【LeetCode每日一题】21. Merge Two Sorted Lists
- 计算广告笔记06-程序化交易广告
- TF入门05-实验过程管理
- [LeetCode]709. To Lower Case
- ISO C forbids comparison between pointer and integer [-fpermissive]
- 【疑难杂症】解决-TensorFlow “FutureWarning: Conversion of the”
- [Deep-Learning-with-Python] 文本序列中的深度学习
- TN-SCUI2020挑战赛详细讲解