设计模式(英语 design pattern)是对面向对象设计中反复出现的问题的解决方案,学习设计模式也是深入体会面向对象魅力的过程。设计模式大致分为23种,下面,我将介绍一些常用的设计模式。
原型模式
原型模式,和字面意思一样,是以一个实例为原型,通过拷贝方式,创建出属性完全相同的新实例。
不用原型模式时,创建一个相同属性的实例一般是这样的:
public class negtive {
/*==============服务端=======================*/
static class Resume{
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/*==============客户端=========================*/
public static void main(String[] args) {
Resume resume01= new Resume();
resume01.setName("ling001");
resume01.setAge(20);
System.out.println(resume01);
Resume resume02= new Resume();
resume02.setName("ling002");
resume02.setAge(23);
System.out.println(resume02);
}
}
可以看到,创建resume02的过程中,必须重新setName、setAge,创建过程繁琐,做了很多无谓的工作,这种方式当然是不可取的,那么如何避免这种重复的编码呢?这时就轮到克隆方法出场了。
clone是object中的一个方法,此方法未实现,是native方法,即本地方法(可以调用底层操作系统的方法),在调用本地方法创建对象时,比直接new创建对象效率要高。
升级后的代码如下
public class postvie_01 {
/*==============服务端=======================*/
static class Resume implements Cloneable{
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/*==============客户端=========================*/
public static void main(String[] args) throws CloneNotSupportedException {
Resume resume01= new Resume();
resume01.setName("ling001");
resume01.setAge(20);
Resume resume02= (Resume) resume01.clone();
resume02.setName("li666");
System.out.println(resume01);
System.out.println(resume02);
System.out.println(resume01.equals(resume02));
}
}
此时不需要重新new一个新对象,只需要服务端先实现一个Cloneable接口,并重写clone方法即可,此接口没有任何方法,只是一个标识接口。
这样一个新对象就创建出来啦,但是不要想着这么简单就结束了,这样的克隆方法叫“浅拷贝”。如果这时新需求来了,需要在这个对象中添加一种引用类型的属性,比如Date类型的成员属性,这时浅拷贝就无能为力了。为什么呢?我们用代码来说话:
public class negtive_02 {
/*==============服务端=======================*/
static class Resume implements Cloneable{
private String name;
private Integer age;
private Date workExperience;
public Date getWorkExperience() {
return workExperience;
}
public void setWorkExperience(Date workExperience) {
this.workExperience = workExperience;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", age=" + age +
", workExperience=" + workExperience +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/*==============客户端=========================*/
public static void main(String[] args) throws CloneNotSupportedException{
Resume resume01= new Resume();
Date date = new Date();
resume01.setName("ling001");
resume01.setAge(20);
resume01.setWorkExperience(date);
Resume resume02= (Resume) resume01.clone();
resume02.getWorkExperience().setTime(0);
System.out.println(resume02);
System.out.println(resume01);
System.out.println(resume01.equals(resume02));
}
}
执行结果如下:
可以看到,此时两个实例共用了一个workExperience属性,并没有达到我们预期的效果,这时怎么回事呢?这里我们有必要学习一下clone方法的实现原理。
浅拷贝(浅复制):clone得到的对象a其实只是对被clone对象b的引用,即对象a是指向b对象上的。简单的说,就是将原始对象中所有东西原封不动的复制一遍,引用类型的引用地址也复制一遍,所以就造成了上面出现的问题。
相对应的,深拷贝就解决了这一问题,使用深拷贝需要重写clone方法,
@Override
public Object clone() throws CloneNotSupportedException {
Resume clone = (Resume) super.clone();
Date cloneDate = (Date) clone.getWorkExperience().clone();
clone.setWorkExperience(cloneDate);
return clone;
}
运行结果如下:
其实就是对浅拷贝的字段再进行深拷贝。
以上面用到的Date引用类型对象为例:
可以看到Date是实现了Cloneable接口的,即表示Date也是可以进行clone(克隆)的。只需要将浅拷贝的Date再使用clone方法进行一次深拷贝,再赋值给clone的对象即可。具体参照上面重写的clone方法。
但是这种方法也有很大的缺陷,如果一个类有很多引用类型,难道我们都要一个一个的去重写克隆方法吗?
如何解决这一问题呢?
这里我们抛出一个问题,Java创建对象有几种方式呢?
答案是4种,
构造器、反射、克隆、反序列化。
这里我们可以用反序列化来进行深克隆,将对象整个序列化,通过io反序列化,这样将得到一个全新的对象,上面的问题不就迎刃而解了吗?
具体操作如下:
@Override
public Object clone() throws CloneNotSupportedException{
try {
FileOutputStream out = new FileOutputStream("F:\\a.txt");
// out.write(this); // 只能写字节
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(this); // 序列化对象
oos.close();
FileInputStream in = new FileInputStream("F:\\a.txt");
// in.read(this);// 只能读字节
ObjectInputStream ois = new ObjectInputStream(in);
Object clone = ois.readObject();
ois.close();
return clone;
} catch (Exception e) {
e.printStackTrace();
}
}
此时就完美解决上述问题了。不过这里涉及io流,代码中有盘符这种“忌讳”字符串,所以我们可以直接写到内存中进行反序列化,代码如下:
@Override
public Object clone() throws CloneNotSupportedException{
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();// 写入内存
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(this); // 序列化对象
oos.close();
byte[] bytes = out.toByteArray(); // 内存中取出数据
ByteArrayInputStream in = new ByteArrayInputStream(bytes);// 内存中读取出来
ObjectInputStream ois = new ObjectInputStream(in);
Object clone = ois.readObject();
ois.close();
return clone;
} catch (Exception e) {
e.printStackTrace();
}
}
就此,完美解决所有问题~
这里的io流用到了装饰者模式,我们下节解析