【java设计模式】原型模式(创建型模式)

简单理解原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在软件系统中,有些对象的创建过程较为复杂,而且有时候需要频繁创建,原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象,这就是原型模式的意图所在

使用场景

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

原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
spring中bean的创建实际就两种模式:单例模式和原型模式。(当然,原型模式需要和工厂模式搭配起来)

结构

在这里插入图片描述
Client: 客户端角色
Prototype: 抽象原型角色,抽象类或者接口,用来声明clone方法
ConcretePrototype: 具体的原型类,是客户端角色使用的对象,即被复制的对象

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:

  1. 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  2. 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此Prototype类需要将clone方法的作用域修改为public类型。

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

实现代码

拿孙悟空来举例

浅克隆

Work

public class Work {
    private String address;
    private String doWhat;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getDoWhat() {
        return doWhat;
    }

    public void setDoWhat(String doWhat) {
        this.doWhat = doWhat;
    }
}

WuKong(ConcretePrototype)

public class WuKong implements Cloneable{
    private String name;
    private Work work = new Work();

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

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

    public void setWork(String address, String doWhat) {
        this.work.setAddress(address);
        this.work.setDoWhat(doWhat);
    }

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

    public void say(){
        System.out.println("我是" + name + ",我的工作是" + work.getAddress() + work.getDoWhat());
    }
}

TestClient(Client)

public class TestClient {
    public static void main(String[] args) {
        test1();
        System.out.println("======================================");
        test2();
    }

    public static void test1() {
        WuKong wukong = new WuKong();
        wukong.setName("真·悟空");
        wukong.setWork("在原地", "看护唐僧");
        wukong.say();

        WuKong clone_wukong1 = wukong.clone();
        clone_wukong1.setName("克隆悟空1号");
        clone_wukong1.setWork("去河边", "打水");
        clone_wukong1.say();

        WuKong clone_wukong2 = wukong.clone();
        clone_wukong2.setName("克隆悟空2号");
        clone_wukong2.setWork("去村里", "化斋");
        clone_wukong2.say();
    }

    public static void test2() {
        WuKong wukong = new WuKong();
        wukong.setName("真·悟空");
        wukong.setWork("在原地", "看护唐僧");

        WuKong clone_wukong1 = wukong.clone();
        clone_wukong1.setName("克隆悟空1号");
        clone_wukong1.setWork("去河边", "打水");

        WuKong clone_wukong2 = wukong.clone();
        clone_wukong2.setName("克隆悟空2号");
        clone_wukong2.setWork("去村里", "化斋");

        wukong.say();
        clone_wukong1.say();
        clone_wukong2.say();
    }
}

输出

执行了构造函数
我是真·悟空,我的工作是在原地看护唐僧
我是克隆悟空1号,我的工作是去河边打水
我是克隆悟空2号,我的工作是去村里化斋
======================================
执行了构造函数
我是真·悟空,我的工作是去村里化斋
我是克隆悟空1号,我的工作是去村里化斋
我是克隆悟空2号,我的工作是去村里化斋

为什么test2的克隆悟空做的工作都和真·悟空的工作一样呢?这是因为Object类提供的clone方法不会拷贝内部数组和引用对象,导致它们仍旧指向原来对象的内部元素地址,这种拷贝叫做浅拷贝
WuKong类中,Work字段是引用类型,WuKong被拷贝后,Work字段仍旧指向原来的WuKong对象的Work字段地址,这样每次设置Work的值,都会覆盖上一次设置的值
这就是浅克隆存在的问题

  • 被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象

深克隆

让我们改造一下上面的代码

Work

Work类实现Cloneable接口,重写clone()方法

public class Work implements Cloneable{
    private String address;
    private String doWhat;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getDoWhat() {
        return doWhat;
    }

    public void setDoWhat(String doWhat) {
        this.doWhat = doWhat;
    }

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

WuKong

public class WuKong implements Cloneable{
    private String name;
    private Work work = new Work();

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

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

    public void setWork(String address, String doWhat) {
        this.work.setAddress(address);
        this.work.setDoWhat(doWhat);
    }

    @Override
    public WuKong clone(){
        WuKong wuKong = null;
        try {
            wuKong = (WuKong) super.clone();
            wuKong.work = this.work.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return wuKong;
    }

    public void say(){
        System.out.println("我是" + name + ",我的工作是" + work.getAddress() + work.getDoWhat());
    }
}

将Work属性也进行克隆

输出

执行了构造函数
我是真·悟空,我的工作是在原地看护唐僧
我是克隆悟空1号,我的工作是去河边打水
我是克隆悟空2号,我的工作是去村里化斋
======================================
执行了构造函数
我是真·悟空,我的工作是在原地看护唐僧
我是克隆悟空1号,我的工作是去河边打水
我是克隆悟空2号,我的工作是去村里化斋

利用序列化和反序列化实现深克隆

Work

public class Work implements Serializable {
    private static final long serialVersionUID = 7344227889442923109L;
    private String address;
    private String doWhat;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getDoWhat() {
        return doWhat;
    }

    public void setDoWhat(String doWhat) {
        this.doWhat = doWhat;
    }
}

WuKong

public class WuKong implements Serializable {
    private static final long serialVersionUID = 7345427889442923974L;
    private String name;
    private Work work = new Work();

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

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

    public void setWork(String address, String doWhat) {
        this.work.setAddress(address);
        this.work.setDoWhat(doWhat);
    }

    public void say(){
        System.out.println("我是" + name + ",我的工作是" + work.getAddress() + work.getDoWhat());
    }
}

CloneUtils

public class CloneUtils {
    public static <T extends Serializable> T clone(T obj){
        T cloneObj = null;
        try {
            //写入字节流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();

            //分配内存,写入原始对象,生成新对象
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);
            //返回生成的新对象
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }
}

TestClient

public class TestClient {
    public static void main(String[] args) {
        WuKong wukong = new WuKong();
        wukong.setName("真·悟空");
        wukong.setWork("在原地", "看护唐僧");

        WuKong clone_wukong1 = CloneUtils.clone(wukong);
        clone_wukong1.setName("克隆悟空1号");
        clone_wukong1.setWork("去河边", "打水");

        WuKong clone_wukong2 = CloneUtils.clone(wukong);
        clone_wukong2.setName("克隆悟空2号");
        clone_wukong2.setWork("去村里", "化斋");

        wukong.say();
        clone_wukong1.say();
        clone_wukong2.say();
    }
}

输出

执行了构造函数
我是真·悟空,我的工作是在原地看护唐僧
我是克隆悟空1号,我的工作是去河边打水
我是克隆悟空2号,我的工作是去村里化斋

效率测试

代码

public class EfficiencyTest {

    public static void main(String[] args) {
        testNew(300);
        testClone(300);
    }

    public static void testClone(int num){
        Long startTime = System.currentTimeMillis();

        ObjDemo demo = new ObjDemo();
        for (int i=0;i<num;i++){
            ObjDemo demo_clone = demo.clone();
        }

        Long endTime = System.currentTimeMillis();
        System.out.println("clone方式耗时:" + (endTime-startTime));
    }

    public static void testNew(int num){
        Long startTime = System.currentTimeMillis();

        for (int i=0;i<num;i++){
            ObjDemo demo = new ObjDemo();
        }

        Long endTime = System.currentTimeMillis();
        System.out.println("new方式耗时:" + (endTime-startTime));
    }
}

class ObjDemo implements Cloneable{
    public ObjDemo() {
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

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

输出

new方式耗时:3313
clone方式耗时:11

原型模式优缺点

优点:

  • 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率
  • 可以动态增加或减少产品类
  • 原型模式提供了简化的创建结构
  • 可以使用深克隆的方式保存对象的状态

缺点:

  • 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”
  • 在实现深克隆时需要编写较为复杂的代码。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值