Prototype 模式
Prototype 模式
在Java中,我们可以使用new关键字指定类名来生成类的实例。但是,有时候也会遇到“在不指定类名的前提下生成实例”的需求。例如:
1、对象种类繁多,无法将他们整合到一个类中时
第一种情况是需要处理的对象太多,如果将它们分别作为一个类,必须要编写很多个类文件。
- 使用‘~’为字符串添加下划线
- 使用‘*’为字符串添加边框
- 使用‘/’为字符串添加边框
- ……
2、难以根据类生成实例时
第二种情况是生成实例的过程太过复杂,很难根据类来生成实例。例如,假设有这样一个实例,即表示用户在编辑器中使用鼠标制作出的图形的实例。想再程序中除创建这样的实例是很困难的。通常,在想生成一个和之前用户通过操作所创建出的实例完全一样的实例的时候,我们会事先将用户通过操作创建的实例保存起来,然后在需要的时候通过复制来生成新的实例。
3、想解耦框架与生成的实例时
第三种情况是想让生成实例的框架不依赖与具体的类。这时,不能指定类名来生成实例,而要事先“注册”一个“原型”实例,然后通过复制该实例来生成新的实例。
Prototype模式即根据实例(复制)来生成新实例的模式。
示例程序
需求是将字符串以多种样式打印出来
1、Product类
该接口继承了Cloneable接口(实现了该接口的类的实例可以调用clone方法来自动复制实例),createClone方法是用于复制实例的方法。
package com.design.pattern6;
public interface Product extends Cloneable {
void use(String s);
Product createClone();
}
2、Manager类
showcase字段是用来保存实例和名称的对应关系。register方法是保存product实例,具体是哪个实例不管。
package com.design.pattern6;
import java.util.HashMap;
public class Manager {
//保存所有实例和其名称
private HashMap showcase = new HashMap();
public void register(String name,Product pro){
showcase.put(name,pro);
}
public Product create(String name){
//根据名称获得实例
Product product = (Product) showcase.get(name);
//调用该实例的clone方法,复制实例
return product.createClone();
}
}
3、Message1类(子类1)
在实现createClone方法时,调用了clone方法,具体原理看后面。
package com.design.pattern6;
public class Message1 implements Product{
@Override
public void use(String s) {
System.out.println("use 1");
}
@Override
public Product createClone() {
Product p = null;
try {
//复制实例
p = (Product) clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
4、Main类
package com.design.pattern6;
public class Main {
public static void main(String[] args) {
Manager manager = new Manager();
Message1 message1 = new Message1();
//保存实例
manager.register("ms1",message1);
//复制实例
Product p1 = manager.create("ms1");
p1.use("hello");
}
}
Prototype模式需要三个元素:
1、Prototype(原型)
负责定义复制现有实例的方法。
2、ConcretePrototype(具体的原型)
实现复制实例的方法。
3、Client(使用者)
负责使用复制实例的方法生成新的实例。
拓展
1、为什么要用这个模式?用new来生成实例不行吗?
本章开头做了简单的回答
2、类名是束缚吗?
话说回来,在源程序中使用类名到底有什么问题呢?在代码中出现要使用的类的名字不是理所当然吗?这里,让我们回忆一下面向对象编程的目标之一,即“作为组件复用”。
在代码中出现要使用的类的名称并非总是坏事,不过,一旦在代码中出现要使用的类的名称,就无法与该类分离开来,也就无法实现复用。
3、深拷贝、浅拷贝?
clone方法进行的是浅复制(浅拷贝),它只是将被复制的实例的字段直接复制到新的实例中。换言之,它并没有考虑字段中所保存的实例的内容。例如,当字段中保存的是数组时,如果使用clone方法进行复制,则只会复制该数组的引用,并不会一一复制数组中的元素(原来数组变化了,复制后的实例中的数组也会随之变化)。而深复制(深拷贝)则在浅拷贝的基础上,把引用的数据复制一遍,生成一个新的引用对象,与原来的实例中的字段没有关联。实现深拷贝的方法有重写clone方法等。
浅复制样例:
package com.design.pattern6;
import java.util.Date;
public class Message2 implements Cloneable{
private String name;
private Date time;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public Message2(String name, Date time) {
this.name = name;
this.time = time;
}
@Override
protected Object clone(){
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
@Override
public String toString() {
return "Message2{" +
"name='" + name + '\'' +
", time=" + time +
'}';
}
}
package com.design.pattern6;
import java.util.Date;
public class Main1 {
public static void main(String[] args) {
Date date = new Date();
Message2 m1 = new Message2("原实例",date);
Message2 m2 = (Message2) m1.clone();
m2.setName("克隆实例");
System.out.println(m1);
System.out.println(m2);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("修改时间后");
date.setTime(new Date().getTime());
System.out.println(m1);
System.out.println(m2);
}
}
Message2{name='原实例', time=Fri Aug 09 14:47:45 CST 2019}
Message2{name='克隆实例', time=Fri Aug 09 14:47:45 CST 2019}
修改时间后
Message2{name='原实例', time=Fri Aug 09 14:47:47 CST 2019}
Message2{name='克隆实例', time=Fri Aug 09 14:47:47 CST 2019}
浅克隆:m1和m2都指向了同一个Date,如果m1修改了时间,那么m2的时间也跟着变。
深复制样例
package com.design.pattern6;
import java.util.Date;
public class Message2 implements Cloneable{
private String name;
private Date time;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public Message2(String name, Date time) {
this.name = name;
this.time = time;
}
@Override
protected Object clone(){
Object obj = null;
try {
obj = super.clone();
Message2 message2 = (Message2) obj;
//把属性进行克隆
message2.time = (Date) this.time.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
@Override
public String toString() {
return "Message2{" +
"name='" + name + '\'' +
", time=" + time +
'}';
}
}
Message2{name='原实例', time=Fri Aug 09 14:53:43 CST 2019}
Message2{name='克隆实例', time=Fri Aug 09 14:53:43 CST 2019}
修改时间后
Message2{name='原实例', time=Fri Aug 09 14:53:45 CST 2019}
Message2{name='克隆实例', time=Fri Aug 09 14:53:43 CST 2019}
深克隆:m2克隆出来的时候,也克隆了一个新的Date,两个m各自指着自己的Date。