后浪 来学习吧!设计模式【02】原型模式

定义

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

主要解决什么

在运行期建立和删除原型。

何时使用

  1. 当一个系统应该独立于它的产品创建,构成和表示时。
  2. 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
  3. 为了避免创建一个与产品类层次平行的工厂类层次时。
  4. 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

如何解决

利用已有的一个原型对象,快速地生成和原型对象一样的实例。

关键代码

  1. 实现克隆操作,在 JAVA 实现 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。
  2. 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。

怎么实现浅拷贝

以JAVA为例,实现Cloneable 接口,并重写clone方法。

这里就不贴代码了。

怎么实现深拷贝

使用clone方法 (需要实现Cloneable接口)

public class DeepProtoType implements Serializable, Cloneable {
    private String name;
    private DeepCloneableTarget deepCloneableTarget;
	//get set 方法这里就不写了
    //深拷贝- 方式1 使用clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object deep = null;
        //完成对基本数据类型和String的克隆
        deep = super.clone();
        //对引用类型的属性,进行单独处理。DeepCloneableTarget是辅助实现的。
        // 如果一个类的引用类型很多, 这种方式则需要一个一个处理。故通过序列化的方式来实现深拷贝
        DeepProtoType deepProtoType = (DeepProtoType) deep;
        deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
        return deep;
    }
}

客户端使用

    public static void main(String[] args) throws CloneNotSupportedException {
        DeepProtoType p = new DeepProtoType();
        p.setName("小明");
        p.setDeepCloneableTarget(new DeepCloneableTarget("小红"));
        //方式一 完成深拷贝
        DeepProtoType p2 = (DeepProtoType) p.clone();
        System.out.println(p.getDeepCloneableTarget().hashCode()); //输出结果356573597
        System.out.println(p2.getDeepCloneableTarget().hashCode()); //输出结果1735600054
    }

根据输出结果可见, 两个对象的引用类型的成员变量不是相同的, 而是一个新的对象, 可证明深复制成功.

通过对象的序列化实现 (需要实现Serializable接口) 推荐使用

public class DeepProtoType implements Serializable, Cloneable {
    private String name;
    private DeepCloneableTarget deepCloneableTarget;
	//get set 方法这里就不写了
	// 深拷贝 -方式2 通过对象的序列化实现(推荐)
    public Object deepClone() {
        //创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        try {
            //序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            //反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            DeepProtoType o = (DeepProtoType) ois.readObject();
            return o;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (Exception e2) {
                System.out.println(e2.getMessage());
            }
        }
    }
}

客户端使用

public static void main(String[] args) throws CloneNotSupportedException {
        DeepProtoType p = new DeepProtoType();
        p.setName("小明");
        p.setDeepCloneableTarget(new DeepCloneableTarget("小红"));
        //方式二 完成深拷贝
        DeepProtoType p2 = (DeepProtoType) p.deepClone();
        System.out.println(p.getDeepCloneableTarget().hashCode()); //输出结果621009875
        System.out.println(p2.getDeepCloneableTarget().hashCode()); //输出结果2065951873
}

优点: 1、性能提高。 2、逃避构造函数的约束。

缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。

Android中的应用

Bitmap中的copy方法、Java中的clone方法和Animator等。

下面以 Intent 为例来分析源码中的原型模式。

5668    /**
5669     * Copy constructor.
5670     */
5671    public Intent(Intent o) {
5672        this(o, COPY_MODE_ALL);
5673    }
5674// CopyMode 是Android 8.1 之后增加的。
5675    private Intent(Intent o, @CopyMode int copyMode) {
5676        this.mAction = o.mAction;
5677        this.mData = o.mData;
5678        this.mType = o.mType;
5679        this.mPackage = o.mPackage;
5680        this.mComponent = o.mComponent;
5681
5682        if (o.mCategories != null) {
5683            this.mCategories = new ArraySet<>(o.mCategories);
5684        }
5685
5686        if (copyMode != COPY_MODE_FILTER) {
5687            this.mFlags = o.mFlags;
5688            this.mContentUserHint = o.mContentUserHint;
5689            this.mLaunchToken = o.mLaunchToken;
5690            if (o.mSourceBounds != null) {
5691                this.mSourceBounds = new Rect(o.mSourceBounds);
5692            }
5693            if (o.mSelector != null) {
5694                this.mSelector = new Intent(o.mSelector);
5695            }
5696
5697            if (copyMode != COPY_MODE_HISTORY) {
5698                if (o.mExtras != null) {
5699                    this.mExtras = new Bundle(o.mExtras);
5700                }
5701                if (o.mClipData != null) {
5702                    this.mClipData = new ClipData(o.mClipData);
5703                }
5704            } else {
5705                if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
5706                    this.mExtras = Bundle.STRIPPED;
5707                }
5708
5709                // Also set "stripped" clip data when we ever log mClipData in the (broadcast)
5710                // history.
5711            }
5712        }
5713    }
5714
5715    @Override
5716    public Object clone() {// 重写clone
// clone 方法实际上在内部并没有调用 super.clone() 来实现拷贝对象,而是通过 new Intent(this)。
5717        return new Intent(this);
5718    }

使用 clone 和 new 需要根据构造对象的成本来决定,如果对象的构造成本比较高或者构造麻烦,那么使用 clone 函数效率较高,反之可以使用 new 关键字的形式。

这和 C++ 中的 copy 构造函数完全一致,将原始对象作为构造函数的参数,然后在构造函数内将原始对象数据挨个 copy。

使用场景

  1. 资源优化场景。
  2. 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
  3. 性能和安全要求的场景。
  4. 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  5. 一个对象多个修改者的场景。
  6. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
  7. 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

参考链接

原型模式

设计模式 ( 三 ) 原型模式

详解原型模式的浅复制(浅拷贝)和深复制(深拷贝)

感谢您阅读本文,希望能在微信公众号(见下方二维码)、掘金简书CSDN一起交流。

CoderWonder

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值