设计模式 - 原型模式

896 阅读3分钟

原型模式

原型模式:属于创建型设计模式,是比较简单的设计模式之一,但是平时却用得不多。

定义

从一个已有的对象通过克隆的方式创建另外一个可定制的对象,但是不需要知道创建时的细节。 这里的对象,通常指的是一些通过繁琐的操作才能创建出来的重对象。对于一些普通的轻对象new的效率比使用原型模式的效率要

又到了举例子时间: 在这个互联网时代,相信大家都网购过。在网络上有这形形色色的赚钱门路,开网店也是其中之一。Tom也相中了这一行,于是决定开一个网店,专门用来给顾客提供充值服务的。

例子

原始写法

//充值类的网店架构
public class OnlineShop {
    //店名
    private String name;
    //充值业务种类
    private ArrayList<String> business = new ArrayList<>();

    public String getName() {
        return name;
    }

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

    public void addBusiness(String businessName){
        this.business.add(businessName);
    }

    @Override
    public String toString() {
        return "OnlineShop{" +
                "网店名='" + name + '\'' +
                ", 充值业务范围=" + business +
                '}';
    }
}

//Tom开的网店
public class TomOnlineShop extends OnlineShop{
    public TomOnlineShop() {
        setName("汤姆充值店");
        addBusiness("QQ会员");
        addBusiness("QQ红钻");
        addBusiness("QQ绿钻");
        addBusiness("QQ黄钻");
        addBusiness("腾讯视频会员");
    }
}

我们来打印看看

//打印
public class Client {
    public static void main(String[] args) {
        OnlineShop tomOnlineShop = new TomOnlineShop();
        System.out.println("汤姆的网店:" + tomOnlineShop.toString());
    }
}

运行结果:

a.png 隔壁邻居Jerry 看到Tom的生活日渐滋润,他也打算模仿Tom开一家网店,于是他就仿照照Tom的网店开启了自己的网店。当然邻居之间也会有比较的,他想比Tom赚得多,活得好,那么他的业务范围也得广泛点吧,于是

//打印
public class Client {
    public static void main(String[] args) {
        OnlineShop tomOnlineShop = new TomOnlineShop();
        System.out.println("汤姆的网店:" + tomOnlineShop.toString());
        OnlineShop jerryOnlineShop = new TomOnlineShop();
        jerryOnlineShop.setName("杰瑞充值店");
        jerryOnlineShop.addBusiness("QQ粉钻");
        System.out.println("杰瑞的网店:"+jerryOnlineShop.toString());
    }
}

运行结果:

b.png 随后周围很多人都开始模仿起来了,所以我们要一个一个的对象new吗?先不说更改业务范围了,就算业务范围一样,看看我们需要new一千万个对象要多长时间

//打印
public class Client {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            OnlineShop tomOnlineShop = new TomOnlineShop();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("耗时: "+(endTime-startTime)+"毫秒");
    }
}

运行结果:

c.png 在仅仅生成一模一样的对象的情况下,一千万个花费了300+毫秒,那么原型模式又有什么优化呢?

原型模式写法

代码改造:

//充值类的网店架构
public class OnlineShop implements Cloneable{
    //店名
    private String name;
    //充值业务种类
    private ArrayList<String> business = new ArrayList<>();

    public String getName() {
        return name;
    }

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

    public void addBusiness(String businessName){
        this.business.add(businessName);
    }

    @Override
    public String toString() {
        return "OnlineShop{" +
                "网店名='" + name + '\'' +
                ", 充值业务范围=" + business +
                '}';
    }

    @Override
    protected Object clone(){
        OnlineShop onlineShop = null;
        try {
            onlineShop = (OnlineShop) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return onlineShop;
    }
}


//打印
public class Client {
    public static void main(String[] args) {
        OnlineShop tomOnlineShop = new TomOnlineShop();
        System.out.println("汤姆的网店:" + tomOnlineShop.toString());
        OnlineShop jerryOnlineShop = (TomOnlineShop) tomOnlineShop.clone();
        jerryOnlineShop.setName("杰瑞充值店");
        jerryOnlineShop.addBusiness("QQ粉钻");
        System.out.println("杰瑞的网店:" + jerryOnlineShop.toString());
    }
}

运行结果:

d.png 这个结果就跟用原始写法得到的结果一致了。

  • 具体写法 首先得让目标类实现Cloneable接口,这个接口是JDK自带的。但是有点小特殊,这个接口是没有方法的,它在这里只是起一个标记的作用。然后再目标类中需要覆写clone方法。既然这个Cloneable接口没有方法的,那么这个clone是哪里来的呢?其实在Cloneable中已经有注释写得很明白了,他是Object中的clone()。在clone()方法中用clone方式对对象进行了克隆。所以我们可以在Client中调用tomOnlineShopclone()方法创建出jerryOnlineShop对象。

这时,我们稍稍插播一下。测试一下同样创建一千万个对象用原型模式的方式耗时是多少

public class Client {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        OnlineShop tomOnlineShop = new TomOnlineShop();
        for (int i = 0; i < 10000000; i++) {
            OnlineShop jeryOnlineShop = (TomOnlineShop)tomOnlineShop.clone();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("耗时: "+(endTime-startTime)+"毫秒");
    }
}

运行结果:

e.png 这就是构建重对象在原始写法(new)和原型模式(clone)下的效率对比。

是不是一切都是很美好,很符合预期?其实不然,这时我们再回过头来看看Tom的网店:

//打印
public class Client {
    public static void main(String[] args) {
        OnlineShop tomOnlineShop = new TomOnlineShop();
        System.out.println("汤姆的网店:" + tomOnlineShop.toString());
        OnlineShop jerryOnlineShop = (TomOnlineShop) tomOnlineShop.clone();
        jerryOnlineShop.setName("杰瑞充值店");
        jerryOnlineShop.addBusiness("QQ蓝钻");
        System.out.println("杰瑞的网店:" + jerryOnlineShop.toString());
        System.out.println("汤姆的网店第二次打印:" + tomOnlineShop.toString());
    }
}

运行结果:

f.png Tom家的网店明明不支持 QQ蓝钻 充值啊,为什么会出现呢?

这时你就需要明白两个东西浅拷贝深拷贝

浅拷贝和深拷贝

  • 浅拷贝 对于要克隆的对象,只会对对象中的基础数据类型属性进行克隆,而非基础数据类型属性只会复制一份引用给到克隆出来的对象,让克隆出来的对象的非基础数据类型属性指向原生对象的内部元素地址,这种拷贝叫做浅拷贝\color{#FF0000}{浅拷贝}

  • 深拷贝 对于要克隆的对象,在clone()时也要把非基础数据类型属性进行拷贝,实现每个克隆出来的对象的非基础数据类型属性都是独立的,互不相干的,这种拷贝叫做深拷贝\color{#FF0000}{深拷贝}

注意:对于深拷贝中的非基础数据类型,也是需要实现Cloneable接口的\color{#FF0000}{对于深拷贝中的非基础数据类型,也是需要实现Cloneable接口的}

现在来修改一下上面的代码,让Jerry的网店和Tom的网店业务进行独立分割:

//充值类的网店架构
public class OnlineShop implements Cloneable{
    //店名
    private String name;
    //充值业务种类
    private ArrayList<String> business = new ArrayList<>();

    public String getName() {
        return name;
    }

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

    public void addBusiness(String businessName){
        this.business.add(businessName);
    }

    @Override
    public String toString() {
        return "OnlineShop{" +
                "网店名='" + name + '\'' +
                ", 充值业务范围=" + business +
                '}';
    }

    @Override
    protected Object clone(){
        OnlineShop onlineShop = null;
        try {
            onlineShop = (OnlineShop) super.clone();
            //这里进行了非基础数据类型的clone
            onlineShop.business = (ArrayList<String>) this.business.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return onlineShop;
    }
}

再次运行的结果:

g.png OK,这时是真的已经好了,每个对象的东西都独立了,互不干扰了。

扩展一

原型模式在创建新的对象时是不会执行类的构造方法的。Object类的clone方法原理是从内存中(堆内存)以二进制流的形式进行拷贝,给拷贝出来的对象重新分配一个内存块。所以从原理上构造方法没有被执行也是正常的。下面可以用代码看看 在OnlineShop类中添加其构造方法:

public OnlineShop() {
        System.out.println("这是OnlineShop的构造方法");
    }

运行结果:

h.png 从这里可以看出来,构造函数,就只在tomOnlineShop创建时调用了一次

扩展二

finalclone对象的clone和对象内的的final关键字是有冲突的。其实显而易见的,都已经设置成final了,也不能更改了,这样的属性clone出去也不能进行修改,clone和不clone就结果都是一样的。如果非要进行clone的话就只能删除掉final关键字,不能在成员变量上用final进行修饰。

扩展三

对于轻对象的创建,原始写法和原型模式写法的效率对比,直接上码:

//充值类的网店架构
public class OnlineShop implements Cloneable{

    //店名
    private String name;
    //充值业务种类
    private ArrayList<String> business = new ArrayList<>();

    public String getName() {
        return name;
    }

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

    public void addBusiness(String businessName){
        this.business.add(businessName);
    }

    @Override
    public String toString() {
        return "OnlineShop{" +
                "网店名='" + name + '\'' +
                ", 充值业务范围=" + business +
                '}';
    }

    @Override
    protected Object clone(){
        OnlineShop onlineShop = null;
        try {
            onlineShop = (OnlineShop) super.clone();
            onlineShop.business = (ArrayList<String>) this.business.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return onlineShop;
    }
}
//Tom开的网店
public class TomOnlineShop extends OnlineShop{
    public TomOnlineShop() {
    //这里为了保证对象是个轻对象,所以只做了设置店名的操作没其他繁琐的操作了。
        setName("汤姆充值店");
//        addBusiness("QQ会员");
//        addBusiness("QQ红钻");
//        addBusiness("QQ绿钻");
//        addBusiness("QQ黄钻");
//        addBusiness("腾讯视频会员");
    }
}

public class Client {
    public static void main(String[] args) {
        System.out.println("============= clone 模式");
        long startTime1 = System.currentTimeMillis();
        OnlineShop tomOnlineShop = new TomOnlineShop();
        for (int i = 0; i < 10000000; i++) {
            OnlineShop jeryOnlineShop = (TomOnlineShop)tomOnlineShop.clone();
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println("耗时: "+(endTime1-startTime1)+"毫秒");
        System.out.println("============= new 模式");
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            OnlineShop testOnlineShop = new TomOnlineShop();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("耗时: "+(endTime-startTime)+"毫秒");
    }
}

运行结果:

i.png 由此可见,原型模式是比较适合重对象的克隆而并不太适合轻对象

总结

虽然设计模式是比较标准的开发模式,但是我们也不能硬套。原型模式虽好,但也要区分对象的轻重。开发敲代码不是死套代码,要灵活运用,深刻理解。

提一嘴:设计模式通常都是多个模式组合一起用,例如工厂+策略,工厂+原型,策略+责任链……

以上就是我个人对设计模式的学习总结,如有错误请不吝指出,大家共同学习!