设计模式_原型模式

原型模式: Prototype Module

顾名思义,这个模式有一个样板实例,用户从这个样板对象中复制出一个内部属性一致的对象,这个过程也就是我们俗称的克隆。被复制的实例就被成为“原型”。

定义

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象

使用场景

  1. 类初始化需要消耗非常多的资源,包括数据,硬件资源,通过原型拷贝可以避免这些消耗
  2. 通过new产生一个对象需要非常繁琐的数据准备或访问权限
  3. 一个对象需要提供给其他对象访问,并且每个调用者可能都需要修改其值时,可以通过保护性原型拷贝拷贝多个对象供调用者使用。

注意
通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者是成本较高时,通过clone方法才能获得效率上的提升。

当然,原型模式除了实现Cloneable接口来实现,同样也存在这其他的实现方式.

实现

方法一: 实现Cloneable接口

import java.awt.List;
import java.util.ArrayList;

public class WordDocument implements Cloneable {

    private String mText;
    private ArrayList<String> mImages = new ArrayList<>();

    public WordDocument() {
        System.out.println("执行构造函数");
    }

    public ArrayList<String> getImages(){
        return mImages;
    }

    public void addImages(String img){
        mImages.add(img);
    }

    public String getText() {
        return mText;
    }

    public void setText(String mText) {
        this.mText = mText;
    }

    public void showDocument() {
        System.out.println("-------------文本内容----------");
        System.out.println("Text : " + mText);
        for (String string : mImages) {
            System.out.println("image name : " + string);
        }
        System.out.println("-------------内容结束----------");
        System.out.println("");
    }

    @Override
    protected WordDocument clone() throws CloneNotSupportedException {
        WordDocument doc = (WordDocument) super.clone();
        doc.mText = this.mText;
        return doc;
    }
}
public class PrototypeModule {

    public static void main(String[] args) {
        WordDocument document = new WordDocument();
        document.setText("初始文本信息");
        document.addImages("img1");
        document.addImages("img2");
        document.addImages("img3");
        document.showDocument();

        try {
            WordDocument documentClone = document.clone();
            System.out.println("修改text");
            documentClone.setText(" + 修改文本信息");
            documentClone.addImages("增加图片");
            documentClone.showDocument();

            System.out.println("增加图片");
            document.showDocument();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
}

设计模式_原型模式_第1张图片
从结果中可以看出,当我们在被拷贝出来的对象中修改ArrayList中的数据时,原对象的ArrayList数据也改变了,这是因为我们这里使用的方式只是一个浅拷贝,它其实并不是将原始文档的所有字段都重新构造了一份,而是直接将副本文档的字段直接引用原始文档的字段,因此当我们改变副本文档时,原始文档也随着改变了。

注意
为什么我们也修改了text,但是原始文档的值还是没有变化?
String类型是一个值类型
String指向的对象是一个不可变的对象,String类型的每次变化其实都是创建一个新的对象,之后再将引用指向这个新的对象而已。之前的那个对象其实还是存在的,只是我们不指向它了。
如果将String mText更改为StringBuffer mText, setText方法更改为append(text)的话结果就一样了。

深拷贝

鉴于上面的浅拷贝存在的一些问题,我们可以采用深层拷贝来解决它。之所以会出现修改副本时原本会改变,是因为副本是直接指向原本的引用的。因此,我们只需要在拷贝对象时,对于引用型的字段也采用拷贝的形式,而不是单纯的引用。

修改如下:

    @Override
    protected WordDocument clone() throws CloneNotSupportedException {
        WordDocument doc = (WordDocument) super.clone();
        doc.mText = this.mText;
        doc.mImages = (ArrayList<String>) this.mImages.clone();
        return doc;
    }

这样的话副本的引用型字段就指向原本字段的拷贝了,而不是它的本身,这样就不会引起连锁反应。
而上面的text由于是String类型,是一个值类型,不是引用类型

实际使用

在Android中,Intent也是使用了一个原型模式。而Intent为什么要使用原型模式呢?就是因为这个Intent和以前的一个Intent在很多地方是一样的,我们只需要更改它的部分信息就可以了,因此先拷贝,再修改,节约资源。而在Intent中,存在一段代码,这段代码也很好的解释了之前总结的那句话:clone方法是要看情况的,需要根据构造对象的成本来决定是clone还是new一个新的。

@Override
public Object clone(){
    return new Intent(this);
}

这里并没有使用super.clone()方法来实现对象的拷贝,而是直接new的一个新的,因为考虑到成本的问题

在Bundle以及OkHttp中也是使用了原型模式,其余使用的地方自己可以多看看,多找找。

—————————-华丽丽的分割线——————————

在这里记录一个自己用到的地方吧:
在进行登陆操作时,会通过一个session来保存用户的登录信息,而这些信息会在APP模块中调用,比如说登陆失效的判断等等。在登陆时,会先获取一次之前的登录信息,登陆完成后就要从网络上获取一个新的信息,例如登陆的时间存放到session中。
而这个时候就可以使用原型模式了,深层拷贝的方法:
第一次登陆时使用拷贝出来的副本进行登陆,等网络请求结束,成功获取到当前登陆的服务器时间时就修改原本中的用户信息。

总结

原型模式使用起来还是挺好的,而具体什么时候使用也是看项目的需求吧,看什么合适就使用什么模式

你可能感兴趣的:(设计模式,prototype,原型模式)