资讯专栏INFORMATION COLUMN

设计模式之原型模式

HelKyle / 613人阅读

摘要:缺点首先要记住原型模式的拷贝时不会执行构造函数的。源码地址原型模式参考慕课网设计模式精讲设计模式之原型模式原型模式示例六原型模式破坏单例模式

0x01.定义与类型

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

特点:不需要知道任何创建的细节,不调用构造函数

类型:创建型

UML

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:

实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。

重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此Prototype类需要将clone方法的作用域修改为public类型。

Java实现

/**
 * 原型模式
 */
public class Prototype implements Cloneable {

    private String name;

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

    public String getName() {
        return name;
    }

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

/**
 * 测试与应用类
 */
public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        Prototype prototype = new Prototype();
        prototype.setName("K.O");
        List names = new ArrayList<>();
        names.add("K.O");
        prototype.setNames(names);
        for (int i = 0; i < 5; i ++) {
            Prototype p = (Prototype) prototype.clone();
            p.setName("sigma");
            p.getNames().add("sigma");
            System.out.println(p.toString());
            System.out.println(p.getName());
            System.out.println(p.getNames().size());

        }
        System.out.println(prototype.toString());
        System.out.println(prototype.getName());
        System.out.println(prototype.getNames().size());
    }
}

测试输出结果

org.ko.prototype.basic.Prototype@1540e19d
sigma
2
org.ko.prototype.basic.Prototype@677327b6
sigma
3
org.ko.prototype.basic.Prototype@14ae5a5
sigma
4
org.ko.prototype.basic.Prototype@7f31245a
sigma
5
org.ko.prototype.basic.Prototype@6d6f6e28
sigma
6
org.ko.prototype.basic.Prototype@135fbaa4
K.O
6

可以看出,输出结果中对象的地址不同(是重新创建的)

修改基本类型时,并不能影响基础类,而引用对象只是指向的基础类的属性。

这里有个问题叫深拷贝,浅拷贝,后续会介绍!

原型模式的各个元素,原型模式比较简单,元素比较少

原型接口:适用原型模式要实现原型接口,重写里面的 clone()方法

原型类:具体产品的实现

0x02.使用场景

类初始化消耗较多资源。

new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)。

构造函数比较复杂。

循环体中生产大量对象时。

0x03.优点

原型模式性能比直接new一个对象性能高,是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量对象时,原型模式可能更好的体现其优点。

还可以简化创建过程。

还有一个重要的用途就是保护性拷贝,也就是对某个对象对外可能是只读的,为了防止外部对这个只读对象的修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。

0x04.缺点

首先要记住原型模式的拷贝时不会执行构造函数的。

clone并不一定比new一个对象快,只有当new对象比较耗时时,才考虑使用原型模式。

必须配备克隆方法。

对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入风险。

深拷贝、浅拷贝要运用得当。

要使用clone方法,类的成员变量上不要增加final关键字,final类型是不允许重赋值的。

0x05.样例实现

使用原型模式实现发送邮件

Java代码实现

/**
 * Mail实现类
 * 实现 Cloneable 接口
 * 重写 Object.clone() 方法
 */
public class Mail implements Cloneable {

    private String name;

    private String emailAddress;

    private String content;

    public Mail () {
        System.out.println("Mail Class Constructor!");
    }

    public String getName() {
        return name;
    }

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

    public String getEmailAddress() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Mail{" +
                "name="" + name + """ +
                ", emailAddress="" + emailAddress + """ +
                ", content="" + content + """ +
                "}" + super.toString();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        System.out.println("clone mail object!");
        return super.clone();
    }
}

/**
 * Mail工具类
 */
public class MailUtil {

    public static void sendMail (Mail mail) {
        String outputContent = "向{0}同学, 邮件地址:{1},邮件内容:{2}, 发送邮件成功!";
        System.out.println(MessageFormat.format(
                outputContent,
                mail.getName(),
                mail.getEmailAddress(), mail.getContent())
        );
    }

    public static void saveOriginMailRecord (Mail mail) {
        System.out.println("存储originMail记录, originMail: " + mail.getContent());
    }
}

测试与应用

/**
 * 测试与应用
 */
public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        Mail mail = new Mail();
        mail.setContent("初始化模板");

        System.out.println("初始化mail: " + mail.toString());
        for (int i = 0; i < 10; i++) {
            Mail mailTemp = (Mail) mail.clone(); //并没有调用Mail构造器
            mailTemp.setName("K.O_" + i);
            mailTemp.setEmailAddress("ko.shen_" + i + "@hotmail.com");
            mailTemp.setContent("恭喜您中奖了。");
            MailUtil.sendMail(mailTemp);
            System.out.println("克隆的mailTemp: " + mailTemp.toString());
        }
        MailUtil.saveOriginMailRecord(mail);
    }
}

测试结果

Mail Class Constructor!
初始化mail: Mail{name="null", emailAddress="null", content="初始化模板"}org.ko.prototype.v2.Mail@1540e19d
clone mail object!
向K.O_0同学, 邮件地址:ko.shen_0@hotmail.com,邮件内容:恭喜您中奖了。, 发送邮件成功!
克隆的mailTemp: Mail{name="K.O_0", emailAddress="ko.shen_0@hotmail.com", content="恭喜您中奖了。"}org.ko.prototype.v2.Mail@677327b6
clone mail object!
向K.O_1同学, 邮件地址:ko.shen_1@hotmail.com,邮件内容:恭喜您中奖了。, 发送邮件成功!
克隆的mailTemp: Mail{name="K.O_1", emailAddress="ko.shen_1@hotmail.com", content="恭喜您中奖了。"}org.ko.prototype.v2.Mail@14ae5a5
clone mail object!
向K.O_2同学, 邮件地址:ko.shen_2@hotmail.com,邮件内容:恭喜您中奖了。, 发送邮件成功!
克隆的mailTemp: Mail{name="K.O_2", emailAddress="ko.shen_2@hotmail.com", content="恭喜您中奖了。"}org.ko.prototype.v2.Mail@7f31245a
clone mail object!
向K.O_3同学, 邮件地址:ko.shen_3@hotmail.com,邮件内容:恭喜您中奖了。, 发送邮件成功!
克隆的mailTemp: Mail{name="K.O_3", emailAddress="ko.shen_3@hotmail.com", content="恭喜您中奖了。"}org.ko.prototype.v2.Mail@6d6f6e28
clone mail object!
向K.O_4同学, 邮件地址:ko.shen_4@hotmail.com,邮件内容:恭喜您中奖了。, 发送邮件成功!
克隆的mailTemp: Mail{name="K.O_4", emailAddress="ko.shen_4@hotmail.com", content="恭喜您中奖了。"}org.ko.prototype.v2.Mail@135fbaa4
clone mail object!
向K.O_5同学, 邮件地址:ko.shen_5@hotmail.com,邮件内容:恭喜您中奖了。, 发送邮件成功!
克隆的mailTemp: Mail{name="K.O_5", emailAddress="ko.shen_5@hotmail.com", content="恭喜您中奖了。"}org.ko.prototype.v2.Mail@45ee12a7
clone mail object!
向K.O_6同学, 邮件地址:ko.shen_6@hotmail.com,邮件内容:恭喜您中奖了。, 发送邮件成功!
克隆的mailTemp: Mail{name="K.O_6", emailAddress="ko.shen_6@hotmail.com", content="恭喜您中奖了。"}org.ko.prototype.v2.Mail@330bedb4
clone mail object!
向K.O_7同学, 邮件地址:ko.shen_7@hotmail.com,邮件内容:恭喜您中奖了。, 发送邮件成功!
克隆的mailTemp: Mail{name="K.O_7", emailAddress="ko.shen_7@hotmail.com", content="恭喜您中奖了。"}org.ko.prototype.v2.Mail@2503dbd3
clone mail object!
向K.O_8同学, 邮件地址:ko.shen_8@hotmail.com,邮件内容:恭喜您中奖了。, 发送邮件成功!
克隆的mailTemp: Mail{name="K.O_8", emailAddress="ko.shen_8@hotmail.com", content="恭喜您中奖了。"}org.ko.prototype.v2.Mail@4b67cf4d
clone mail object!
向K.O_9同学, 邮件地址:ko.shen_9@hotmail.com,邮件内容:恭喜您中奖了。, 发送邮件成功!
克隆的mailTemp: Mail{name="K.O_9", emailAddress="ko.shen_9@hotmail.com", content="恭喜您中奖了。"}org.ko.prototype.v2.Mail@7ea987ac
存储originMail记录, originMail: 初始化模板

从输出信息可以看出来,使用clone方法不需要通过构造函数创建

由于原型模式uml比较简单,和上面基本一致,这里就不再介绍了!

0x06.扩展(深拷贝与浅拷贝) 1. 浅克隆

直接实现原型模式

/**
 * 浅克隆
 */
public class Pig1 implements Cloneable {
    private String name;

    private Date birthday;

    public Pig1(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

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

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

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

    @Override
    public String toString() {
        return "Pig1{" +
                "name="" + name + """ +
                ", birthday=" + birthday +
                "}" + super.toString();
    }
}

测试类

public class Test1 {

    public static void main(String[] args) throws CloneNotSupportedException {
        //浅克隆, 没办法克隆引用对象
        Date birthday = new Date(0l);

        Pig1 pig1 = new Pig1("佩奇", birthday);
        Pig1 pig2 = (Pig1) pig1.clone();

        System.out.println(pig1);
        System.out.println(pig2);
        System.out.println("-------");

        pig1.getBirthday().setTime(666666666666L);

        System.out.println(pig1);
        System.out.println(pig2);
    }
}

输出日志

Pig1{name="佩奇", birthday=Thu Jan 01 08:00:00 CST 1970}org.ko.prototype.clone.Pig1@6d6f6e28
Pig1{name="佩奇", birthday=Thu Jan 01 08:00:00 CST 1970}org.ko.prototype.clone.Pig1@135fbaa4
-------
Pig1{name="佩奇", birthday=Sat Feb 16 09:11:06 CST 1991}org.ko.prototype.clone.Pig1@6d6f6e28
Pig1{name="佩奇", birthday=Sat Feb 16 09:11:06 CST 1991}org.ko.prototype.clone.Pig1@135fbaa4

如上所示,修改pig1中的birthday,pig2中的也响应了变化,所以直接使用clone方法返回的对象中的引用变量并没有重新创建而是直接复用的原有对象中的变量。

由此得出结论:clone方法默认使用的是浅拷贝。

如果想要引用变量也全部复制?

2.深拷贝

其实深克隆只是自己实现了引用变量的创建,请看实现:

/**
 * 深克隆
 */
public class Pig2 implements Cloneable {
    private String name;

    private Date birthday;

    public Pig2(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

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

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Pig2 pig2 = (Pig2) super.clone();

        //深克隆, 要自己去做对象引用的克隆
        pig2.birthday = (Date) pig2.birthday.clone();
        return pig2;
    }

    @Override
    public String toString() {
        return "Pig1{" +
                "name="" + name + """ +
                ", birthday=" + birthday +
                "}" + super.toString();
    }
}

测试类

public class Test2 {

    public static void main(String[] args) throws CloneNotSupportedException {
        //深克隆
        Date birthday = new Date(0l);

        Pig2 p1 = new Pig2("佩奇", birthday);
        Pig2 p2 = (Pig2) p1.clone();

        System.out.println(p1);
        System.out.println(p2);
        System.out.println("-------");

        p1.getBirthday().setTime(666666666666L);

        System.out.println(p1);
        System.out.println(p2);
    }
}

输出日志

Pig2{name="佩奇", birthday=Thu Jan 01 08:00:00 CST 1970}org.ko.prototype.clone.Pig2@6d6f6e28
Pig2{name="佩奇", birthday=Thu Jan 01 08:00:00 CST 1970}org.ko.prototype.clone.Pig2@135fbaa4
-------
Pig2{name="佩奇", birthday=Sat Feb 16 09:11:06 CST 1991}org.ko.prototype.clone.Pig2@6d6f6e28
Pig2{name="佩奇", birthday=Thu Jan 01 08:00:00 CST 1970}org.ko.prototype.clone.Pig2@135fbaa4

可以看出,修改了pig1的时间,pig2并没有跟着响应。所以深拷贝完成。

0x07.原型模式对单例模式的破坏

当单例对象实现了clone方法时,会返回多个实例,请看实现:

/**
 * 简单的饿汉式单例
 */
public class StaticInnerClassSingleton implements Cloneable {

    /**
     * 看静态类的初始化锁那个线程可以拿到
     */
    private static class InnerClass {
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }

    private StaticInnerClassSingleton() {
        if (InnerClass.staticInnerClassSingleton != null) {
            throw new RuntimeException("单例对象禁止反射调用");
        }
    }

    /**
     * 直接重写clone方法
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        //获取单例对象
        StaticInnerClassSingleton singleton = StaticInnerClassSingleton.getInstance();
        System.out.println(singleton.toString());

        //clone获取克隆对象
        StaticInnerClassSingleton singleton1 = (StaticInnerClassSingleton) singleton.clone();
        System.out.println(singleton1.toString());
    }
}

输出日志

org.ko.prototype.singleton.StaticInnerClassSingleton@1540e19d
org.ko.prototype.singleton.StaticInnerClassSingleton@677327b6

根据日志可以看出,单例模式被破坏掉。

重写clone()方法,直接返回INSTANCE对象解决原型模式对单例模式的破坏

/**
 * 简单的饿汉式单例
 */
public class StaticInnerClassSingleton1 implements Cloneable {

    /**
     * 看静态类的初始化锁那个线程可以拿到
     */
    private static class InnerClass {
        private static StaticInnerClassSingleton1 staticInnerClassSingleton = new StaticInnerClassSingleton1();
    }

    public static StaticInnerClassSingleton1 getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }

    private StaticInnerClassSingleton1() {
        if (InnerClass.staticInnerClassSingleton != null) {
            throw new RuntimeException("单例对象禁止反射调用");
        }
    }

    /**
     * 修改克隆方法,返回单例对象
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return InnerClass.staticInnerClassSingleton;
    }
}

/**
 * 修改后的测试类
 */
public class Test1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        //获取单例对象
        StaticInnerClassSingleton1 singleton1 = StaticInnerClassSingleton1.getInstance();
        System.out.println(singleton1.toString());

        //获取clone对象
        StaticInnerClassSingleton1 singleton2 = (StaticInnerClassSingleton1) singleton1.clone();
        System.out.println(singleton2.toString());
    }
}

输出日志

org.ko.prototype.singleton.StaticInnerClassSingleton1@1540e19d
org.ko.prototype.singleton.StaticInnerClassSingleton1@1540e19d

可以看出,返回的对象地址时一致的。这样就解决了原型对单例模式的破坏。

0x08.源码地址

原型模式: https://github.com/sigmako/design-pattern/tree/master/prototype

0x09.参考

慕课网设计模式精讲: https://coding.imooc.com/class/270.html

设计模式之原型模式: https://blog.csdn.net/chenliguan/article/details/69855738

C06 原型模式 示例(六) 原型模式破坏单例模式: https://blog.csdn.net/weixin_33669968/article/details/88889565

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77862.html

相关文章

  • #yyds干货盘点#Java设计模式(四)——原型模式

    摘要:如果一个对象的初始化需要很多其他对象的数据准备或其他资源的繁琐计算,那么可以使用原型模式。当需要一个对象的大量公共信息,少量字段进行个性化设置的时候,也可以使用原型模式拷贝出现有对象的副本进行加工处理。 1、什么是原型模式Specify the kinds of objects to create using a prot...

    番茄西红柿 评论0 收藏2637
  • 《javascript高级程序设计》第六章 读书笔记 javascript对象的几种创建方式

    摘要:三种使用构造函数创建对象的方法和的作用都是在某个特殊对象的作用域中调用函数。这种方式还支持向构造函数传递参数。叫法上把函数叫做构造函数,其他无区别适用情境可以在特殊的情况下用来为对象创建构造函数。 一、工厂模式 工厂模式:使用字面量和object构造函数会有很多重复代码,在此基础上改进showImg(https://segmentfault.com/img/bVbmKxb?w=456&...

    xiaotianyi 评论0 收藏0
  • 优才公开课笔记:php设计模式 (四)原型模式

    摘要:继续分享设计模式的公开课,这是第四篇创建型模式之原型模式设计模式的一般介绍在第一篇文章讲了,不了解的可以先看看。设计模式的第一部分,创建型模式就总结完了。下面还有两部分结构型设计模式和行为型设计模式稍后继续。 继续分享设计模式的公开课,这是第四篇创建型模式之原型模式 设计模式的一般介绍在第一篇文章讲了,不了解的可以先看看。 原型模式: 用原型实例指定创建对象的种类,并且通过拷贝这个...

    Maxiye 评论0 收藏0
  • 面向对象的程序设计原型模式

    摘要:可以用删除实例对象中自己添加的属性可以确定属性是原型中还是实例对象中,当时实例对象中时,返回的是操作符,有两种使用方式,单独使用和循环中。单独使用,通过对象能够访问属性时返回,无论时在原型中还是实例对象中。 原型模式,每个创建的对象都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。 ------------...

    yunhao 评论0 收藏0
  • JavaScript深入创建对象的多种方式以及优缺点

    摘要:深入系列第十四篇,讲解创建对象的各种方式,以及优缺点。也就是说打着构造函数的幌子挂羊头卖狗肉,你看创建的实例使用都无法指向构造函数这样方法可以在特殊情况下使用。 JavaScript深入系列第十四篇,讲解创建对象的各种方式,以及优缺点。 写在前面 这篇文章讲解创建对象的各种方式,以及优缺点。 但是注意: 这篇文章更像是笔记,因为《JavaScript高级程序设计》写得真是太好了! 1....

    Terry_Tai 评论0 收藏0

发表评论

0条评论

HelKyle

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<