继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

戏说设计模式 - 小鸟的名片 - 原型模式

草帽行者
关注TA
已关注
手记 6
粉丝 8
获赞 222
设计模式 - 原型模式

[toc]

简介

Prototype
原型模式:调用object.clone()方法,将对象复制一份,产生一个新的对象,和原有对象一样,然后再修改细节的数据,而不通过new 关键字来产生一个对象,而是通过这种复制的方式产生对象的实现模式叫原型模式.

  • 类别
    • 浅拷贝
    • 深拷贝
组成角色
  • 抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口。
  • 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
  • 客户(Client)角色:客户类提出创建对象的请求。
浅拷贝 与 深拷贝

浅拷贝

浅拷贝:只拷贝基本的数据类型,对于数组、容器对象、引用对象都不会拷贝,只是创建对应对象指向这些不能拷贝对象的地址空间

深拷贝

深拷贝:除拷贝基本数据类型外,还在原型模式中实现了数组、容器对象、引用对象的拷贝

优缺点
  • 优点

    • 性能优良:相对于new 一个对象来说,它由于是在内存中进行二进制流的拷贝,所以它的性能要好很多,这一点特别体现在循环体中产生大量对象时
    • 隐藏了创建新实例 的复杂性
  • 缺点
    • 每一个类必须被配一个clone方法
    • 深层复制比较复杂
适用场合
  • 复制对象的结构与数据
  • 希望对目标对象的修改稿不影响既有的原型对象
  • 创建对象的成本较大的情况,eg:类创建时比较耗时,有各种属性要配置
  • 一个对象多个修改者
注意问题
  • 构造函数不会执行
  • 原型模式与单例模式冲突:使用原型模式复制对象不会调用类的构造方法,单例模式要保证类的实例的唯一性,所以单例模式不要实现Cloneable
  • 原型模式中不要出现final对象
案例

小鸟的名片

大牛:“我们小鸟也是有身份的人哦,你看,你们公司都给你配上名片了。”

小鸟:“牛哥,你就别取笑我了,你又不是不知道我的水平,小菜鸟一枚,名片上印着高级工程师,就代表我水平达到高级了?面子工程,还不容给我来点实际的,比如说,把这经费给我,去买2本专业书籍,或者涨涨工资啊!”

大牛:“哈哈,不错,看来我们的小鸟很务实,今天就教你一手涨工资的技能。”

小鸟:“来吧!来吧!这个我最喜欢了”

大牛:“如果说,你们公司为了响应环保号召,决定使用电子名片,需要的时候,才根据电子名片进行打印,要你来开发这款项目的打印模块时,你打算怎么弄?”

小鸟:“如果要打印电子名片,首先我需要知道名片上有什么内容,就拿我这张名片来说,上面有:公司名称、公司产品、我的名字、电话号码...”,有这些信息,那么我这里就需要一个名片类(主要是我上面说的那些信息 + get/set + toString).

注意:为了解说方便,我们只使用 “公司名称”和“个人名称”这两个字段

public class VisitingCard {

    // 公司名称
    private String companyName;
    // 个人名称
    private String userName;

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Override
    public String toString() {
        return "VisitingCard{" +
                "companyName='" + companyName + '\'' +
                ", userName='" + userName + '\'' +
                '}';
    }
}

小鸟:“之后,我的打印,即客户端调用”

VisitingCard card = new VisitingCard();
card.setCompanyName("XX公司");
card.setUserName("小鸟");
System.out.println(card.toString());

大牛:“如果你们boss要求你打印5个人的名片呢?”

小鸟:“那还不简单,我直接把上面的打印代码,复制5份,就OK了”

大牛:“如果,现在要你你打印100张呢?难道你复制100份?”

小鸟:“我这一时图快,当然不能这么弄了,我可以将它用for循环包含起来的,由于打印的是5个不同人的名片,他们除了名字不同外,都是我们公司的,所以我这里还要声明一个数组,用来存放这5个人的名字”
说完,小鸟就重新写下了自己的代码.

String[] userNames = {"小鸟", "张三","李四","王五","赵六"};
for (int i = 0; i < userNames.length; i++) {
    VisitingCard card = new VisitingCard();
    card.setCompanyName("XX公司");
    card.setUserName(userNames[i]);

    System.out.println(card.toString());
}

大牛:“这还有点样子,你还记得你刚才所说的,名片上其他信息都一样,就是名字不同吗?”

小鸟:“记得啊,这里有什么问题吗?”

浅拷贝

大牛:“听说过克隆吗?”

小鸟:“听过,当然听过,记得初中生物课就有一节课是关于克隆的。这个跟我们今天的涨工资技能有关?”

大牛:“小鸟,不错呦!其实我们程序中也有克隆一词,它提供的对外接口就是Cloneable

第1步:实现Cloneable接口

实现Cloneable --> 覆写clone()

小鸟:“你是说让我用名片类去实现Cloneable”

小鸟:“牛哥,是这样吗?”

public class VisitingCard implements Cloneable{

    // 公司名称
    private String companyName;
    // 个人名称
    private String userName;

    @Override
    public VisitingCard clone() {
        VisitingCard visitingCard = null;

        try {
            visitingCard = (VisitingCard) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return visitingCard;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Override
    public String toString() {
        return "VisitingCard{" +
                "companyName='" + companyName + '\'' +
                ", userName='" + userName + '\'' +
                '}';
    }
}

第2步:客户端调用

String[] userNames = {"小鸟", "张三","李四","王五","赵六"};
VisitingCard cardClone = new VisitingCard();
cardClone.setTelPhones("我是电话号码");
for (int i = 0; i < userNames.length; i++) {
    // 调用clone方法创建对象,而不是new 
    VisitingCard card = cardClone.clone();
    card.setCompanyName("XX公司");
    card.setUserName(userNames[i]);

    System.out.println(card.toString());
}

深拷贝

大牛:“嗯嗯,悟性不错! 有没有想过给它增加电话号码?”

小鸟:“这还不容易,电话号码,像有在家那个地区用的,有在公司的所在地区用的,所以我们一般会有多个,故此,声明一个list对象用来存放电话号码,然后生命其get和set方法就ok了。”

大牛:“你要不要写写试一下.”

小鸟:“写就写。”

public class VisitingCard implements Cloneable{

    // 公司名称
    private String companyName;
    // 个人名称
    private String userName;

    private ArrayList<String> telPhones = new ArrayList<>();

    @Override
    public VisitingCard clone() {
        VisitingCard visitingCard = null;

        try {
            visitingCard = (VisitingCard) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return visitingCard;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public List<String> getTelPhones() {
        return telPhones;
    }

    public void setTelPhones(String addString) {
        this.telPhones.add(addString);
    }

    @Override
    public String toString() {
        return "VisitingCard{" +
                "companyName='" + companyName + '\'' +
                ", userName='" + userName + '\'' +
                ", telPhones=" + telPhones +
                '}';
    }
}

测试代码

String[] userNames = {"小鸟", "张三","李四","王五","赵六"};
VisitingCard cardClone = new VisitingCard();
for (int i = 0; i < userNames.length; i++) {
    // 调用clone方法创建对象,而不是new
    VisitingCard card = cardClone.clone();
    card.setCompanyName("XX公司");
    card.setUserName(userNames[i]);
    card.setTelPhones("139-0322-345"+i);
    System.out.println(card.toString());
}

小鸟:“牛哥,貌似我开始的思路不对耶,你看结果这里,电话号码,我本来是每人设置了一个,但是随着for循环执行次数的增加,电话号码也跟着增加了,是不是我的分析有问题啊!”
图片描述

大牛:“你前面的分析完全没问题,只是你需要改一下覆写的clone方法,让list对象也调用一下其clone 方法”

@Override
public VisitingCard clone() {
    VisitingCard visitingCard = null;

    try {
        visitingCard = (VisitingCard) super.clone();
        // 给list对象添加clone方法
        this.telPhones = (ArrayList<String>) this.telPhones.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }

    return visitingCard;
}

大牛:“你再运行试试。”

小鸟:“咦!这次是对的,每人只有一个号码,这是为啥呢,为啥我要调用list对象的clone方法呢?”

大牛:“这里就涉及到两个概念,浅拷贝与深拷贝,浅拷贝就是你前面的那种实现方式,它进行拷贝的时候,数组,引用类型这些数据是不会拷贝的。”

小鸟:“既然不会拷贝,那为什么我还能用呢?”

大牛:“这是因为,拷贝的时候,它会创建一个变量指向数组,引用类型这些数据所在的地址,其实就是相当于拷贝后的对象和拷贝前的对象共用了一个地址,这也就是为什么你会看到电话号码随for循环次数增加时,也会增加的原因,因为它们改变的是一处地址空间。”

小鸟:“那后面修改clone方法修改list对象,调用其clone 方法是不是就是就是叫深拷贝。”

大牛:“是的,我们小鸟挺有老板天分啊,挺会总结的.”

打开App,阅读手记
3人推荐
发表评论
随时随地看视频慕课网APP