版权声明:如有任何疑问,欢迎@我:[email protected] https://blog.csdn.net/qq_37206105/article/details/84112104
导航
介绍原型模式的基本特点,对象拷贝的运用 。要理解 浅度拷贝 和 深度拷贝 的区别和使用。
原型设计模式介绍
- 定义:指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
- 特点:不需要知道任何创建细节,不调用构造函数
- 类型:创建型
- 适用场景:
- 类初始化消耗较多资源
- new产生的一个对象需要非常频繁的过程,例如数据准备,访问权限等。
- 构造函数较为复杂
- 循环体中生产大量对象时
- 优点
- 性能比直接new一个对象性能高
- 简化创建过程
- 缺点
- 必须配备克隆方法,克隆方法是这个模式的核心。(Java提供cloneable接口表示对象是可拷贝的;必须重写Object的clone方法)
- 对克隆复杂对象或克隆出对象进行复杂改造时,容易引入风险。
- 深拷贝,浅拷贝要运用得恰当。
一句话来说,就是实现类的克隆,其中包含深度拷贝,浅度拷贝。
代码实现
实现代码的业务场景:及时通讯app,某个人在特定时间内发送很多消息给好友。
首先,定义简单消息类,以及发送消息的工具类。
/**
* 定义消息类。
* 注意点:implement Cloneable ; @Override clone
*/
public class Message implements Cloneable {
private String content;
private String senderName;
private String receiverName;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSenderName() {
return senderName;
}
public void setSenderName(String senderName) {
this.senderName = senderName;
}
public String getReceiverName() {
return receiverName;
}
public void setReceiverName(String receiverName) {
this.receiverName = receiverName;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/**
* 定义发送消息类,简单的print一下。以代表相关的发送逻辑
*/
public class MessageUtil {
public static void sendMessage(Message msg){
System.out.println( msg.getSenderName() +
" is Sending a message : {" +
msg.getContent() +
"} to " +
msg.getReceiverName() );
}
}
下面是测试类:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Message msg = new Message();
msg.setSenderName("Me");
msg.setContent("默认内容");
msg.setReceiverName("默认接收者");
// 模拟业务场景,此时一个人在某个时间点需要发送多条消息。
for( int i= 0 ; i < 5 ; i ++ ){
Message clonedMsg = (Message) msg.clone();
clonedMsg.setContent("第" + (i+1) + "条消息");
clonedMsg.setReceiverName("第" + (i+1) + "个人");
MessageUtil.sendMessage(clonedMsg);
}
}
}
从测试代码容易看出,业务频繁要创建对象实例。并且这个实例我们已知。那么我们就利用现成的已知对象来克隆新的对象。
让克隆抽象化
// 抽象可克隆的类。
public class AbstractCloneableClass implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 具体的实体类
public class Entity extends AbstractCloneableClass {
private String content;
private String senderName;
private String receiverName;
/**
* 下面是getter和setter,我们省略。仅仅为了展示克隆抽象化的体现
*/
//由于继承抽象类的关系,我们在实体类中不再需要重写clone和实现cloneable接口了
}
代码通过注释很容易理解。
浅度拷贝出现的问题
前面我们的成员变量都为String。其实对于成员变量只有基本数据类型和String类型的类,我们在重写clone函数时,直接使用super.clone()就足以解决问题。但是当我们的成员变量是一个类的时候,此时的拷贝只能拷贝我们前面所说的几种常见的数据类型。这就要求我们在重写clone函数时,进行深度拷贝。具体的代码实现如下:
现在对于一个User类,我们有它的基本信息name,birthday。
按照前面的实现思路,实现下面的拷贝:
public class User implements Cloneable {
//User类,注意到成员变量birthday的类型为Birthday自定义Class类型。
private String name;
private BirthDay birthday;
public User(String name, BirthDay time){
this.name = name;
this.birthday = time;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BirthDay getBirthday() {
return birthday;
}
public void setBirthday(BirthDay birthday) {
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/**
* 生日类。这个类在User类中作为成员变量
*/
public class BirthDay {
private String year;
private String month;
private String day;
public BirthDay(String year, String month, String day) {
this.year = year;
this.month = month;
this.day = day;
}
public String getYear() {
return year;
}
public void setYear(String year) {
this.year = year;
}
public String getMonth() {
return month;
}
public void setMonth(String month) {
this.month = month;
}
public String getDay() {
return day;
}
public void setDay(String day) {
this.day = day;
}
}
下面是测试代码用例:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
User user = new User("XiaoMing", new BirthDay( "1998", "1", "1" ) );
User clonedUser = (User) user.clone();
/**测试两个整体User实例,看是否是指向同一地址。运行结果:
user hashCode@1304836502
cloned user hashCode@225534817
两者是否指向同一内存地址?false
*/
System.out.println( "user hashCode@" + user.hashCode() +
"\ncloned user hashCode@" + clonedUser.hashCode() +
"\n两者是否指向同一内存地址?" + (user==clonedUser) );
/**
* 下面是对两个对象内部的birthday对象进行测试。下面是输出结果
User : XiaoMing @1878246837
Cloned User : XiaoMing @1878246837
生日是否指向同一内存地址:true
*/
System.out.println( "\nUser : " + user.getName() +
" @" + user.getBirthday().hashCode() );
System.out.println( "Cloned User : " + clonedUser.getName() +
" @" + clonedUser.getBirthday().hashCode() );
System.out.println( "生日是否指向同一内存地址:" + ( user.getBirthday() == clonedUser.getBirthday() ) );
}
}
从结果我们可以看到,两个User对象内部的birthday对象的hashCode相等,并且指向同一内存地址。这样的拷贝并不完全。
深度拷贝解决方案
下面是深拷贝的方法:
@Override
protected Object clone() throws CloneNotSupportedException {
User clonedUser = (User) super.clone();
clonedUser.birthday = new BirthDay( clonedUser.getBirthday().getYear(),
clonedUser.getBirthday().getMonth(),
clonedUser.getBirthday().getDay() );
return clonedUser;
}
测试用例:
// 用例生成的hashCode的具体值不必在乎。
// 在乎的是比较hashCode的相等性。
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
User user = new User("XiaoMing", new BirthDay( "1998", "1", "1" ) );
User clonedUser = (User) user.clone();
/**测试两个整体User实例,看是否是指向同一地址。运行结果:
user hashCode@1304836502
cloned user hashCode@225534817
两者是否指向同一内存地址?false
*/
System.out.println( "user hashCode@" + user.hashCode() +
"\ncloned user hashCode@" + clonedUser.hashCode() +
"\n两者是否指向同一内存地址?" + (user==clonedUser) );
/**
* 下面是对两个对象内部的birthday对象进行测试。下面是输出结果
User : XiaoMing @1878246837
Cloned User : XiaoMing @929338653
生日是否指向同一内存地址:false
*/
System.out.println( "\nUser : " + user.getName() +
" @" + user.getBirthday().hashCode() );
System.out.println( "Cloned User : " + clonedUser.getName() +
" @" + clonedUser.getBirthday().hashCode() );
System.out.println( "生日是否指向同一内存地址:" + ( user.getBirthday() == clonedUser.getBirthday() ) );
}
}