设计模式(六)原型模式
最后更新于:2022-04-01 11:05:15
### 一、说说鸣人的影分身
话说鸣人听了水木老师的建议偷出了卷轴并且学会了一招禁术:影分身之术。当鸣人使用影分身之术的时候就会有好多个和鸣人一模一样的人出现,就像复制出来的一样,这种影分身之术在面向对象的设计领域里就叫做原型模式。
### 二、什么是原型模式
有了上边的鸣人的例子,我们再理解圆形模式的定义应该会更简单了,GOF给它的定义是:用原型实例指定创建对象的种类并且通过拷贝这些原型对象创建新的对象。
在Java中提供了clone()方法来实现对象的克隆,所以原型模式(Prototype)实现变得简单的多了。
### 三、再来说说clone()方法
Java的所有类 都是从java.lang.Object类继承而来的,而Object类提供下面的方法对对象进行复制:
`protected Object clone()`
子类也可以将这个方法覆盖掉,用自己的逻辑实现自己的复制方法。可以被使用clone()方法的类都必须实现Cloneable接口,Cloneable接口只起一个作用就是在运行时期通知Java虚拟机可以安全地在这个类上使用clone方法。
克隆又分为两种:浅克隆和深度克隆
**浅度克隆:**
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_5717585331dd5.jpg)
如上图所示,浅度复制只是复制对象的值,我们知道对象的属性一共分为两种,基本类型和引用类型,对于浅度复制基本类型的数据会复制一份到新的对象中去,对于引用类型的属性仅仅复制引用的值,引用所指向的具体的对象不会复制,所以A和B实际上是用的同一个对象c,如果再A中改变c的属性,B中也能看到,因为改变的是两者共有对象。Java提供的clone方法就是这种类型的。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_5717585343203.jpg)
深度复制与浅度复制的不同就是深度复制不但会复制对象的引用,并且还会复制引用所指的对象。所以在第二幅图中A和B是指向的不同的对象,此时在A中操作c对象不会对B产生任何影响。
**克隆满足的条件:**
1、对任何对象x,都有:x.clone()!=x.换言之,克隆对象与原来的对象不是同一个对象。
2、对任何对象x,都有:x.clone().getClass==x.getClass(),换言之,克隆对象与原对象的类型一致。
3、如果对象x的equals()方法定义恰当的话,那么x.clone().equals(x)应该是成立的。
**关于equals方法的说明:**被克隆的对象按照他们的内部状态是否可变,划分为可变对象和不可变对象(String的内部数值是不能改变的)。对于可变对象只有当他们是同一个对象时才会返回true,而对于不变对象,当他们的内部状态值是一样的时候就认为是true,但是内部状态一直的不一定就是同一个对象。
### 四、原型模式的结构
原型模式模式分为两种,一种是不带管理类的原型模式,另一种是带管理类的原型模式。
下面这种是不带管理类的原型模式:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_5717585353dc5.jpg)
这种形式涉及到三个角色:
客户角色:客户提出创建对象的请求。
抽象原型角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现,这个类可能会继承Cloneable接口。
具体原型角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
我们通过一个实例来看一下具体的使用过程。
我们举一个大学里常见的例子,一个班里有一个学霸的话整个班级的作业就不用愁了,大家可以拿学霸的作业去复制嘛。
这个类是作业的抽象父类,定义了一些作业都要实现的方法,这里只实现了一个数学作业类,将来可以能有编程作业等。
~~~
package com.designpattern.prototype1;
public abstract class Homework implements Cloneable {
public abstract Object clone();
public abstract void show();
}
~~~
数学作业的类要实现自己的复制逻辑,因为数学作业和编程作业的抄袭的方法肯定是不一样的。
~~~
package com.designpattern.prototype1;
import java.util.Date;
public class MathHomework extends Homework{
/**
* 这里只是用一个日期类来表示一下深度复制
*/
private Date A = new Date();
private int a = 1;
public void show() {
System.out.println("Math clone");
}
/**
* 实现自己的克隆方法
*/
public Object clone(){
MathHomework m = null;
/**
* 深度复制
*/
m = (MathHomework) this.clone();
m.A = (Date)this.getA().clone();
return m;
}
public Date getA(){
return A;
}
}
~~~
客户端就可以使用学霸的作业抄袭了
~~~
package com.designpattern.prototype1;
public class Main {
public static void main(String[] args){
/**
* 建立一个学霸,全班同学的作业就靠他了
*/
MathHomework xueba = new MathHomework();
/**
* 学渣都是从学霸那复制来的
*/
MathHomework xuezha = (MathHomework)xueba.clone();
xuezha.show();
}
}
~~~
那如果一个班里有两个学霸呢,那肯定班里的同学有的会超A同学的,有的会抄B同学的,这样的话系统里就必须要保留两个原型类,这时候使用我们的带有管理类的原型模式就比较方便了。
此时的结构图是这样的:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_571758536576c.jpg)
新增加的管理类:
~~~
package com.designpattern.prototype1;
import java.util.Map;
public class Manager {
private static Manager manager;
private Map prototypes = null;
private Manager() {
manager = new Manager();
}
//使用了简单工厂模式
public static Manager getManager() {
if (manager == null)
manager = new Manager();
return manager;
}
public void put(String name,Homework prototype){
manager.put(name, prototype);
}
public Homework getPrototype(String name){
if(prototypes.containsKey(name)){
return (Homework) ((Homework)prototypes.get(name)).clone();
}else{
Homework homework = null;
try{
homework = (Homework)Class.forName(name).newInstance();
put(name, homework);
}catch(Exception e){
e.printStackTrace();
}
return homework;
}
}
}
~~~
~~~
package com.designpattern.prototype1;
public class MainManager {
public static void main(String[] args){
/**
* 建立一个学霸,全班同学的作业就靠他了
*/
MathHomework xueba = new MathHomework();
Manager.getManager().put("com.designpattern.prototype1.MathHomework", xueba);
/**
* 学渣都是从学霸那复制来的
*/
MathHomework xuezha = (MathHomework) Manager.getManager().getPrototype("com.designpattern.prototype1.MathHomework");
xuezha.show();
}
}
~~~
简单形式和登记形式的原型模式各有其长处和短处,如果需要创建的原型对象数目较少 而且比较固定的话可以采取简单形式,如果创建的原型对象数目不固定的话建议采取第二种形式。
### 五、原型模式的优缺点
**优点:**
1、将产品的创建过程封装起来,客户端不需要了解产品的具体创建流程。
2、利用Java的clone方法来创建对象肯定要比使用new来创建对象快很多,尤其是那些很复杂的对象的时候。
3、可以在不修改其他代码的情况下添加新的产品,符合“开-闭”原则。
**缺点:**原型模式的最大缺点就是每一个类必须都有一个clone方法,如果这个类的组成不太复杂的话还比较好,如果类的组成很复杂的话,如果想实现深度复制就非常困难了。
### 六、原型模式的选择
假设一个系统的产品类是动态加载的,而且产品类具有一定的等级结构。这个时候如果采用工厂模式的话,工厂类就不得不具有一个相应的等级结构。而产品类的等级结构一旦发生变化,工厂类的等级结构就不得不有一个相应的变化,这对于产品结构可能经常变化的系统来说采用工厂模式是很不方便的。这个时候如果采用原型模式,给每个产品类装配一个clone方法便可以避免使用工厂方式所带来的具有固定等级结构的工厂类。
源码下载:[http://download.csdn.net/detail/xingjiarong/9308023](http://download.csdn.net/detail/xingjiarong/9308023)