文档章节

小鸟的名片 - 原型模式

 草帽行者
发布于 2016/05/16 10:37
字数 2191
阅读 79
收藏 1

#设计模式 - 原型模式

[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 方法是不是就是就是叫深拷贝。”

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

© 著作权归作者所有

共有 人打赏支持
粉丝 6
博文 35
码字总数 29999
作品 0
广州
程序员
设计模式(十六)原型模式

相关文章 设计模式系列 前言 公众号有同学留言设计模式,才发现好久没有写设计模式了。关于创建型设计模式只差原型模式没写了,这一篇就来填补这个空缺。 1.原型模式定义 原型模式定义 定义:...

刘望舒
2017/06/30
0
0
只有一个中国 - 单例模式

设计模式 - 单例模式 [toc] 简介 Singleton 保证类有且仅有一个实例,并提供一个访问它的全局站点 类型 饿汉模式 懒汉模式 懒汉模式 PK 饿汉模式 | / | 懒汉模式 | 饿汉模式 | 备注 || :--...

草帽行者
2016/05/11
28
0
Python+树莓派+YOLO打造一款人工智能相机

摘要:今天,我们将自己动手打造出一款基于深度学习的照相机,当小鸟出现在摄像头画面中时,它将能检测到小鸟并自动进行拍照。最终成品所拍摄的画面如下所示: 不久之前,亚马逊刚刚推出了D...

Freebuf
01/03
0
0
小米 VS 华为 - 抽象工厂模式

抽象工厂模式 简介 定义了一个接口用于创建相关或有依赖关系的对象族,而无明确指定具体的类 组成角色 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的...

草帽行者
2016/05/06
1K
4
四则运算法则表 - 简单工厂模式

简单工厂模式 简介 定义一个创建对象的类,由这个类来封装实例化对象的行为 组成角色 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不同,产生具体的工厂产品。 抽...

草帽行者
2016/05/06
98
0

没有更多内容

加载失败,请刷新页面

加载更多

js 操作cookie

var cookie = {// 设置cookie方法set:function(key, val, time){// 获取当前时间var date = new Date();// 将date设置为n天以后的时间var expiresDays = time;//...

小丶二
12分钟前
0
0
限制root远程登录 su和sudo命令

9月21日任务 3.7 su命令 3.8 sudo命令 3.9 限制root远程登录 对于Linux而言,权限的重要性毋庸置疑!对于普通用户而言无法执行那些只有root用户才能有效的命令,导致工作无法有效进行; 系统...

robertt15
14分钟前
0
0
MQTT协议的初浅认识之通讯级别和持久会话

背景 这是我最近了解MQTT协议的最后一部分内容了,MQTT协议里面的QOS和Keep Alive是两个比较重要的内容。QOS的设置,直接影响了订阅客户端与中间件之间的消息交互行为。而Keep Alive直接影响...

亚林瓜子
16分钟前
1
0
calc

width: calc(100% - 30px); 特别注意:减号左右空格,均不能去掉。 width: calc(100% - 30px);

柴高八斗之父
24分钟前
0
0
Spring Cloud Gateway全局过滤器GlobalFilter:返回消息和重定向

Spring Cloud Gateway的全局过滤器GlobalFilter,顾名思义,声明后会对所有的请求生效,可以用来做权限控制,这里简单记录一下拦截到非法请求后如何返回自定义信息和将请求重定向到指定URL。...

夜雨寄北09
26分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部