设计模式(13):原型模式

一、概念

1、定义:原型实例指定创建对象的种类,可以通过拷贝这些原型创建新的对象。(不需要知道任何创建的细节,且不调用构造函数)
2、类型:创建型
3、适用场景

  • 类初始化消耗较多资源时
  • new一个对象的过程繁琐(需要数据准备、访问权限)
  • 或构造函数复杂
  • 循环体产生大量对象时

4、优缺点

  • 优点:性能比直接new一个对象性能高,简化了创建过程

  • 缺点:

    • 必须配备克隆方法;
    • 克隆复杂对象,或是对克隆出的对象进行复杂改造时容易引入风险;
    • 深拷贝、浅拷贝要使用得当

5、扩展:

  • 深克隆:对于引用类型,如果需要它们指向不同的对象,那么要使用深克隆,深克隆对于某一个类象的引用类型的时候,要显式的写对哪个属性去深克隆

  • 浅克隆:默认实现

    创建对象是一定要注意是否和预期一致,也就是这个对象是新创建出来的,还是只是创建一个引用,指向的是同一个地址。

6、UML图
UML

二、Coding

业务场景:网站有活动,如积分抽奖等,会发送邮件通知用户,假设邮件对象创建繁琐。

1、设计不好的情况如下:

//较差的设计
//邮件类
public class Mail {

    private String name;
    private String emailAddress;
    private String content;

    //后面看克隆时候有没有调用构造器
    public Mail() {
        System.out.println("Mail的构造器");
    }

    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 + '\'' +
                '}';
    }
}

//工具类
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) {
        //假设new一个mail很消耗性能
        Mail mail = new Mail();
        mail.setContent("初始化模版");
        for (int i = 0; i < 10; i++) {
            //由于记录只能放到最后,只能在这new,每次都要new一个,消耗资源太大
          	mail = new Mail();
            mail.setName("姓名" + i);
            mail.setEmailAddress("姓名" + i + "@qq.com");
            mail.setContent("恭喜您,此次活动中奖了");
            MailUtil.sendMail(mail);
        }
        //假设实际业务很复杂,存储一定要放在最后,还要记录发送结果等,都要记录到原始的mail对象中,原始的!
        MailUtil.saveOriginMailRecord(mail);

    }
}

2、使用克隆

原型模式是在内存中进行二进制流的拷贝,肯定要比直接new一个对象性能好。

记住克隆时候式没有调用构造器的,且每次克隆后地址也不一样

//MailUtil不变

//实现Cloneable接口并重写clone方法
public class Mail implements Cloneable{

    private String name;
    private String emailAddress;
    private String content;

    public Mail() {
        System.out.println("Mail的构造器");
    }

    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;
    }

    //重写克隆方法,调用父类的clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        System.out.println("clone mail object");
        return super.clone();
    }

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

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        //假设new一个mail很消耗性能
        Mail mail = new Mail();
        mail.setContent("初始化模版");
        for (int i = 0; i < 10; i++) {
            //用原始的mail去克隆,让克隆出的tempMail去set,最后保存还是保存原始的mail
            Mail tempMail = (Mail) mail.clone();
            tempMail.setName("姓名" + i);
            tempMail.setEmailAddress("姓名" + i + "@qq.com");
            tempMail.setContent("恭喜您,此次活动中奖了");
            MailUtil.sendMail(tempMail);
        }
        //假设实际业务很复杂,存储一定要放在最后
        MailUtil.saveOriginMailRecord(mail);

    }
}
//只调用了一次Mail构造器
Mail的构造器
clone mail object
向姓名0同学,邮件地址:姓名0@qq.com,邮件内容:(2)发送邮件成功
clone mail object
向姓名1同学,邮件地址:姓名1@qq.com,邮件内容:(2)发送邮件成功
clone mail object
向姓名2同学,邮件地址:姓名2@qq.com,邮件内容:(2)发送邮件成功
clone mail object
向姓名3同学,邮件地址:姓名3@qq.com,邮件内容:(2)发送邮件成功
clone mail object
向姓名4同学,邮件地址:姓名4@qq.com,邮件内容:(2)发送邮件成功
clone mail object
向姓名5同学,邮件地址:姓名5@qq.com,邮件内容:(2)发送邮件成功
clone mail object
向姓名6同学,邮件地址:姓名6@qq.com,邮件内容:(2)发送邮件成功
clone mail object
向姓名7同学,邮件地址:姓名7@qq.com,邮件内容:(2)发送邮件成功
clone mail object
向姓名8同学,邮件地址:姓名8@qq.com,邮件内容:(2)发送邮件成功
clone mail object
向姓名9同学,邮件地址:姓名9@qq.com,邮件内容:(2)发送邮件成功
存储originMail记录,originMail:初始化模版

3、深克隆和浅克隆

3.1浅克隆

public class Pig implements Cloneable{
    private String name;
    private Date birthday;

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

    public Pig(String name) {
        this.name = name;
    }

    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 "Pig{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}' + super.toString();
    }
}

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        Date birthday = new Date(0L);
        Pig pig1 = new Pig("佩奇", birthday);
        Pig pig2 = (Pig) pig1.clone();
        //发现是两个不同的对象,是因为没有重写hashCode
        System.out.println(pig1);   //Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}Pig@5e2de80c
        System.out.println(pig2);   //Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}Pig@1d44bcfa

        //重新设置pig1的生日后,我们发现pig2的时间也变了,他们引用的都是同一个Date对象,默认是浅克隆
        pig1.getBirthday().setTime(6666666666666L);
        System.out.println(pig1);   //Pig{name='佩奇', birthday=Wed Apr 04 19:51:06 CST 2181}Pig@5e2de80c
        System.out.println(pig2);   //Pig{name='佩奇', birthday=Wed Apr 04 19:51:06 CST 2181}Pig@1d44bcfa
    }
}

3.2深克隆

上边例子是因为生日对象的引用类型没有重写clone方法出现的,使得只是向日期对象创建了一个引用(pig1和pig2中的生日属性都指向同一个日期对象),我们只要在克隆方法中改变一下就行。

 //浅拷贝
 @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
//深拷贝
@Override
    protected Object clone() throws CloneNotSupportedException {
        Pig pig = (Pig) super.clone();

        //深克隆,单独为生日实现克隆方法,这样只会改变pig1的生日而不会改变克隆对象的生日
        pig.birthday = (Date) pig.birthday.clone();
        return pig;
    }

4、克隆破坏单例模式

//实现了Cloneable接口,重写了clone方法,直接看最下面
public class HungrySingleton implements Serializable,Cloneable {

    private static HungrySingleton hungrySingleton;
    static{
        hungrySingleton = new HungrySingleton();
    }

    //避免反射攻击
    public HungrySingleton() {
        if (hungrySingleton != null) {
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

    //解决序列化和反序列化破坏单例模式
    private Object readResolve() {
        return hungrySingleton;
    }

    //克隆破坏单例,这边权限是protected
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Test {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        HungrySingleton hungrySingleton = HungrySingleton.getInstance();
        //反射,获取方法名称
        Method method = hungrySingleton.getClass().getDeclaredMethod("clone");
        method.setAccessible(true);     //把protect权限打开
        HungrySingleton cloneHungrySingleton = (HungrySingleton) method.invoke(hungrySingleton);
        System.out.println(hungrySingleton);        //HungrySingleton@610455d6
        System.out.println(cloneHungrySingleton);   //HungrySingleton@511d50c0
    }
}

我们发现得到的对象不一样,克隆破坏了单例模式!怎么解决呢?

  • 要么单例类不去实现Cloneable接口
  • 如果实现了,必须在clone方法中改写成return getInstance();
    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

    //克隆破坏单例,这边权限是protected
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 这么写会破坏单例模式
        // return super.clone();
        
        //解决方法
        return getInstance();
    }

三、源码解析

ArrayList中

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

通过Arrays.copyOf()方法把元素复制一份

同理,HashMap中

@Override
public Object clone() {
    HashMap<K,V> result;
    try {
        result = (HashMap<K,V>)super.clone();
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
    result.reinitialize();
    result.putMapEntries(this, false);
    return result;
}
发布了43 篇原创文章 · 获赞 6 · 访问量 3907

猜你喜欢

转载自blog.csdn.net/weixin_44424668/article/details/103269642