探究常用设计模式(二)原型模式

设计模式(英语 design pattern)是对面向对象设计中反复出现的问题的解决方案,学习设计模式也是深入体会面向对象魅力的过程。设计模式大致分为23种,下面,我将介绍一些常用的设计模式。

原型模式
原型模式,和字面意思一样,是以一个实例为原型,通过拷贝方式,创建出属性完全相同的新实例。

不用原型模式时,创建一个相同属性的实例一般是这样的:

public class negtive {
    /*==============服务端=======================*/
    static class Resume{
        private String name;
        private Integer age;

        public String getName() {
            return name;
        }

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

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }

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

    /*==============客户端=========================*/
    public static void main(String[] args) {
        Resume resume01= new Resume();
        resume01.setName("ling001");
        resume01.setAge(20);

        System.out.println(resume01);

        Resume resume02= new Resume();
        resume02.setName("ling002");
        resume02.setAge(23);

        System.out.println(resume02);
    }
}

可以看到,创建resume02的过程中,必须重新setName、setAge,创建过程繁琐,做了很多无谓的工作,这种方式当然是不可取的,那么如何避免这种重复的编码呢?这时就轮到克隆方法出场了。
clone是object中的一个方法,此方法未实现,是native方法,即本地方法(可以调用底层操作系统的方法),在调用本地方法创建对象时,比直接new创建对象效率要高。

升级后的代码如下

public class postvie_01 {
        /*==============服务端=======================*/
        static class Resume implements Cloneable{
            private String name;
            private Integer age;

            public String getName() {
                return name;
            }

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

            public Integer getAge() {
                return age;
            }

            public void setAge(Integer age) {
                this.age = age;
            }

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

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

        /*==============客户端=========================*/
        public static void main(String[] args) throws CloneNotSupportedException {
            Resume resume01= new Resume();
            resume01.setName("ling001");
            resume01.setAge(20);

            Resume resume02= (Resume) resume01.clone();
            resume02.setName("li666");

            System.out.println(resume01);
            System.out.println(resume02);
            System.out.println(resume01.equals(resume02));
        }
}

此时不需要重新new一个新对象,只需要服务端先实现一个Cloneable接口,并重写clone方法即可,此接口没有任何方法,只是一个标识接口。

这样一个新对象就创建出来啦,但是不要想着这么简单就结束了,这样的克隆方法叫“浅拷贝”。如果这时新需求来了,需要在这个对象中添加一种引用类型的属性,比如Date类型的成员属性,这时浅拷贝就无能为力了。为什么呢?我们用代码来说话:

public class negtive_02 {
    /*==============服务端=======================*/
    static class Resume implements Cloneable{
        private String name;
        private Integer age;
        private Date workExperience;

        public Date getWorkExperience() {
            return workExperience;
        }

        public void setWorkExperience(Date workExperience) {
            this.workExperience = workExperience;
        }

        public String getName() {
            return name;
        }

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

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "Resume{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", workExperience=" + workExperience +
                    '}';
        }

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

    /*==============客户端=========================*/
    public static void main(String[] args) throws CloneNotSupportedException{
        Resume resume01= new Resume();
        Date date = new Date();
        resume01.setName("ling001");
        resume01.setAge(20);
        resume01.setWorkExperience(date);

        Resume resume02= (Resume) resume01.clone();
        resume02.getWorkExperience().setTime(0);

        System.out.println(resume02);
        System.out.println(resume01);

        System.out.println(resume01.equals(resume02));
    }
}

执行结果如下:
在这里插入图片描述
可以看到,此时两个实例共用了一个workExperience属性,并没有达到我们预期的效果,这时怎么回事呢?这里我们有必要学习一下clone方法的实现原理。

浅拷贝(浅复制):clone得到的对象a其实只是对被clone对象b的引用,即对象a是指向b对象上的。简单的说,就是将原始对象中所有东西原封不动的复制一遍,引用类型的引用地址也复制一遍,所以就造成了上面出现的问题。
在这里插入图片描述
相对应的,深拷贝就解决了这一问题,使用深拷贝需要重写clone方法,

	@Override
        public Object clone() throws CloneNotSupportedException {
            Resume clone = (Resume) super.clone();
            Date cloneDate = (Date) clone.getWorkExperience().clone();
            clone.setWorkExperience(cloneDate);
            return clone;
        }

运行结果如下:
在这里插入图片描述
其实就是对浅拷贝的字段再进行深拷贝。

以上面用到的Date引用类型对象为例:
在这里插入图片描述
可以看到Date是实现了Cloneable接口的,即表示Date也是可以进行clone(克隆)的。只需要将浅拷贝的Date再使用clone方法进行一次深拷贝,再赋值给clone的对象即可。具体参照上面重写的clone方法。

但是这种方法也有很大的缺陷,如果一个类有很多引用类型,难道我们都要一个一个的去重写克隆方法吗?

如何解决这一问题呢?

这里我们抛出一个问题,Java创建对象有几种方式呢?
答案是4种,
构造器、反射、克隆、反序列化。

这里我们可以用反序列化来进行深克隆,将对象整个序列化,通过io反序列化,这样将得到一个全新的对象,上面的问题不就迎刃而解了吗?

具体操作如下:

    @Override
    public Object clone() throws CloneNotSupportedException{
        try {
            FileOutputStream out = new FileOutputStream("F:\\a.txt");
//            out.write(this); // 只能写字节
            ObjectOutputStream oos = new ObjectOutputStream(out);
            oos.writeObject(this); // 序列化对象
            oos.close();

            FileInputStream in = new FileInputStream("F:\\a.txt");
//            in.read(this);// 只能读字节
            ObjectInputStream ois = new ObjectInputStream(in);
            Object clone = ois.readObject();

            ois.close();
            return clone;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

此时就完美解决上述问题了。不过这里涉及io流,代码中有盘符这种“忌讳”字符串,所以我们可以直接写到内存中进行反序列化,代码如下:

    @Override
    public Object clone() throws CloneNotSupportedException{
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();// 写入内存
            ObjectOutputStream oos = new ObjectOutputStream(out);
            oos.writeObject(this); // 序列化对象
            oos.close();

            byte[] bytes = out.toByteArray(); // 内存中取出数据
            ByteArrayInputStream in = new ByteArrayInputStream(bytes);// 内存中读取出来
            ObjectInputStream ois = new ObjectInputStream(in);
            Object clone = ois.readObject();

            ois.close();
            return clone;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

就此,完美解决所有问题~
这里的io流用到了装饰者模式,我们下节解析

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值