当前位置: 移动技术网 > IT编程>软件设计>设计模式 > 大话设计模式笔记(七)の原型模式

大话设计模式笔记(七)の原型模式

2019年07月13日  | 移动技术网IT编程  | 我要评论
举个栗子 问题描述 要求有一个简历类,必须要有姓名,可以设置性别和年龄,可以设置工作经历,最终需要三份简历。 简单实现 简历类 测试 测试结果 存在的问题 跟手写简历没有差别,三份简历需要三份实例化,如果客户需要二十份简历,那就得实例化二十次。 原型模式 定义 用原型实例指定创建对象的种类,并且通过 ...

举个栗子

问题描述

要求有一个简历类,必须要有姓名,可以设置性别和年龄,可以设置工作经历,最终需要三份简历。

简单实现

简历类

/**
 * 简历类
 * created by callmedevil on 2019/7/13.
 */
public class resume {
    
    private string name;
    private string sex;
    private string age;
    private string timearea;
    private string company;
    
    public resume (string name) {
        this.name = name;
    }

    /**
     * 设置个人信息
     * @param sex
     * @param age
     */
    public void setpersonalinfo(string sex, string age){
        this.sex = sex;
        this.age = age;
    }

    /**
     * 设置工作经历
     * @param timearea
     * @param company
     */
    public void setworkexperience(string timearea, string company) {
        this.timearea = timearea;
        this.company = company;
    }

    /**
     * 显示
     */
    public void display () {
        system.out.println(string.format("%s %s %s", name, sex, age));
        system.out.println(string.format("工作经历:%s %s", timearea, company));
    }
 
    // 此处省略get、set方法
    
}

测试

/**
 * 测试
 * created by callmedevil on 2019/7/13.
 */
public class test {

    public static void main(string[] args) {
        resume resumea = new resume("callmedevil");
        resumea.setpersonalinfo("男", "24");
        resumea.setworkexperience("2018-2019", "伟大的航道");

        resume resumeb = new resume("callmedevil");
        resumeb.setpersonalinfo("男", "24");
        resumeb.setworkexperience("2018-2019", "伟大的航道");

        resume resumec = new resume("callmedevil");
        resumec.setpersonalinfo("男", "24");
        resumec.setworkexperience("2018-2019", "伟大的航道");

        resumea.display();
        resumeb.display();
        resumec.display();
    }

}

测试结果

callmedevil 男 24
工作经历:2018-2019 伟大的航道
callmedevil 男 24
工作经历:2018-2019 伟大的航道
callmedevil 男 24
工作经历:2018-2019 伟大的航道

存在的问题

跟手写简历没有差别,三份简历需要三份实例化,如果客户需要二十份简历,那就得实例化二十次。

原型模式

定义

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

uml图

代码实现

/**
 * 简历类(实现jdk克隆接口)
 * created by callmedevil on 2019/7/13.
 */
public class resume implements cloneable{

    private string name;
    private string sex;
    private string age;
    private string timearea;
    private string company;

    public resume (string name) {
        this.name = name;
    }

    /**
     * 设置个人信息
     * @param sex
     * @param age
     */
    public void setpersonalinfo(string sex, string age){
        this.sex = sex;
        this.age = age;
    }

    /**
     * 设置工作经历
     * @param timearea
     * @param company
     */
    public void setworkexperience(string timearea, string company) {
        this.timearea = timearea;
        this.company = company;
    }

    /**
     * 显示
     */
    public void display () {
        system.out.println(string.format("%s %s %s", name, sex, age));
        system.out.println(string.format("工作经历:%s %s", timearea, company));
    }

    /**
     * 实现克隆方法,可进行自己的克隆逻辑
     * @return
     * @throws clonenotsupportedexception
     */
    @override
    protected object clone() throws clonenotsupportedexception {
        return super.clone();
    }

    // 此处省略get、set方法

}

测试

/**
 * 原型模式测试
 * created by callmedevil on 2019/7/13.
 */
public class test {

    public static void main(string[] args) throws clonenotsupportedexception {
        resume resumea = new resume("callmedevil");
        resumea.setpersonalinfo("男", "24");
        resumea.setworkexperience("2018-2019", "伟大的航道");

        // 只需要调用clone方法就可以实现新简历的生成,并且可以修改新简历的细节
        resume resumeb = (resume) resumea.clone();
        resumeb.setworkexperience("2019-2020", "新世界");

        resume resumec = (resume) resumea.clone();
        resumec.setpersonalinfo("男", "25");

        resumea.display();
        resumeb.display();
        resumec.display();
    }

}

测试结果

callmedevil 男 24
工作经历:2018-2019 伟大的航道
callmedevil 男 24
工作经历:2019-2020 新世界
callmedevil 男 25
工作经历:2018-2019 伟大的航道

好处

  • 一般在初始化的信息不发生变化的情况下,克隆是最好的方法。这既隐藏了对象创建的细节,又对性能是大大的提高。
  • 不用重新初始化对象,而是动态的获得对象运行时的状态。

浅复制与深复制

浅复制

在上面这个简历类中,如果字段是值类型(基本数据类型)的,则对该字段直接进行复制;如果是引用类型(string等),则/复制引用/但不/复制引用的对象/;因此,原始对象及其副本引用同一对象。

在此之前,我们先做一个简单的测试

    system.out.println("123" == "123");
    system.out.println("123".equals("123"));
    system.out.println(new string("123") == new string("123"));
    system.out.println(new string("123").equals(new string("123")));

相信有点基础的都知道答案吧?就不卖弄了,直接上结果

true
true
false
true

至于结果为什么会这样,网上也许多分析,此处重点在浅复制的讲解,因此不做过多叙述。

带着上面的理解再看下面的内容。在可克隆的简历类例子中,基本数据类型就没什么好测试的,有兴趣的也可以将年龄改成 int 类型;对于其他 string 类型,就拿 company 字段来说,我们新建一个测试类看下是什么结果

public class test2 {

    public static void main(string[] args) throws clonenotsupportedexception {
        resume resumea = new resume("callmedevil");
        resumea.setworkexperience("0", "伟大的航道");// 直接声明string

        resume resumeb = (resume) resumea.clone();

        // a 与 b 的公司两种比较
        system.out.println(resumea.getcompany() == resumeb.getcompany());
        system.out.println(resumea.getcompany().equals(resumeb.getcompany()));

        resumea.setworkexperience("0", new string("伟大的航道"));//new 的方式创建string

        resume resumec = (resume) resumea.clone();

        // a 与 c 的公司两种比较
        system.out.println(resumea.getcompany() == resumec.getcompany());
        system.out.println(resumea.getcompany().equals(resumec.getcompany()));
    }

}

比对第一个“123”的例子,稍微思考下在比对下面结果看是否一致

true
true
true
true

是的,这就是浅复制。不论a简历company直接声明的还是 new 出来的,在clone方法中都只会对 company 字段复制一份引用而已。因此才会出现 “==”“equal()”的结果都是“true”。对于引用类型来说,这个字段所在类实现了clone方法是不够的,它只会对引用类型复制一份引用,那如果连引用类型的字段也要创建一份新的数据,那便是 “深复制”

  • 浅复制就是,被复制的对象的所有变量都含有与原来对象相同的值,而所有其他对象的引用都仍然只想原来的对象。
  • 深复制把引用对象的变量指向复制过的新对象,而不是援用的被引用的对象。

深复制

此处不纠结于如何对上述 string 的深复制。现将简历类进行改造,把“工作经历”抽出成另一个类 workexperience

简历类2

/**
 * 简历类2(深复制)
 * created by callmedevil on 2019/7/13.
 */
public class resume2 implements cloneable {

    private string name;
    private string sex;
    private string age;
    // 工作经历
    private workexperience work;

    public resume2 (string name) {
        this.name = name;
        this.work = new workexperience();
    }
    
    private resume2(workexperience work) throws clonenotsupportedexception {
        this.work = (workexperience) work.clone();
    }

    /**
     * 设置个人信息
     * @param sex
     * @param age
     */
    public void setpersonalinfo(string sex, string age){
        this.sex = sex;
        this.age = age;
    }

    /**
     * 设置工作经历
     * @param timearea
     * @param company
     */
    public void setworkexperience(string timearea, string company) {
        // 此处赋值给 work 对象
        this.work.setworkdate(timearea);
        this.work.setcompany(company);
    }

    /**
     * 显示
     */
    public void display () {
        system.out.println(string.format("%s %s %s", name, sex, age));
        // 此处显示 work 对象的值
        system.out.println(string.format("工作经历:%s %s", work.getworkdate(), work.getcompany()));
    }

    /**
     * 实现克隆方法,可进行自己的克隆逻辑
     * @return
     * @throws clonenotsupportedexception
     */
    @override
    protected object clone() throws clonenotsupportedexception {
        // 调用私有的构造方法,让“工作经历”对象克隆完成,然后再给这个“简历”对象
        // 相关的字段赋值,最终返回一个深复制的简历对象
        resume2 obj = new resume2(this.work);
        obj.setname(this.name);
        obj.setsex(this.sex);
        obj.setage(this.age);
        return obj;
    }

    // 此处省略get、set方法
    
}

工作经历

/**
 * 工作经历
 * created by callmedevil on 2019/7/13.
 */
public class workexperience implements cloneable{

    private string workdate;

    private string company;

    @override
    protected object clone() throws clonenotsupportedexception {
        return super.clone();
    }

    // 此处省略get、set方法
    
}

测试类

/**
 * 深复制测试
 * created by callmedevil on 2019/7/13.
 */
public class testdeepclone {

    public static void main(string[] args) throws clonenotsupportedexception {
        resume2 resumea = new resume2("callmedevil");
        resumea.setpersonalinfo("男", "24");
        resumea.setworkexperience("2018-2019", "伟大的航道");

        resume2 resumeb = (resume2) resumea.clone();
        resumeb.setworkexperience("2019-2020", "新世界");

        resume2 resumec = (resume2) resumea.clone();
        resumec.setworkexperience("2020-xxxx", "木叶忍者村");

        resumea.display();
        resumeb.display();
        resumec.display();
    }

}

测试结果

callmedevil 男 24
工作经历:2018-2019 伟大的航道
callmedevil 男 24
工作经历:2019-2020 新世界
callmedevil 男 24
工作经历:2020-xxxx 木叶忍者村

可以看到,每次克隆都能将简历类中的工作经历类一同新建,而不是单纯的同个对象进行改变内容。如果是浅复制的实现,那么在相同测试类中会出现什么结果呢?应该能猜到吧,那就是三次输出都是一样的。
对于工作经历类浅复制实现本文就不描述了,有兴趣的可以自行测试,很简单,需要修改的地方有这么两处:

  • 简历类实现的克隆方法中直接调用 super.clone() 而不需要重写。
  • 取消工作经历类实现克隆接口及其方法。

只需要更改这两处,在相同的测试类中就能看到以下结果

callmedevil 男 24
工作经历:2020-xxxx 木叶忍者村
callmedevil 男 24
工作经历:2020-xxxx 木叶忍者村
callmedevil 男 24
工作经历:2020-xxxx 木叶忍者村

看到此处就不需要解释了吧,每次克隆只是新实例化了“简历”,但三个“简历”中的“工作经历”都是同一个,每次 set值 的时候都必将影响到所有对象,所以输出的工作经历都是相同的。这也同时说明了实现深复制的必要条件

  • 需要实现深复制的引用类型字段的类(比如上文中的工作经历)必须实现 cloneable 接口
  • 该字段的所属类(比如上文中的简历)实现的克隆方法中必须对该字段进行克隆与赋值

做到以上两点,即可将原本浅复制的功能转变为深复制

总结

原型模式无非就是对指定创建的原型实例进行复制,再对新对象另做操作,省去重新 new 的过程。其中复制便分为浅复制深复制

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网