java设计模式——原型模式

一. 定义与类型

定义:指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。不需要知道任何创建的细节,不调用构造函数

类型:创建型

二.使用场景

类初始化消耗较多资源

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

构造函数比较复杂

循环体中生产大量对象时

三.优缺点

优点:

  原型模式性能比直接new一个对象性能高,简化创建过程

缺点:

  必须配备克隆方法,

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

  深拷贝,浅拷贝要运用得当

四. 扩展

深克隆:对于引用类型,如果需要指向不同的对象,而对于某个对象的引用类型的时候,必须要显式的去写对那个属性进行深克隆

浅克隆:

五. Coding

/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:11
 **/
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();
    }
}
/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:17
 **/
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());
    }
}
/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:20
 **/
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Mail mail = new Mail();
        mail.setContent("初始化模板");
        System.out.println("初始化mail:" + mail);
        for (int i = 0; i < 10; i++) {
            //克隆的时候,并不会使用原对象的构造器
            Mail mailTemp = (Mail) mail.clone();
            mailTemp.setName("姓名" + i);
            mailTemp.setEmailAddress("姓名" + i + "@qq.com");
            mailTemp.setContent("恭喜您,中奖了");
            MailUtil.sendMail(mailTemp);
        }
        MailUtil.saveOriginMailRecord(mail);
    }
}

从上面的代码不难看出,其实原型模式就是实现了cloneable接口,在创建一个不同的对象,来完成邮件的发送,而保留了原来的邮件模板。由于clone不会,调用原对象的构造器,所以在效率上比直接new 对象要高。但是,因为在Mail类中的属性都是简单类型,所以在clone的时候,基本上不会出现上面问题。但是看下面一个实体:

/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:33
 **/
public class Pig implements Cloneable {
    private String name;
    private Date birthDay;

    public Pig(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
    public String toString() {
        return "Pig{" +
                "name='" + name + '\'' +
                ", birthDay=" + birthDay +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:34
 **/
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Date birthDay = new Date(0L);
        Pig pig = new Pig("佩奇", birthDay);
        Pig pig1 = (Pig) pig.clone();
        System.out.println(pig);
        System.out.println(pig1);

        pig.getBirthDay().setTime(6666666666666L);
        /**
         * 通过上面对birthDay进行操作,如果是浅拷贝,那么pig与pig1的date对象都会改变:
         *  因为在浅拷贝的时候,两个对象中的引用对象date,都是引用同一个对象,所以改变了一个,那么两个都会改变
         *
         * 如果是深拷贝,那么pig与pig1引用的date对象就是不一样的,改变其中一个,对另一个并没有影响。
         *
         * 由于深克隆,浅克隆的关系,也算是原型模式的一个坑。(原则是,都会使用深克隆,不然就算是给项目埋坑)
         *
         */
        System.out.println(pig);
        System.out.println(pig1);
    }
}

输出结果为:

上面的注释和结果,其实也清楚的看到了。我只改变了pig对象中的birthDay,但是pig1中也改变了。如果clone的对象中存在引用类型的对象,那么如果是浅拷贝,拷贝与被拷贝出的对象的引用对象都是指向同一地址的,所以改变其中一个,另一个也会改变。这时候,如果根据需求就必须使用深拷贝。

也就是对于对象中的引用对象也要进行clone。看下面代码:

/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:33
 **/
public class Pig implements Cloneable {
    private String name;
    private Date birthDay;

    public Pig(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
    public String toString() {
        return "Pig{" +
                "name='" + name + '\'' +
                ", birthDay=" + birthDay +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Pig pig = (Pig) super.clone();

        //深克隆
        pig.birthDay = (Date) pig.birthDay.clone();
        return pig;
    }
}

输出结果:

可以看到,Pig中重写了clone方法,对Date对象也进行了clone,从而使得,引用对象指向不同的地址。所以改变pig中的birthDay,对于pig1并没有影响。

由于clone方法,可以用原型模式拷贝来破坏单例模式:

/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:34
 **/
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        /**
         * 使用原型模式,克隆破坏单例模式
         *
         * 这种情况的解决方式:
         * 要么单例模式类不去实现Cloneable接口,要么就重写clone方法,直接返回getInstance()方法,这个对象的实例
         */
        HungrySingleton hungrySingleton = HungrySingleton.getInstance();
        Method method = hungrySingleton.getClass().getDeclaredMethod("clone");
        method.setAccessible(true);
        HungrySingleton cloneHungrySingleton = (HungrySingleton) method.invoke(hungrySingleton);
        System.out.println(hungrySingleton);
        System.out.println(cloneHungrySingleton);
    }
}

六. 源码分析

基本上只要知道了哪些类使用了Cloneable就知道,原型模式如何使用。

比如ArrayList,HashMap类都重写了clone方法

Mybatis中的CacheKey类也重写了clone()方法

posted @ 2018-12-13 17:34  MichaelKai  阅读(248)  评论(0编辑  收藏  举报