设计模式(1)--创建型--原型模式ProtoType

354 阅读3分钟

一、原型模式ProtoType简介

基于指定的原型对象创建新对象。其proto跟original同义。

image.png uml类图非常简单,原型类需要实现Cloneable接口,当然这里cloneable是一个标志接口。重写Object类的clone方法。

public interface Cloneable {  // 只是一个标志接口
}

二、原型模式关键点:原型类的clone()实现

2.1、浅拷贝

如果一个model的成员属性全部都是基本数据类型,那都是浅拷贝。
如果成员是String引用类型,String比较特殊,它虽然是引用类型,但String没有重写clone方法,所以不能调用clone方法,可当做基本数据类型使用,当修改clone对象的String类型的值,该成员就会指向另外的String对象,不会影响到原型对象的值。

@Override
protected WordDoc clone() throws CloneNotSupportedException {
    return (WordDoc) super.clone();
}

如果成员是其他引用类型,修改创建的对象的值,原型对象的该值也会跟着变化。二者引用指向同一块内存区域,地址传递。

2.2、深拷贝

除String类的其他引用类型,则需要深拷贝,才是我们原型模式需要关注的点。下面以ArrayList对象为例,其实现的clone方法:

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}
// 复制数组数据
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}
// native进行复制,不是赋值,是复制
public static native void arraycopy(Object src,  int  srcPos,Object dest, int destPos,
                                                            int length);

所以针对引用类型,我们直接使用java封装好的clone方法即可。

@Override
protected WordDoc clone() {
    try {
        // 如果WordDoc的成员全部是基本类型,那可以直接浅拷贝就可以
        // 如果存在引用类型,则需要深拷贝
        WordDoc doc = (WordDoc) super.clone();
        //这里的ArrayList就是深拷贝 调用其clone方法。
        doc.mImages = (ArrayList<String>) this.mImages.clone();
        return doc;
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return null;
}

三、完整的例子

public class WordDoc implements Cloneable {
    private String mText;
    private ArrayList<String> mImages = new ArrayList<>();

    public WordDoc() {
        System.out.println("构造函数");
    }

    public String getText() {
        return mText;
    }

    public void setText(String text) {
        mText = text;
    }

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

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

    public void show() {
        System.out.println("--------------" + this + "------------");
        System.out.println("text-->" + mText);
        for (String img : mImages) {
            System.out.println(img);
        }
    }
    
     @Override
    protected WordDoc clone() {
        try {
            // 如果WordDoc的成员全部是基本类型,那可以直接浅拷贝就可以
            // 如果存在引用类型,则需要深拷贝
            WordDoc doc = (WordDoc) super.clone();
            //这里的ArrayList就是深拷贝
            doc.mImages = (ArrayList<String>) this.mImages.clone();
            return doc;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

client调用:
public static void main(String[] args) {
    WordDoc doc = new WordDoc();
    doc.setText("a");
    doc.addImages("http://url1");
    WordDoc doc2 = doc.clone();
    doc2.addImages("http://url2");
    doc.getImages().remove("http://url1");
    doc.show();
    doc2.show();
}

log日志如下:

image.png

只打印了一次构造函数,说明clone对象时并不会走构造
当有除String外的引用类型成员时,使用深拷贝,原型对象和clone创建的对象二者不会相互影响。

四、从log得出使用场景

4.1、如果创建对象时走构造比较耗费资源时,再次创建该类对象,可以考虑改模式。

4.2、深拷贝出来的对象不会对原型对象有影响。当有多个调用方时,只有1份原型对象,但每个调用方都会对这个对象进行操作,那为了避免影响,可以针对每个调用方clone一个对象,执行操作后,返回给调用方。原型对象则还是最初的那一份。这里就是所谓的保护性拷贝

最后,特别强调下,调用clone方法的话,则一定要实现Cloneable接口且实现clone方法,缺一不可。