设计模式-原型模式

原型模式介绍

原型模式(Prototype Pattern)是创建型模式的一种。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。

原型模式定义

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

原型模式使用场景

  1. 如果类的初始化需要耗费较多的资源,那么可以通过原型拷贝避免这些消耗。
  2. 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
  3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以拷贝多个对象供调用者使用,即保护性拷贝。

原型模式 UML 类图


角色介绍:

  • Client:客户端角色。
  • Prototype:抽象原型角色,抽象类或者接口,用来声明clone方法。
  • ConcretePrototype:具体的原型类,是客户端角色使用的对象,即被复制的对象。

需要注意的是,Prototype 通常是不用自己定义的,因为拷贝这个操作十分常用,Java 中就提供了Cloneable 接口来支持拷贝操作,它就是原型模式中的Prototype。当然,原型模式也未必非得去实现Cloneable接口,也有其他的实现方式。

原型模式的实现

原型模式的核心是clone方法,通过该方法进行拷贝,这里举一个名片拷贝的例子。
现在已经流行电子名片了,只要扫一下就可以将名片拷贝到自己的名片库中, 我们先实现名片类。
Java 原型类就是 Cloneable 了

具体原型类

public class BusinessCard {
    private String name;
    private String company;

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

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

    public void setCompany(String company) {
        this.company = company;
    }

    @Override
    public BusinessCard clone() {
        BusinessCard businessCard = null;
        try {
            businessCard = (BusinessCard) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return businessCard;
    }

    public void show() {
        System.out.println("name:" + name + ", company:" + company);
    }
}

注意: Cloneable 是一个空接口,clone 方法是在 Object 类中定义,访问类型为 protected,当执行 clone 方法时会检测是不是 Cloneable 类型,如果不是就会报java.lang.CloneNotSupportedException 错误。

客户端

public class Client {
    public static void main(String[] args) {
        BusinessCard businessCard = new BusinessCard();
        businessCard.setName("小明");
        businessCard.setCompany("百度");
  
        BusinessCard cloneCard1 = businessCard.clone();
        cloneCard1.setName("小强");
        cloneCard1.setCompany("阿里");

        BusinessCard cloneCard2 = businessCard.clone();
        cloneCard2.setName("小红");
        cloneCard2.setCompany("腾讯");

        businessCard.show();
        cloneCard1.show();
        cloneCard2.show();
    }
}

除了第一个名片,其他两个名片都是通过clone方法得到的,需要注意的是,clone方法返回的对象是在内存中二进制拷贝的原对象,并不会执行其构造方法。运行结果为:

执行构造函数BusinessCard
name:小明, company:百度
name:小强, company:阿里
name:小红, company:腾讯

浅拷贝和深拷贝

原型模式涉及到浅拷贝和深拷贝的知识点,为了更好的理解它们,还需要举一些例子。

实现浅拷贝
上述的例子中,BusinessCard 的字段都是String类型的,如果字段是引用的类型的,会出现什么情况呢?如下所示:

public class BusinessCard implements Cloneable {
    private String name;
    private Company company = new Company();

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

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

    public void setCompany(String name, String address) {
        company.name = name;
        company.address = address;
    }

    @Override
    public BusinessCard clone() {
        BusinessCard businessCard = null;
        try {
            businessCard = (BusinessCard) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return businessCard;
    }

    public void show() {
        System.out.println("name:" + name + ", company:" + company);
    }
}
public class Company {
    String name;
    String address;

    @Override
    public String toString() {
        return "Company{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

客户端代码

public class Client {
    public static void main(String[] args) {
        BusinessCard businessCard = new BusinessCard();
        businessCard.setName("小明");
        businessCard.setCompany("百度", "北京");

        BusinessCard cloneCard1 = businessCard.clone();
        cloneCard1.setName("小强");
        cloneCard1.setCompany("阿里", "杭州");

        BusinessCard cloneCard2 = businessCard.clone();
        cloneCard2.setName("小红");
        cloneCard2.setCompany("腾讯", "深圳");

        businessCard.show();
        cloneCard1.show();
        cloneCard2.show();
    }
}

执行结果:

执行构造函数BusinessCard
name:小明, company:Company{name='腾讯', address='深圳'}
name:小强, company:Company{name='腾讯', address='深圳'}
name:小红, company:Company{name='腾讯', address='深圳'}

从结果可以看出 company 字段为最后设置的”腾讯”、”深圳”。这是因为 Object 类提供的 clone 方法,不会执行对象中的内部数组和引用对象的 clone 方法,导致 clone 出的对象的 company 指向的对象仍旧是原型中的 company 指向的对象。这种拷贝叫做浅拷贝

浅拷贝显然不符号要求,实际开发时我们希望每个对象可以独立变化,此时我们就需要通过深拷贝来解决,也就是执行 clone 方法时,也需要执行内部对象的 clone 方法。

实现深拷贝
首先需要让 Company 支持 clone,修改代码如下:

public class Company implements Cloneable {
    String name;
    String address;

    @Override
    public Company clone() {
        Company company = null;
        try {
            company = (Company) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return company;
    }

    @Override
    public String toString() {
        return "Company{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

修改 BusinessCard 的 clone 方法

public class BusinessCard implements Cloneable {
  ...
  @Override
    public BusinessCard clone() {
        BusinessCard businessCard = null;
        try {
            businessCard = (BusinessCard) super.clone();
            businessCard.company = company.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return businessCard;
    }
}

Client 不变,运行结果如下:

执行构造函数BusinessCard
name:小明, company:Company{name='百度', address='北京'}
name:小强, company:Company{name='阿里', address='杭州'}
name:小红, company:Company{name='腾讯', address='深圳'}

总结

原型模式比较简单,核心问题就是对原始对象进行拷贝,需要注意的就是深、浅拷贝的问题。开发中,为了避免操作副本时影响原始对象,应尽量采用深拷贝。

优点
原型模式是在内存中二进制流的拷贝,要比 new 一个对象的性能要好,特别是需要产生大量对象时。
缺点
直接在内存中拷贝,构造函数是不会执行的,这样就减少了约束,这既是优点也是缺点,需要在实际应用中去考量。

Android 源码中的原型模式

Android 源码中的常用的 Intent 就实现了 clone 方法。

public class Intent implements Parcelable, Cloneable {
    public Intent(Intent o) {
        this(o, COPY_MODE_ALL);
    }

    private Intent(Intent o, @CopyMode int copyMode) {
        this.mAction = o.mAction;
        this.mData = o.mData;
        this.mType = o.mType;
        this.mPackage = o.mPackage;
        this.mComponent = o.mComponent;

        if (o.mCategories != null) {
            this.mCategories = new ArraySet<>(o.mCategories);
        }

        if (copyMode != COPY_MODE_FILTER) {
            this.mFlags = o.mFlags;
            this.mContentUserHint = o.mContentUserHint;
            this.mLaunchToken = o.mLaunchToken;
            if (o.mSourceBounds != null) {
                this.mSourceBounds = new Rect(o.mSourceBounds);
            }
            if (o.mSelector != null) {
                this.mSelector = new Intent(o.mSelector);
            }

            if (copyMode != COPY_MODE_HISTORY) {
                if (o.mExtras != null) {
                    this.mExtras = new Bundle(o.mExtras);
                }
                if (o.mClipData != null) {
                    this.mClipData = new ClipData(o.mClipData);
                }
            } else {
                if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
                    this.mExtras = Bundle.STRIPPED;
                }
            }
        }
    }    
    @Override
    public Object clone() {
        return new Intent(this);
    }
    ...
}

可以看到 clone 方法实际上内部没有调用 super.clone() 方法来实现对象拷贝,而是调用了 new Intent(this)。可以看到 Intent 中有很多参数,使用 clone 即可增加构建对象的便捷性,在 clone 中使用 new,可以实现定制化拷贝对象,如果使用 super.clone() 则会将原对象进行拷贝。
使用 new 和 clone 需要根据构造对象的成本来决定,如果对象的构造成本比较高或者比较麻烦,那么使用 clone 方法效率更高,否则就使用 new

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容