设计模式_原型模式

设计模式_原型模式


作者:云都小生


概述



现实中有这么一种情况,我们需要同时用到许多个对象,这些对象都有一部分共同的特点(默认属性),但我们有时候还想要使它们有些不同(改变属性)。

例如,许多人经常跟PPT打交道,PPT中提供了多个不同的模版,当我们做PPT的时候,就可以直接使用这些模版,不用自己去制作。如果没有这些模版,我们每次都需要自己去制作,又或者是不断的copy,那该多烦。

在设计模式中,我们希望通过”模板”克隆出一个新的对象,以此节省我们许多的工作,提高工作效率,这种设计模式就是——原型模式。


简单原型模式的实现


//原型接口
interface Prototype {
    void printLog();
    void setName(String name);
    String getName();
    void setLogTxt(String Logs);
    String getLogTxt();
    Prototype clone();
}

//模版1
public class OneTemplate  implements  Prototype, Cloneable {
    private String name = "默认"; //工作人员名称
    private String LogTxt = "今天工作顺利,没有出现任何问题,就是工作量有点大,希望能坚持下去。";        //日志内容

    public void printLog()
    {
        System.out.println("工作人员:" + name + "\n日志内容:" + LogTxt);
    }

    public void setName(String name) 
    {
        this.name = name;
    }

    public String getName() 
    {
        return name;
    }

    public void setLogTxt(String LogTxt) 
    {
        this.LogTxt = LogTxt;
    }

    public String getLogTxt() 
    {
        return LogTxt;
    }

    public Prototype clone() 
    {
        Object object = null;
        try 
        {
            object = super.clone();
            return (Prototype)object;
        } catch (CloneNotSupportedException e) 
        {
            e.printStackTrace();
            return null;
        }

    }
}

//Client端
public class Client {
    public static void main(String[] args) {
        Prototype p1 = new OneTemplate();
        Prototype p2 = p1.clone();
        p2.setName("云都小生");
        p2.printLog();
    }
}

其实这里还有一个模板2,没有帖出来,跟模板1差不多,只不过日志内容(LogTxt)不一样。

Java有独特的对象克隆机制,需要有以下三个步骤。


1. 该”模板”类(或者克隆类)要实现Cloneable接口;

2. 该”模板”类(或者克隆类)要覆盖基类的clone()方法,并声明为public;

3. 在覆盖的clone方法中,要调用super.clone()方法;

通过这个简单的对象克隆示例,我们已经了解有对象克隆机制是多么方便。这个时候如果我们需要设计一个工作日志的程序,我们就可以定义不同的模板,需要的时候直接clone。(妈妈再也不用担心我的工作啦!)

但是,对象克隆设计还有两个比较重要的点,一个是浅克隆,另一个是深克隆。


遗难



上面我们已经设计了一个简单的日志记录程序框架,但是我们发现了一个问题,如果我们需要在日志中添加一些附件(运营文档)。我们希望这个东西也能被复制,生成一个不同的实例(附件),但是很遗憾,复制出来的附件,都指向了同一个对象。

什么意思呢?我们看一下浅克隆中的例子,你就明白了。Java中的克隆机制分为浅克隆和深克隆,浅克隆只能克隆基本数据类型的成员,而克隆引用类型成员的时候,是直接把指针到同一个引用对象。深克隆就能支持真正的克隆对象。


浅克隆


//原型接口
interface Prototype {
    void printLog();
    void setName(String name);
    String getName();
    void setLogTxt(String Logs);
    String getLogTxt();
    Prototype clone();
    void setTxTAnnex(TxTAnnex txtAnnex);
    TxTAnnex getTxTAnnex();
}

//原型1
public class OneTemplate  implements  Prototype, Cloneable {
    private TxTAnnex txtAnnex;
    private String name = "默认"; //工作人员名称
    private String LogTxt = "今天工作顺利,没有出现任何问题,就是工作量有点大,希望能坚持下去。";        //日志内容

    public void setTxTAnnex(TxTAnnex txtAnnex)
    {
        this.txtAnnex = txtAnnex;
    }

    public TxTAnnex getTxTAnnex()
    {
        return txtAnnex;
    }

    public void printLog()
    {
        System.out.println(LogTxt);
    }

    public void setName(String name) 
    {
        this.name = name;
    }

    public String getName() 
    {
        return name;
    }

    public void setLogTxt(String LogTxt) 
    {
        this.LogTxt = LogTxt;
    }

    public String getLogTxt() 
    {
        return LogTxt;
    }

    public Prototype clone() 
    {
        Object object = null;
        try 
        {
            object = super.clone();
            return (Prototype)object;
        } catch (CloneNotSupportedException e) 
        {
            e.printStackTrace();
            return null;
        }
    }
}

//附件类
public class TxTAnnex {
    String name = "运营文档";   //附件名

    public void setName(String name){
        this.name = name;
    }

    public String getName()
    {
        return name;
    }

    private void addAnnex() 
    {
        System.out.println(name + ",文档添加成功!");
    }
}

//Client端
public class Client 
{
    public static void main(String[] args) {
        Prototype p1 = new OneTemplate();
        TxTAnnex txtAnnex = new TxTAnnex();
        txtAnnex.setName("公众号运营文档");
        p1.setTxTAnnex(txtAnnex);


        Prototype p2 = p1.clone();
        p2.setName("云都小生");

        System.out.println("两个日志是否相同 " + (p2 == p1));
        System.out.println("两个日志中的附件是否相同 " + (p1.getTxTAnnex() == p2.getTxTAnnex()));
    }
}

我们增加了一个附件类,然后在原型类中,可以添加附件,当我们克隆的附件的时候,附件都指向了同一个对象。

程序运行结果是:

两个日志是否相同 false
两个日志中的附件是否相同 true

当涉及到引用类型的克隆时,我们就需要深克隆啦。


深克隆



在Java语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。需要注意的是能够实现序列化的对象其类必须实现 Serializable接口,否则无法实现序列化操作。

//原型接口
import java.io.IOException;

interface Prototype {
    void printLog();
    void setName(String name);
    String getName();
    void setLogTxt(String Logs);
    String getLogTxt();
    Prototype deepClone() throws IOException, ClassNotFoundException;
    void setTxTAnnex(TxTAnnex txtAnnex);
    TxTAnnex getTxTAnnex();
}

//原型1
import java.io.*;

public class OneTemplate  implements  Prototype, Serializable {
    private TxTAnnex txtAnnex;
    private String name = "默认"; //工作人员名称
    private String LogTxt = "今天工作顺利,没有出现任何问题,就是工作量有点大,希望能坚持下去。";        //日志内容

    public void setTxTAnnex(TxTAnnex txtAnnex)
    {
        this.txtAnnex = txtAnnex;
    }

    public TxTAnnex getTxTAnnex()
    {
        return txtAnnex;
    }

    public void printLog()
    {
        System.out.println(LogTxt);
    }

    public void setName(String name) 
    {
        this.name = name;
    }

    public String getName() 
    {
        return name;
    }

    public void setLogTxt(String LogTxt) 
    {
        this.LogTxt = LogTxt;
    }

    public String getLogTxt() 
    {
        return LogTxt;
    }

    public Prototype deepClone() throws IOException, ClassNotFoundException
    {
        //将对象写入流中
        ByteArrayOutputStream bao=new  ByteArrayOutputStream();
        ObjectOutputStream oos=new  ObjectOutputStream(bao);
        oos.writeObject(this);

        //将对象从流中取出
        ByteArrayInputStream bis=new  ByteArrayInputStream(bao.toByteArray());
        ObjectInputStream ois=new  ObjectInputStream(bis);
        return  (Prototype)ois.readObject();
    }
}

//附件原型
import java.io.Serializable;

public class TxTAnnex  implements Serializable{
    String name = "运营文档";   //附件名

    public void setName(String name){
        this.name = name;
    }

    public String getName()
    {
        return name;
    }

    private void addAnnex() 
    {
        System.out.println(name + ",文档添加成功!");
    }
}

//Client端
import java.io.IOException;

public class Client {

    public static void main(String[] args) {
        Prototype p1 = new OneTemplate();
        TxTAnnex txtAnnex = new TxTAnnex();
        txtAnnex.setName("公众号运营文档");
        p1.setTxTAnnex(txtAnnex);


        Prototype p2 = null;
        try {
            p2 = p1.deepClone();
        } catch (Exception e) 
        {
            System.out.println("对象克隆失败!");
            System.exit(0);
        } 
        p2.setName("云都小生");
        p2.printLog();

        System.out.println("两个日志是否相同" + (p2 == p1));
        System.out.println("两个日志中的附件是否相同" + (p1.getTxTAnnex() == p2.getTxTAnnex()));
    }
}

实现深克隆有几点需要注意的:


1. 克隆的原型类要实现Serializable类;

2. 深克隆不再像浅克隆那样要实现Cloneable类,覆盖clone方法,调用clone方法··· 而是采用写入流的方式来克隆;

3. 由于I/O操作可能会引发很多异常,必要时要抛出或者捕捉这些异常。

最主要的克隆代码在deepClone()方法里面,将自己写入流中,然后再从流取出来。这样一来,就可以实现两个附件对象的独立。

//将对象写入流中
ByteArrayOutputStream bao=new  ByteArrayOutputStream();
ObjectOutputStream oos=new  ObjectOutputStream(bao);
oos.writeObject(this);

//将对象从流中取出
ByteArrayInputStream bis=new  ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois=new  ObjectInputStream(bis); 
return  (Prototype)ois.readObject();


对象克隆管理器



现在我们引入一个对象克隆管理器的概念,我们有很多不同类型的文档,分别是公众号运营文档、用户数据分析文档··· 这个时候我们需要克隆哪一个文档,只需要向对象克隆管理器传入相应的参数即可。

跟工厂三兄弟有点类似,将对象创建封装起来,在客户端,只需要调用方法即可。这里我们还需要用到单例模式,因为对象克隆管理器需要独立。

//文档接口
interface Document extends  Cloneable {
    void setName(String name);
    String getName();
    Document clone();
    void printDoc();
}

//公众号运营文档
public class WecatPublicDoc implements Document
{
    String name = "微信公众号运营文档";

    public void setName(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }

    public Document clone()
    {
        Document wpd = null;
        try 
        {
            wpd = (Document)super.clone();
        } catch (CloneNotSupportedException e) 
        {
            e.printStackTrace();
        }
        return wpd;
    }

    public void printDoc()
    {
        System.out.println(name);
    }
}

//用户数据文档
public class UserDataDoc implements Document 
{
    String name = "用户数据文档";

    public void setName(String name)
    {
        this.name = name;
    }

    public String getName() 
    {
        return name;
    }

    public Document clone()
    {
        Document udd = null;
        try 
        {
            udd = (Document)super.clone();
        } catch (CloneNotSupportedException e) 
        {
            e.printStackTrace();
        }
        return udd;
    }

    public void printDoc()
    {
        System.out.println(name);
    }
}

//文档克隆管理器
import java.util.Hashtable;

public class DocCloneManager {
    //饿汉式单例模式
    private Hashtable ht=new Hashtable();
    private static DocCloneManager dcm = new DocCloneManager();

    public static DocCloneManager getManager()
    {
        return dcm;
    }

    private DocCloneManager()
    {
        ht.put("Wecat",new WecatPublicDoc());
        ht.put("User",new UserDataDoc());               
    }

    public Document docClone(String type)
    {
        return  ((Document)ht.get(type)).clone();
    }
}

//Client端
public class Client {
    public static void main(String[] args)
    {
        DocCloneManager dcm = DocCloneManager.getManager();
        Document d1 = dcm.docClone("Wecat");
        d1.printDoc();
        Document d2 = dcm.docClone("Wecat");
        d2.printDoc();
        System.out.println(d1 == d2);
    }
}

我们添加了一个单例类——对象克隆管理器,在这个管理器中我们存放了两种文档的示例。当我们需要克隆某一个文档的时候,直接传入参数即可,这样一来,客户端的代码复杂度下降了。

这个只是浅克隆的例子,当然,如果你希望这些文档里面有附件,也可以改成深克隆。


优缺点


优点:通过原型模式,我们能高效的克隆某些对象,简化对象创建的过程,可扩展性也很强。

缺点:如果我们需要给克隆类的克隆方法进行修改,就必须在内部修改,因为克隆方法在该克隆类内部。在实现深克隆的时候,需要用到I/O的操作,会比较复杂。

2017/10/9 11:53:02 @Author:云都小生(Cloudking)

参考文章:史上最全设计模式导学目录(完整版)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值