从这一专栏开始将学习设计模式,上课学习和自己总结归纳的笔记将总结出来供大家参考。
参考书籍:《设计模式就该这样学》
其他文章:
Java设计模式-UML类图 | Java设计模式-七大架构设计原则-开闭原则 |
---|---|
Java设计模式-七大架构设计原则-依赖倒置原则 | Java设计模式-七大架构设计原则-单一职责原则 |
Java设计模式-七大架构设计原则-接口隔离原则 | Java设计模式-七大架构设计原则-最少知道原则(迪米特法则) |
Java设计模式-七大架构设计原则-里氏替换原则和合成复用原则 | Java设计模式-创建型设计模式-简单工厂模式 |
Java设计模式-创建型设计模式-工厂方法模式(工厂模式) | Java设计模式-创建型设计模式-抽象工厂模式 |
Java设计模式-创建型设计模式-建造者模式 | Java设计模式-创建型设计模式-原型模式 |
Java设计模式-创建型设计模式-单例模式 | Java设计模式-结构型设计模式-适配器模式 |
Java设计模式-行为型设计模式-观察者模式 | |
用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。说大白话就是自己复制自己,通过原生对象复制出一个新的对象,这两个对象结构相同且相似,但不是通过new出来的(不调用构造函数)。
需要注意的是,原型对象自己不仅是个对象还是个工厂。并且通过克隆方式创建的对象是全新的对象,它们都是有自己的新的地址,通常对克隆模式所产生的新对象进行修改,是不会对原型对象造成任何影响的,每一个克隆对象都是相对独立的,通过不同的方式对克隆对象进行修改后,可以的到一系列相似但不完全相同的对象。
原型模式我们也称为克隆模式,即一个某个对象为原型克隆出来一个一模一样的对象,该对象的属性和原型对象一模一样。而且对于原型对象没有任何影响。原型模式的克隆方式有两种:浅克隆和深克隆。
在GoF23种设计模式中属于创建型设计模式:
其中包括:简单工厂模式(不在GoF23种设计模式中)、工厂方法模式、抽象工厂模式、建造者模式、原型模式、单例模式。
浅克隆:复制对象时仅仅复制对象本身,包括基本属性,但该对象的属性引用其他对象时,该引用对象不会被复制,即拷贝出来的对象与被拷贝出来的对象中的属性引用的对象是同一个。
深克隆:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深克隆把要复制的对象所引用的对象都复制了一遍。
抽象原型类(Prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口,甚至还可以是具体实现类。
具体原型类(ConcretePrototype):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
客户类(Client):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。
优点:
缺点:
适用环境:
在Java中可以直接使用Object提供的clone方法实现对象的克隆。但需要注意的是,能够实现克隆的java类必须实现一个标识接口:Cloneable,标识这个Java类支持复制。如果一个类没有实现这个接口而调用了clone方法,java编译器将抛出一个CloneNotSupportedException异常。
Clone方法的条件:
1.对于任何对象x,有x.clone()!=x
2.对于任何对象x,有x.clone().getClass()==x.getClass()
3.如果x的equals方法定义恰当,那么x.clone().equals(x)应当成立
类图如下:
1.附件类Attachment
/**
* 附件类
*/
public class Attachment {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void download(){
System.out.println("下载附件,文件名为:" + this.name);
}
}
2.原型类WeeklyLog
/**
* WeeklyLog原型类
* @author WxrStart
* @create 2022-04-12 16:43
*/
public class WeeklyLog implements Cloneable{
private Attachment attachment;
private String name;
private Date date;
private String content;
public Attachment getAttachment() {
return attachment;
}
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
//使用clone()方法来实现浅克隆
public WeeklyLog clone(){
Object o = null;
try {
o = super.clone();
return (WeeklyLog) o;
} catch (CloneNotSupportedException e) {
System.out.println("不支持复制");
e.printStackTrace();
}
return null;
}
}
3.客户端测试类
**
* 客户端测试类
* @author WxrStart
* @create 2022-04-12 16:45
*/
public class Client {
public static void main(String[] args) {
WeeklyLog preLog,newLog;
Attachment attachment = new Attachment();
attachment.setName("week 1 attachment");
preLog = new WeeklyLog();
preLog.setContent("周报week1的内容");
preLog.setDate(new Date(System.currentTimeMillis()-7*24*3600*1000));
preLog.setName("周报week1");
preLog.setAttachment(attachment);
//浅克隆新的对象newLog
newLog = preLog.clone();
//这里就是判断通过克隆方式创建的对象是否是全新的对象,false为全新对象
System.out.println("周报地址(克隆的对象地址)是否相同?" + (preLog == newLog));
System.out.println("*********************************************************");
//通过下面几行代码,证实浅拷贝的特点
System.out.println("preLog和newLog的未更改之前的Attachment的Name");
System.out.println("preLog的Attachment的Name: "+preLog.getAttachment().getName());
System.out.println("newLog的Attachment的Name: "+newLog.getAttachment().getName());
//给newLog设置新的Attachment对象的属性Name
newLog.getAttachment().setName("week 2 attachment");
System.out.println("preLog和newLog的更改之后的Attachment的Name");
System.out.println("preLog的Attachment的Name: "+preLog.getAttachment().getName());
System.out.println("newLog的Attachment的Name: "+newLog.getAttachment().getName());
System.out.println("*********************************************************");
System.out.println("preLog和newLog的未更改之前的date属性");
System.out.println("preLog的date: "+preLog.getDate());
System.out.println("newLog的date: "+newLog.getDate());
//给newLog设置新的Attachment对象的属性Name
newLog.setDate(new Date());
System.out.println("preLog和newLog的更改之后的date属性");
System.out.println("preLog的date: "+preLog.getDate());
System.out.println("newLog的date: "+newLog.getDate());
}
}
测试结果:
很显然,浅拷贝成功了
1.被preLog克隆出来的newLog对象地址不一样。
2.复制对象时仅仅复制对象本身,包括基本属性(Date),但该对象的属性引用其他对象时(Attachment),该引用对象不会被复制,即拷贝出来的对象与被拷贝出来的对象中的属性引用的对象是同一个。所以Date修改后preLog和newLog的Date值不一样,但是Attachment的name值修改后会一样。
很显然,在clone方法的时候有问题了,所以我们需要重新修改clone方法,但是如果还用clone的话很难做到,所以我们选择使用Java自带的序列化机制
1.附件类Attachment
/**
* 附件类
*/
public class Attachment implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void download() {
System.out.println("下载附件,文件名为:" + this.name);
}
}
2.原型类WeeklyLog
/**
* WeeklyLog原型类
* @author WxrStart
* @create 2022-04-12 16:43
*/
public class WeeklyLog implements Serializable{
private Attachment attachment;
private String name;
private Date date;
private String content;
public Attachment getAttachment() {
return attachment;
}
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
//使用序列化技术实现深克隆
public WeeklyLog deepClone() throws Exception{
//1.创建一个字节数组输出流
ByteArrayOutputStream bos=new ByteArrayOutputStream();
//2.用对象输入流包装
ObjectOutputStream oos=new ObjectOutputStream(bos);
//3.将调用该方法的对象写出
oos.writeObject(this);
//4.创建一个字节数组输入流,读取刚刚输出到字节数组的对象this
ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
//5.用对象输入流包装
ObjectInputStream ois=new ObjectInputStream(bis);
//6.返回深克隆对象
return (WeeklyLog)ois.readObject;
}
}
3.客户端测试类
**
* 客户端测试类
* @author WxrStart
* @create 2022-04-12 16:45
*/
public class Client {
public static void main(String[] args) throws Exception {
WeeklyLog preLog,newLog;
Attachment attachment = new Attachment();
attachment.setName("week 1 attachment");
preLog = new WeeklyLog();
preLog.setContent("周报week1的内容");
preLog.setDate(new Date(System.currentTimeMillis()-7*24*3600*1000));
preLog.setName("周报week1");
preLog.setAttachment(attachment);
//浅克隆新的对象newLog
newLog = preLog.deepClone();
//这里就是判断通过克隆方式创建的对象是否是全新的对象,false为全新对象
System.out.println("周报地址(克隆的对象地址)是否相同?" + (preLog == newLog));
System.out.println("*********************************************************");
//通过下面几行代码,证实浅拷贝的特点
System.out.println("preLog和newLog的未更改之前的Attachment的Name");
System.out.println("preLog的Attachment的Name: "+preLog.getAttachment().getName());
System.out.println("newLog的Attachment的Name: "+newLog.getAttachment().getName());
//给newLog设置新的Attachment对象的属性Name
newLog.getAttachment().setName("week 2 attachment");
System.out.println("preLog和newLog的更改之后的Attachment的Name");
System.out.println("preLog的Attachment的Name: "+preLog.getAttachment().getName());
System.out.println("newLog的Attachment的Name: "+newLog.getAttachment().getName());
System.out.println("*********************************************************");
System.out.println("preLog和newLog的未更改之前的date属性");
System.out.println("preLog的date: "+preLog.getDate());
System.out.println("newLog的date: "+newLog.getDate());
//给newLog设置新的Attachment对象的属性Name
newLog.setDate(new Date());
System.out.println("preLog和newLog的更改之后的date属性");
System.out.println("preLog的date: "+preLog.getDate());
System.out.println("newLog的date: "+newLog.getDate());
}
}
测试结果:
很显然,深拷贝成功了
1.被preLog克隆出来的newLog对象地址不一样。
2.复制对象时不光复制对象本身,包括基本属性(Date)和该对象的属性引用其他对象 (Attachment),该引用对象也会被复制,即拷贝出来的对象与被拷贝出来的对象中的属性引用的对象不是同一个。所以Date修改后preLog和newLog的Date值不一样,但是Attachment的name值修改后也会不一样。