书接上回,本篇讲一下创建型模式-原型设计模式
原型设计模式
定义:用原型实例指定创建对象种类,并通过拷贝这些原型来创建新的对象。
另外在拷贝时,不需要知道任何构建过程细节。也不用调用构造器
原型设计模式本质:对象克隆
案例分析
需求:给10个用户发节日祝福短信
普通版
短信类
//短信
public class SMS {
private String usename; //用户名
private String phone; //手机
private String content; //短信内容
//通过短信模板拼接短信
public String getContent(){
return MessageFormat.format(SMSUtil.getSmsTemplate(), usename, content);
}
public String getUsename() {
return usename;
}
public void setUsename(String usename) {
this.usename = usename;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setContent(String content) {
this.content = content;
}
短信工具类
//短信工具类
public class SMSUtil {
//发短信
public static void sendSms(SMS sms){
System.out.println(MessageFormat.format("向手机号为:{0}的用户,发送了短信,内容为:{1}",
sms.getPhone(), sms.getContent()));
}
//获取短信模板,用于拼接短信
public static String getSmsTemplate(){
return "亲爱的{0}女士/男士:{1}";
}
}
测试
public class App {
public static void main(String[] args) {
for (int i = 10; i < 20; i++){
SMS sms = new SMS();
sms.setUsename("dafei_" + i);
sms.setPhone("137000000" + i);
sms.setContent("春节快乐");
SMSUtil.sendSms(sms);
}
}
}
结果
向手机号为:13700000010的用户,发送了短信,内容为:亲爱的dafei_10女士/男士:春节快乐
向手机号为:13700000011的用户,发送了短信,内容为:亲爱的dafei_11女士/男士:春节快乐
向手机号为:13700000012的用户,发送了短信,内容为:亲爱的dafei_12女士/男士:春节快乐
向手机号为:13700000013的用户,发送了短信,内容为:亲爱的dafei_13女士/男士:春节快乐
向手机号为:13700000014的用户,发送了短信,内容为:亲爱的dafei_14女士/男士:春节快乐
向手机号为:13700000015的用户,发送了短信,内容为:亲爱的dafei_15女士/男士:春节快乐
向手机号为:13700000016的用户,发送了短信,内容为:亲爱的dafei_16女士/男士:春节快乐
向手机号为:13700000017的用户,发送了短信,内容为:亲爱的dafei_17女士/男士:春节快乐
向手机号为:13700000018的用户,发送了短信,内容为:亲爱的dafei_18女士/男士:春节快乐
向手机号为:13700000019的用户,发送了短信,内容为:亲爱的dafei_19女士/男士:春节快乐
原型版
短信类
有个2个变化,实现了Cloneable接口, 重写Object类的clone方法
//短信
public class SMS implements Cloneable{
private String usename; //用户名
private String phone; //手机
private String content; //短信内容
public String getContent(){
return MessageFormat.format(SMSUtil.getSmsTemplate(), usename, content);
}
public String getUsename() {
return usename;
}
public void setUsename(String usename) {
this.usename = usename;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setContent(String content) {
this.content = content;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object clone = super.clone();
System.out.println("克隆了,原hashCode:" + this.hashCode() +" 克隆后hashCode:" + clone.hashCode());
return clone;
}
}
测试
public class App {
public static void main(String[] args) throws CloneNotSupportedException {
SMS sms = new SMS();
sms.setContent("春节快乐");
for (int i = 10; i < 20; i++){
SMS clone = (SMS) sms.clone();
clone.setUsename("dafei_" + i);
clone.setPhone("137000000" + i);
SMSUtil.sendSms(clone);
}
}
}
结果
克隆了,原hashCode:403424356 克隆后hashCode:321142942
向手机号为:13700000010的用户,发送了短信,内容为:亲爱的dafei_10女士/男士:春节快乐
克隆了,原hashCode:403424356 克隆后hashCode:1307096070
向手机号为:13700000011的用户,发送了短信,内容为:亲爱的dafei_11女士/男士:春节快乐
克隆了,原hashCode:403424356 克隆后hashCode:1014328909
向手机号为:13700000012的用户,发送了短信,内容为:亲爱的dafei_12女士/男士:春节快乐
克隆了,原hashCode:403424356 克隆后hashCode:2081303229
向手机号为:13700000013的用户,发送了短信,内容为:亲爱的dafei_13女士/男士:春节快乐
克隆了,原hashCode:403424356 克隆后hashCode:1223685984
向手机号为:13700000014的用户,发送了短信,内容为:亲爱的dafei_14女士/男士:春节快乐
克隆了,原hashCode:403424356 克隆后hashCode:1076835071
向手机号为:13700000015的用户,发送了短信,内容为:亲爱的dafei_15女士/男士:春节快乐
克隆了,原hashCode:403424356 克隆后hashCode:1463757745
向手机号为:13700000016的用户,发送了短信,内容为:亲爱的dafei_16女士/男士:春节快乐
克隆了,原hashCode:403424356 克隆后hashCode:1525262377
向手机号为:13700000017的用户,发送了短信,内容为:亲爱的dafei_17女士/男士:春节快乐
克隆了,原hashCode:403424356 克隆后hashCode:1837760739
向手机号为:13700000018的用户,发送了短信,内容为:亲爱的dafei_18女士/男士:春节快乐
克隆了,原hashCode:403424356 克隆后hashCode:1418428263
向手机号为:13700000019的用户,发送了短信,内容为:亲爱的dafei_19女士/男士:春节快乐
从上面打印出来的hashcode值来看,发现每一个clone出来的对象都不一样,这就是克隆,原型模式差不多就这样啦。
适用场景
1>类初始化消耗较多资源
2>new产生的一个对象需要非常繁琐的过程(数据准备, 访问权限等)
3>构造函数比较复杂
4>需要重复生产大量相同或类似对象时
优缺点
优点
性能比直接new一个对象性能高(直接内存开辟空间)
简化构建过程(直接ctrl+c/v)
缺点
必须配备克隆方法
对克隆复杂对象或者克隆出的对象进行复杂改造时,容易引入风险(浅克隆风险)
深拷贝,浅拷贝要运用得当(克隆的前提)
克隆隐患
原型模式的安全隐藏在克隆方式,java中的克隆有2种:浅克隆跟深克隆
浅克隆
看一个例子
部门类
public class Dept {
private String name;
public Dept(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dept{name='" + name +"', hashcode="+this.hashCode()+"}";
}
}
员工类
员工类依赖Dept部门类,同时实现了Cloneable接口,重写clone方法
public class Emp implements Cloneable{
private String name;
private Dept dept;
public Emp(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' + ", hashcode:" + this.hashCode() +
", dept=" + dept +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试
创建一个部门类,员工类,将部门对象赋值给员工类部门属性,执行克隆之后修改部门对象name属性
public class App {
public static void main(String[] args) throws CloneNotSupportedException {
//克隆前
Dept dept = new Dept("开发部");
Emp emp = new Emp("dafei");
emp.setDept(dept);
System.out.println(emp);
System.out.println("------------------------");
//克隆后
Emp clone = (Emp) emp.clone();
dept.setName("测试部");
System.out.println(clone);
}
}
结果
Emp{name='dafei', hashcode:321142942, dept=Dept{name='开发部', hashcode=1223685984}}
------------------------
Emp{name='dafei', hashcode:1374677625, dept=Dept{name='测试部', hashcode=1223685984}}
从结果打印出来的hashcode值来看, 员工对象确实实现拷贝,已经是2个不同对象了,但是部门并不是2个对象。也就是说员工类的super.clone()方法仅仅是对员工对象克隆而已,员工对象的对象属性并没有进行拷贝,这种现象称之为浅拷贝。
上面描述换成图形
浅克隆的危险性在,引用类型的属性变动会给原型对象造成属性变动隐患。那怎么改进?使用深克隆。引用类型的属性也进行克隆。
深克隆
部门类
让部门类也实现Cloneable接口,实现clone方法
public class Dept implements Cloneable{
private String name;
public Dept(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dept{name='" + name +"', hashcode="+this.hashCode()+"}";
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
员工类
修改了clone方法
public class Emp implements Cloneable{
private String name;
private Dept dept;
public Emp(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' + ", hashcode:" + this.hashCode() +
", dept=" + dept +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Emp emp = (Emp) super.clone();
Dept dept = (Dept) emp.getDept().clone();
emp.setDept(dept);
return emp;
}
}
测试
没有变
结果
Emp{name='dafei', hashcode:321142942, dept=Dept{name='开发部', hashcode=1223685984}}
------------------------
Emp{name='dafei', hashcode:1374677625, dept=Dept{name='开发部', hashcode=1345636186}}
结果就很明显了,emp跟dept对象都完整克隆出来了。
开发案例
开发案例还是选用JDK里-ArrayList
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
}
这里需要注意:ArrayList中元素使用Arrays.copyOf方法进行克隆,该方法时浅克隆。
总结
原型设计模式本质是:对象克隆
克隆时要注意:原型克隆时深克隆还是浅克隆