09.简历复印 - 原型模式(Prototype Pattern)

9.1 夸张的简历

  • 时间: 3月23日21点   地点: 小菜房间   人物: 小菜, 大鸟

     “小菜, 在忙什么呢?” 大鸟回家来看到小菜在整理一堆材料.

     “明天我要去参加一个招聘会, 所以在准备简历呢.”
     “怎么这么多, 可能发的出去吗?” 大鸟很惊讶于小菜的建立有很厚的一叠.
     “没办法呀, 听其他同学说, 如果简历上什么也没有, 对于我们这种毕业生来说, 更加不会被重视了. 所以方式能写的, 我都写了, 明天能多投一些就多投一些, 以量取胜. 另外一些准备发给一些报纸上登广告的企业.”
     “哦, 我看看.” 大鸟拿起小菜的简历, “啊, 不会吧, 你连小学在哪里读, 得了什么奖状都写上去了? 那个吗不把幼儿园在哪里读也写上去.”
     “嘿嘿!”
     “C#精通, C++精通, Java 精通, SQL Server 精通, Oracle 精通, 搞没搞错, 你这些东西都精通?”
     “其实只是学过一些, 有什么办法呢, 要是不写, 人家就以为你什么都不懂, 我写的夸张一点, 可以多吸引吸引眼球吧.”
     “胡闹啊, 要是我是招聘的, 一个稍微懂点常识的人, 一看这种简历, 更加不会去理会. 这根本就是瞎扯嘛.”
     “那你说我怎么办? 我只是一个还没毕业的学生, 哪来的什么经验或工作经历, 我能写什么呢?”
     “哈, 说的也是, 对你们要求高其实也是不切实际的. 那你有么有准备求职信呢?”
     “求职信? 没考虑过, 那有空呀. 再说, 就写些空话, 废话, 只会浪费纸张.”
     “你以为你现在不是在浪费纸张? 你可知道, 当年的我们, 是如何写简历的吗?”
     “不知道, 难道都是手写?”
     “当然, 我们当年有不少同学都是手写简历和求职信, 这手抄的建立其实效果不差的, 只是比较麻烦. 又一次, 我只写了一份简历在人才市场上转悠, 身上也没带什么钱, 复印就不可能了, 于是在谈一家公司时, 人家想留下我的简历, 我却强力要求要了回来, 只留了个电话.”
     “啊, 还有你这样的求职者? 估计后来没戏了.”
     “错, 后来这家公司还真给我打电话了. 回想起来, 那时候对自己手写的简历很珍惜, 人家公司也很重视, 收到都会认真的看并答复, 哪像现在.” 大鸟感慨道, “印简历就像印草纸一样, 发简历更像是发广告. 我听说有些公司竟然在见面会结束是以拿不了不理由, 扔掉所受的简历就走的事情, 求职者要是看到岂不气晕啊. 不过话说回来, 像你这样自己都不重视的简历发出去, 人家公司不在意也在情理之中了.”
     “大鸟不会是希望我也手抄那么十几份简历吧.”
     “哈, 那当然没必要. 毕竟时代不同了. 现在程序员写简历都知道复印, 在编程的时候, 就不是那么多人懂的应用了.”
     “哪里呀, 程序员别的不一定行, Ctrl + C 到 Ctrl + V 实在是太溜了, 复制代码谁还不懂啊.”
     “对编程来说, 简单的复制粘贴极有可能造成重复代码的灾难. 我所说的意思你根本没听懂. 那就以刚才的例子, 我出个需求你写写看, 要求有一个简历类, 必须要有姓名, 可以设置性别和年龄, 可以设置工作经历. 最终我需要三份简历.”

9.2 简历代码初步实现

     二十分钟后, 小菜给出了一个版本.

  • 简历类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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;
    }
    public void setPersonaleInfo(String sex, String age) {
    this.sex = sex;
    this.age = age;
    }
    public void setWorkExperience(String timeArea, String company) {
    this.timeArea = timeArea;
    this.company = company;
    }
    @Override
    public String toString() {
    return this.name + " " + this.sex + " " + this.age + "\n" +
    "工作经历: " + this.timeArea + " " + this.company;
    }
    }
  • 客户端调用代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public static void main(String[] args) {
    Resume a = new Resume("大鸟");
    a.setPersonaleInfo("男", "29");
    a.setWorkExperience("1998-2000", "XX公司");
    Resume b = new Resume("大鸟");
    b.setPersonaleInfo("男", "29");
    b.setWorkExperience("1998-2000", "XX公司");
    Resume c = new Resume("大鸟");
    c.setPersonaleInfo("男", "29");
    c.setWorkExperience("1998-2000", "XX公司");
    System.out.println(a + "\n" + b + "\n" + c);
    }
  • 结果显示

    1
    2
    3
    4
    5
    6
    大鸟 男 29
    工作经历: 1998-2000 XX公司
    大鸟 男 29
    工作经历: 1998-2000 XX公司
    大鸟 男 29
    工作经历: 1998-2000 XX公司

     “很好, 这其实就是当年我手写简历时代的代码. 三份简历需要三次实例化. 你觉得这样的客户端代码是不是很麻烦, 如果要二十份, 你就需要二十次实例化.”
     “是呀, 而且如果我写错了一个字, 比如98年改成99年, 那就要改二十次.”
     “你为什么不这样写呢?”

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
Resume a = new Resume("大鸟");
a.setPersonaleInfo("男", "29");
a.setWorkExperience("1998-2000", "XX公司");
Resume b = a;
Resume c = b;
System.out.println(a + "\n" + b + "\n" + c);
}

     “哈, 这其实是传引用, 而不是传值, 这样做就如同是在 b 纸张和 c 纸张上写着建立在 a 出一样, 没有实际的内容的.”
     “不错, 不错, 小菜的基本功还是很扎实的. 那你觉得有什么办法?”
     “我好像听说过有Clone克隆这样的方法, 但怎么做不知道了.”

9.3 原型模式 (Prototype)

     “哈, 就是他了. 讲它前, 要先提一个设计模式.”

原型模式(Prototype), 用原型实例指定创建对象的种类, 并且通过拷贝这些原型创建新的对象.[DP]



     “原型模式其实就是从一个对象在创建另外一个可定制的对象, 而且不需要知道任何创建的细节. 我们来看看基本的原型模式的代码.”

  • 原型类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public abstract class Prototype {
    private String id;
    public Prototype(String id) {
    this.id = id;
    }
    public String getId() {
    return id;
    }
    // 抽象类关键就是有这样一个 Clone 方法
    public abstract Prototype clone();
    }
  • 具体原型类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class ConcretePrototype extends Prototype {
    public ConcretePrototype(String id) {
    super(id);
    }
    @Override
    public Prototype clone() {
    return new ConcretePrototype(super.getId());
    }
    }
  • 客户端代码

    1
    2
    3
    4
    public static void main(String[] args) {
    ConcretePrototype p1 = new ConcretePrototype("I");
    ConcretePrototype c1 = (ConcretePrototype) p1.clone();
    }

     “哦, 这样就可以不用实例化 ConcretePrototypeA 了, 直接克隆就行了?” 小菜问道.
     “说的没错, 就是这样. 但是对于 Java 来说, 那个原型抽象类 Prototype 根本用不着, 因为克隆模式实在是太常用了, 所以 命名空间中提供了 ICloneable 接口,, 其中之一就是唯一一个方法clone(), 这样你就知道只需要实现这个接口就可以完成原型模式了.现在明白了?去改吧.”
     “OK, 这东西看起来不难呀.”

9.4 简历的原型实现

     半小时后, 小菜的第二版本代码



  • 简历类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    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;
    }
    public void setPersonalInfo(String sex, String age) {
    this.sex = sex;
    this.age = age;
    }
    public void setWorkExperience(String timeArea, String company) {
    this.timeArea = timeArea;
    this.company = company;
    }
    @Override
    public String toString() {
    return this.name + " " + this.sex + " " + this.age + "\n" +
    "工作经历: " + this.timeArea + " " + this.company;
    }
    @Override
    protected Object clone() {
    try {
    Resume resume = (Resume) super.clone();
    resume.setPersonaleInfo(this.sex, this.age);
    resume.setWorkExperience(this.timeArea, this.company);
    return resume;
    } catch (CloneNotSupportedException e) {
    // this shouldn't happen, since we are Cloneable
    throw new RuntimeException(e);
    }
    }
    }
  • 客户端调用代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public static void main(String[] args) {
    Resume a = new Resume("大鸟");
    a.setPersonaleInfo("男", "29");
    a.setWorkExperience("1998-2000", "XX公司");
    // 只需要 clone() 方法就可以新简历的生成,
    // 并且可以在修改新简历的细节
    Resume b = (Resume) a.clone();
    b.setWorkExperience("1998-2006", "YY企业");
    Resume c = (Resume) a.clone();
    c.setPersonaleInfo("男", "24");
    System.out.println(a + "\n" + b + "\n" + c);
    }
  • 结果显示

    1
    2
    3
    4
    5
    6
    大鸟 男 29
    工作经历: 1998-2000 XX公司
    大鸟 男 29
    工作经历: 1998-2006 YY企业
    大鸟 男 24
    工作经历: 1998-2000 XX公司

     “怎么样大鸟, 这样一来, 客户端的代码就清爽多了, 而且你要是想改某份简历, 只需要对这份简历做一定的修改就可以了, 不会影响到其他的简历, 相同的部分就不用再重复了. 不过不知道这样子对性能是不是大大的提高了呢?”
     “当然是大大提高, 你想呀, 每 NEW 一次, 都需要执行一次构造函数, 如果构造函数的执行时间很长, 那么多次的执行这个初始化操作就实在是太低效了.一般在初始化的信息不发生变化的情况下, 克隆是最好的办法. 这及隐藏了对象的创建细节, 又对性能是大大的提高, 何乐而不为呢?”
     “的确, 我开始也没感觉到他的号, 听你这么一说, 感觉这样做的好处还真是不少, 他等于是不用重新初始化对象, 二是动态的获得对象的运行时的状态. 这个模式真的很不错.”

9.5 深复制与浅复制

     “别高兴得太早, 如果我现在要改需求, 你又头疼了. 现在的 ‘简历’ 对象里的数据都是 String 类型的, 而 String 类型是一种用于值类型特点的特殊引用类型.super.clone() 方法如果字段是值类型, 则对该字段执行逐位复制, 如果字段是引用类型, 则复制引用但不复制引用的对象; 因此, 原始对象及其复本引用同一对象. 设么意思呢, 就是说如果你的 ‘简历’ 类当中有对象引用, 那么引用的对象数据是不会被克隆过来的.”
     “没太听懂, 为什么不能一同复制过来呢?”
     “举个例子你就明白了, 你现在的 ‘简历’ 类当中有一个 ‘设置工作经历’ 的方法, 在现实设计当中, 一般会再有一个 ‘工作经历’ 类, 当中有 ‘时间区间’ 和 ‘公司名称’ 等属性, ‘简历’ 类直接调用这个对象即可. 你按照我说的在写写看.”
     “好的, 我试试.”
     半小时后, 小菜的第三个版本.



  • 工作经历类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class WorkExperience {
    private String workDate;
    private String company;
    public String getWorkDate() {
    return workDate;
    }
    public void setWorkDate(String workDate) {
    this.workDate = workDate;
    }
    public String getCompany() {
    return company;
    }
    public void setCompany(String company) {
    this.company = company;
    }
    }
  • 简历类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class Resume implements Cloneable {
    private String name;
    private String sex;
    private String age;
    private WorkExperience work;
    public Resume(String name) {
    this.name = name;
    work = new WorkExperience();
    }
    public void setPersonalInfo(String sex, String age) {
    this.sex = sex;
    this.age = age;
    }
    public void setWorkExperience(String timeArea, String company) {
    work.setCompany(company);
    work.setWorkDate(timeArea);
    }
    @Override
    public String toString() {
    return this.name + " " + this.sex + " " + this.age + "\n" +
    "工作经历: " + work.getWorkDate() + " " + work.getCompany();
    // 显示时, 显示 "工作经历" 的两属性值.
    }
    @Override
    public Object clone() {
    try {
    Resume resume = (Resume) super.clone();
    return resume;
    } catch (CloneNotSupportedException e) {
    // this shouldn't happen, since we are Cloneable
    throw new RuntimeException(e);
    }
    }
    }
  • 客户端代码调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public static void main(String[] args) {
    Resume a = new Resume("大鸟");
    a.setPersonaleInfo("男", "29");
    a.setWorkExperience("1998-2000", "XX公司");
    // b 和 c 都克隆于a, 但当他们都设置了 "工作经历"
    // 时, 我们我们希望的结果是三哥的显示不一样.
    Resume b = (Resume) a.clone();
    b.setWorkExperience("1998-2006", "YY企业");
    Resume c = (Resume) a.clone();
    c.setPersonaleInfo("男", "24");
    c.setWorkExperience("1998-2003", "ZZ企业");
    System.out.println(a + "\n" + b + "\n" + c);
    }
  • 结果显示

    1
    2
    3
    4
    5
    6
    7
    8
    // 可惜, 没有达到我们的要求, 三次显示的结果
    // 都是最后一次的值
    大鸟 男 29
    工作经历: 1998-2006 1998-2006
    大鸟 男 29
    工作经历: 1998-2006 1998-2006
    大鸟 男 24
    工作经历: 1998-2006 1998-2006

     “通过写代码, 我大概知道你的意思了, 由于他是浅表复制, 所以对于值类型, 没什么问题, 对引用类型, 就只是复制了引用, 对引用的对象还是指向了原来的对象, 所以就会出现我给a, b, c 三哥引用设置 ‘工作经历’, 但却同时看到三哥引用都是最后一次设置, 因为三个引用都指向了同一个对象.”
     “你写的和说的都很好, 就是这个原因, 这叫做 ‘浅复制’, 被复制对象的所有变量都含有与原来的对象相同的值, 而所有的对其他对象的引用都仍然指向原来的对象. 但是我们可能更需要这样一种需求, 把要复制的对象所引用的对象都复制一遍. 比如刚才的例子, 我们希望是 a, b, c 三个引用的对象都是不同的, 复制时就一变二, 二变三, 此时, 我们就叫这种方式为 ‘深复制’, 深复制把引用对象的变量指向复制过程的新对象, 而不是原有的被引用的对象.
     “那如果 ‘简历’ 对象引用了 ‘工作经历’, ‘工作经历’, 在引用 ‘公司’, ‘公司’ 在引用 ‘职位’ …. 这样一个引用一个, 很多层, 怎么办?”
     “这的确是个很难回答的问题, 深复制要深入到多少层, 需要事先就考虑好, 而且要当心楚翔循环引用的问题, 需要小心处理, 这里比较复杂, 可以慢慢研究. 就现在这个例子, 问题应该不大, 深入到第一层就可以了.”
     “那应该如何改, 我没方向了.”
     “好, 来看我的.”

9.6 简历的深复制实现



  • 工作经历类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class WorkExperience implements Cloneable{
    private String workDate;
    private String company;
    public String getWorkDate() {
    return workDate;
    }
    public void setWorkDate(String workDate) {
    this.workDate = workDate;
    }
    public String getCompany() {
    return company;
    }
    public void setCompany(String company) {
    this.company = company;
    }
    // 工作经历类实现克隆方法
    @Override
    protected Object clone() {
    try {
    WorkExperience workExperience = (WorkExperience) super.clone();
    return workExperience;
    } catch (CloneNotSupportedException e) {
    // since we are cloneable, this shouldn't happen
    throw new RuntimeException(e);
    }
    }
    }
  • 简历类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    public class Resume implements Cloneable {
    private String name;
    private String sex;
    private String age;
    private WorkExperience work;
    public Resume(String name) {
    this.name = name;
    work = new WorkExperience();
    }
    public void setPersonalInfo(String sex, String age) {
    this.sex = sex;
    this.age = age;
    }
    public void setWorkExperience(String timeArea, String company) {
    work.setCompany(company);
    work.setWorkDate(timeArea);
    }
    @Override
    public String toString() {
    return this.name + " " + this.sex + " " + this.age + "\n" +
    "工作经历: " + work.getWorkDate() + " " + work.getCompany();
    }
    // 提供 clone 方法, 以便克隆 '工作经历'数据
    private void setWork(WorkExperience work) {
    this.work = (WorkExperience) work.clone();
    }
    @Override
    public Object clone() {
    try {
    // 让工作经历克隆完成, 最终返回一个深复制简历对象.
    Resume resume = (Resume) super.clone();
    resume.setWork(this.work);
    return resume;
    } catch (CloneNotSupportedException e) {
    // this shouldn't happen, since we are Cloneable
    throw new RuntimeException(e);
    }
    }
    }
  • 同之前客户端代码, 其结果显示

    1
    2
    3
    4
    5
    6
    大鸟 男 29
    工作经历: 1998-2000 XX公司
    大鸟 男 29
    工作经历: 1998-2006 YY企业
    大鸟 男 24
    工作经历: 1998-2003 ZZ企业 // 达到了我们希望显示三次不同的结果

     “哈, 原来深复制是这个意思, 我明白了.”
     “由于在一些特定的场合, 会经常涉及深复制或浅复制, 比如说, 数据集对象 DataSet, 他就有 clone() 方法, 和 copy() 方法, clone() 方法, clone() 方法用来复制 DataSet 的结构, 但不复制 DataSet 的数据, 实现了原型模式的浅复制. copy() 方法不但复制结构, 也复制数据, 其实就是实现了原型模式的深复制.”

9.7 复制简历 vs. 手写求职信

     “哈, 这样说来, 我大量的复制我的简历, 当然是原型模式的最佳体现, 你的首超时代已经结束了.” 小菜得意的说.
     “我反倒认为, 与其简历写得如何如何, 不如认认真真的研究一下你要应聘的企业, 比如看看他们的网站和对职位的要求, 然后写一封比较中肯的求职信来的好. 加上你字还写得不错, 手写的求职信, 更加与众不同.”
     “那多累呀, 也写不了多少.”
     “嗨! 高科技害人呀, 尽管打印, 复印是方便很多, 所有的应聘这都这样做. 但也正因为此, 招聘方的重视程度也就同样低很多. 如果你是手写的求职信, 那就会有鹤立鸡群的效果, 毕竟这样的简历或求职信太少了.”
     “你说的有道理. 不过一封封的写出来感觉还是很费事呀?”
     “如果是写代码, 我当然会鼓励你去应用原型模式简化代码, 优化设计. 但对于求职, 你是愿意你的简历和求职信备受重视呢, 还是愿意和所有的毕业生一样千篇一律的毫无新意的碰运气?”
     “哈, 行, 听大鸟的总没错. 那我得好好想想求职信如何写?” 小才开始拿起了笔, 边写边念叨着, “亲爱的领导, 冒号…..”

~感谢捧场,您的支持将鼓励我继续创作~