原型模式是一种创建型设计模式,它通过复制一个已经存在的实例来返回新的实例,而不是新建实例.被复制的实例就是我们所称的原型.
意图
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
别名
clone
结构与参与者
Prototype(原型类):声明一个克隆自身的接口
ConcretePrototype(具体原型类):实现一个克隆自身的操作
Client(调用类):让一个原型克隆自身从而创建一个新的对象
原型模式的核心是clone()方法,通过该方法进行对象的拷贝。
Java提供了一个Cloneable接口来表示这个对象时可拷贝的。
从“有可能被拷贝”到“可以被拷贝”
需要重写clone()方法。
举个栗子:
投简历
需求:找工作要向三个公司投简历
要求名字,性别等基本信息
简历类
public class Resume {
private String name;
private String age;
private String gender;
private String education;
public Resume(String name,String age,String gender,String education){
this.name=name;
this.age=age;
this.gender=gender;
this.education=education;
}
public void show(){
System.out.println(name+" "+" "+age+" "+gender+" "+" "+education);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getEducation() {
return education;
}
public void setEducation(String education) {
this.education = education;
}
}
public class Client {
@Test
public void fun1() {
Resume resume1=new Resume("张三", "18", "男", "本科");
Resume resume2=new Resume("张三", "18", "男", "本科");
Resume resume3=new Resume("张三", "18", "男", "本科");
resume1.show();
resume2.show();
resume3.show();
}
}
运行结果
张三 18 男 本科
张三 18 男 本科
张三 18 男 本科
问题及改进
在每次需要新简历的时候,都要使用
Resume resume=new Resume(“张三”, “18”, “男”, “本科”);
进行初始化对象。
三份简历需要三次实例化,如果是二十份、五十份,就需要二十次、五十次的实例化,显然对客户端的压力会增大。
@Test
public void fun2(){
Resume resume1=new Resume("张三", "18", "男", "本科");
Resume resume2=resume1;
Resume resume3=resume1;
resume1.show();
resume2.show();
resume3.show();
}
虽然运行结果一致,但这么做却没有实际意义。
传的是引用,而不是传值。
使用原型模式改进简历代码
@Test
public void fun2(){
Resume resume1=new Resume("张三", "18", "男", "本科");
Resume resume2=resume1;
Resume resume3=resume1;
resume1.show();
resume2.show();
resume3.show();
}
运行结果
张三 18 男 本科
张三 18 男 本科
张三 18 男 本科
浅复制
被拷贝对象的所有变量都含有与原对象相同的值,而且对其他对象的引用仍然是指向原来的对象。即浅拷贝只负责当前对象实例,对引用的对象不做拷贝。
java八大基本类型
int、float、double、char、byte、long、boolean、short
属于值类型变量。
在clone()中,将String 视为值类型变量。
其他类型如 ArrayList等都属于引用变量
深复制
被拷贝对象的所有变量都含有与原来对象相同的值,除了那些引用其他对象的变量。那些引用其他对象的变量将指向一个被拷贝的新对象,而不再是原有那些被引用对象。
示例说明
投简历
需求:找工作要向三个公司投简历
要求名字,性别等基本信息
必须含有工作经历。
//工作经历
public class WorkExperience implements Cloneable{
private String workDate;
private String company;
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = workDate;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
@Override
public WorkExperience clone(){
WorkExperience workExperience=null;
try{
workExperience=(WorkExperience)super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return workExperience;
}
}
//简历
public class Resume implements Cloneable{
private String name;
private String age;
private String gender;
private String education;
private WorkExperience work;
public Resume(String name,String age,String gender,String education){
this.name=name;
this.age=age;
this.gender=gender;
this.education=education;
work=new WorkExperience();
}
public void show(){
System.out.println(name+" "+" "+age+" "+gender+" "+" "+education);
System.out.println("工作经历:"+work.getWorkDate()+" "+work.getCompany());
}
public void SetWorkExperience(String workDate,String company){
work.setWorkDate(workDate);
work.setCompany(company);
}
/*
//浅复制
@Override
public Resume clone(){
Resume resume=null;
try{
resume=(Resume)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return resume;
}*/
//深复制
@Override
public Resume clone(){
Resume resume=null;
try{
resume=(Resume)super.clone();
resume.work=this.work.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return resume;
}
}
public class Client {
@Test
public void fun1(){
Resume resume1=new Resume("张三", "18", "男", "本科");
resume1.SetWorkExperience("2002-2012","xx公司");
Resume resume2= resume1.clone();
resume2.SetWorkExperience("2003-2014","yy公司");
Resume resume3= resume1.clone();
resume3.SetWorkExperience("2004-2016","zz公司");
resume1.show();
resume2.show();
resume3.show();
}
}
运行结果:
张三 18 男 本科
工作经历:2004-2016 zz公司
张三 18 男 本科
工作经历:2004-2016 zz公司
张三 18 男 本科
工作经历:2004-2016 zz公司
广告邮件发送
public class AdvTemplate {
private String subject="XX银行XXX活动";
private String context="只要刷卡就送100万";
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
}
public class Mail implements Cloneable {
private String receiver;//接收者
private String subject;//主题
private String appellation;//称谓
private String context;//正文
private String tail;//结尾
public Mail(AdvTemplate advTemplate) {
this.subject=advTemplate.getSubject();
this.context=advTemplate.getContext();
}
@Override
protected Mail clone(){
Mail mail=null;
try {
mail=(Mail)super.clone();
}catch (Exception e){
e.printStackTrace();
}
return mail;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getAppellation() {
return appellation;
}
public void setAppellation(String appellation) {
this.appellation = appellation;
}
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
public String getTail() {
return tail;
}
public void setTail(String tail) {
this.tail = tail;
}
}
import java.util.Random;
/**
* Created by yangy on 2017/6/21.
*/
public class Client {
private static final int MAX_COUNT=6;
public static void main(String[] args) {
int i=0;
Mail mail=new Mail(new AdvTemplate());
mail.setTail("XXX_copyright");
while (i<MAX_COUNT){
mail.setAppellation(getRandString(5)+"先生(女士)");
mail.setReceiver(getRandString(5)+"@"+getRandString(8)+".com");
sendMail(mail);
i++;
}
}
public static void sendMail(Mail mail){
System.out.println("标题:"+mail.getSubject()+"\t收件人:"+mail.getReceiver()+"\t 发送成功");
System.out.println("内容为:尊敬的"+mail.getAppellation()+","+mail.getContext());
}
public static String getRandString(int maxLength){
String source="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuffer stringBuffer=new StringBuffer();
Random random=new Random();
for (int i=0;i<maxLength;i++){
stringBuffer.append(source.charAt(source.length()-1));
}
return stringBuffer.toString();
}
}
运行结果:
标题:XX银行XXX活动 收件人:ZZZZZ@ZZZZZZZZ.com 发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100万
标题:XX银行XXX活动 收件人:ZZZZZ@ZZZZZZZZ.com 发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100万
标题:XX银行XXX活动 收件人:ZZZZZ@ZZZZZZZZ.com 发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100万
标题:XX银行XXX活动 收件人:ZZZZZ@ZZZZZZZZ.com 发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100万
标题:XX银行XXX活动 收件人:ZZZZZ@ZZZZZZZZ.com 发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100万
标题:XX银行XXX活动 收件人:ZZZZZ@ZZZZZZZZ.com 发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100万
原型模式的效果
Prototype有许多和Abstract Factory一样的效果;它对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。此外,这些模式使客户无需改变即可使用与特定应用相关的类。
优点:
1.如果创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
2.可以使用深克隆保持对象的状态。
3.扩展性好。
4.原型模式提供了简化的创建结构
缺点:
1.在实现深克隆的时候可能需要比较复杂的代码。
2.需要为每一个类配备一个克隆方法,可且这个克隆方法需要对类的功能进行通盘考虑,在修改已有类是违背“开闭原则”。
带有管理器的原型模式
示例代码:
public class Prototype implements Cloneable {
@Override
protected Prototype clone()throws CloneNotSupportedException{
return (Prototype) super.clone();
}
}
public class ConcretePrototype extends Prototype {
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException{
return (ConcretePrototype)super.clone();
}
}
public class ConcretePrototype extends Prototype {
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException{
return (ConcretePrototype)super.clone();
}
}
public class PrototypeManager {
private Map<String,Prototype> map=new HashMap<String,Prototype>();
public boolean contain(String name){
return map.containsKey(name);
}
public void add(String path,String name){
try {
//通过类路径来实例化该类
Prototype prototype=(Prototype) Class.forName(path).newInstance();
map.put(name,prototype);
System.out.println("Add"+prototype.getClass());
}catch (InstantiationException|IllegalAccessException|ClassNotFoundException e){
e.printStackTrace();
}
}
public Prototype get(String name)throws CloneNotSupportedException{
System.out.println("Clone a prototype");
return map.get(name).clone();
}
}
public class Client {
static PrototypeManager prototypeManager=new PrototypeManager();
static Prototype prototype;
public static void main(String[] args) {
String path="ClonePatternDemo04.ConcretePrototype";
String name="ConcretePrototype";
if(!prototypeManager.contain(name)){
prototypeManager.add(path,name);
}
//克隆出所需对象
try {
prototype=prototypeManager.get(name);
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
}
}
两种方法的比较
若需要创建的原型对象数目较少并且种类固定,使用普通的原型模型。在这种情况下原型对象的引用由客户端自己保存。
如果创建的原型对象不固定,则使用带有登记管理器的原型模式。这种情况下,客户端不保存原型对象的引用。
相关模式
1、原型模式可以和所有的工厂模式一起使用,原型模式通过clone()方法创建一个对象,再由工厂方法将创建的对象提供给调用者。
2、原型模式也常常和合成模式、装饰模式一起使用。
已知应用
1.音乐编辑器Unidraw绘图框架。
2.ThingLab中,用户能够生成复合对象,然后把它安装到一个可重用的对象库中促使它成为一个原型。