原型模式

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

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

类型
创建型

适用场景

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

优点

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

缺点

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

扩展

  • 深克隆
  • 浅克隆

代码

业务场景

商家发放优惠活动邮件给用户,默认邮件创建非常繁琐

// 邮件类
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) {
        Mail mail = new Mail();
        //要求1: 需要打印的信息
        mail.setContent("初始化模板");
        for (int i = 0; i < 10; i++) {
            mail.setName("姓名" + i);
            mail.setEmailAddress("姓名" + i + "@test.com");
            mail.setContent("恭喜您,本次活动中奖");
            MailUtil.sendMail(mail);
        }
        //要求2: 必须要在邮件发送完毕后打印
        MailUtil.saveOriginMailRecord(mail);

    }
}

结果

创建Mail
向姓名0用户,邮件地址:姓名0@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
向姓名1用户,邮件地址:姓名1@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
向姓名2用户,邮件地址:姓名2@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
向姓名3用户,邮件地址:姓名3@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
向姓名4用户,邮件地址:姓名4@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
向姓名5用户,邮件地址:姓名5@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
向姓名6用户,邮件地址:姓名6@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
向姓名7用户,邮件地址:姓名7@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
向姓名8用户,邮件地址:姓名8@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
向姓名9用户,邮件地址:姓名9@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
存储originMail记录,originMail恭喜您,本次活动中奖

并没有打印出我们想要的信息,而是打印出了最终的信息
如果我们默认Mail创建很复杂,且会消耗大量性能,循环过程中会出现大量的类。而原型模式是在内存中进行二进制流的拷贝,肯定比直接new一个对象性能好很多

原型模式修改
修改Mail类

public class Mail implements Cloneable{

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

    public Mail() {
        System.out.println("创建Mail");
    }
    // get set ...
    @Override
    protected Mail clone() throws CloneNotSupportedException {
        System.out.println("clone mail object");
        return (Mail) super.clone();
    }
}

修改测试

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        Mail mail = new Mail();
        mail.setContent("初始化模板");
        for (int i = 0; i < 10; i++) {
            // 修改为克隆模式
            Mail mailTemp =  mail.clone();
            mailTemp.setName("姓名" + i);
            mailTemp.setEmailAddress("姓名" + i + "@test.com");
            mailTemp.setContent("恭喜您,本次活动中奖");
            MailUtil.sendMail(mailTemp);
        }
        MailUtil.saveOriginMailRecord(mail);
    }
}

结果

创建Mail
clone mail object
向姓名0用户,邮件地址:姓名0@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
clone mail object
向姓名1用户,邮件地址:姓名1@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
clone mail object
向姓名2用户,邮件地址:姓名2@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
clone mail object
向姓名3用户,邮件地址:姓名3@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
clone mail object
向姓名4用户,邮件地址:姓名4@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
clone mail object
向姓名5用户,邮件地址:姓名5@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
clone mail object
向姓名6用户,邮件地址:姓名6@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
clone mail object
向姓名7用户,邮件地址:姓名7@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
clone mail object
向姓名8用户,邮件地址:姓名8@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
clone mail object
向姓名9用户,邮件地址:姓名9@test.com,邮件内容:恭喜您,本次活动中奖,邮件发送成功
存储originMail记录,originMail初始化模板

关于深克隆和浅克隆

代码

public class Pig implements Cloneable {

    private String name;
    private Date birthday;

    public Pig(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }
    // get set
    @Override
    public String toString() {
        return "Pig{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}' + super.toString();
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
 }

测试类

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        Date birthday = new Date(0L);
        Pig pig1 = new Pig("佩奇1", birthday);
        Pig pig2 = (Pig) pig1.clone();
        System.out.println(pig1);
        System.out.println(pig2);
    }
}

结果

Pig{name='佩奇1', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@2503dbd3
Pig{name='佩奇1', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@4b67cf4d

打印出了相同的结果,但是hashcode值是不一样的。接着我们修改测试类

  public static void main(String[] args) throws CloneNotSupportedException {
        Date birthday = new Date(0L);
        Pig pig1 = new Pig("佩奇1", birthday);
        Pig pig2 = (Pig) pig1.clone();
        System.out.println(pig1);
        System.out.println(pig2);
        pig1.getBirthday().setTime(66666666L);
        System.out.println(pig1);
        System.out.println(pig2);
    }

将pig1的生日修改,然后再次打印两个对象,我们想要的结果应该是只有pig1的生日被修改,pig2不变

Pig{name='佩奇1', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@2503dbd3
Pig{name='佩奇1', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@4b67cf4d
Pig{name='佩奇1', birthday=Fri Jan 02 02:31:06 CST 1970}com.design.pattern.creational.prototype.clone.Pig@2503dbd3
Pig{name='佩奇1', birthday=Fri Jan 02 02:31:06 CST 1970}com.design.pattern.creational.prototype.clone.Pig@4b67cf4d

pig1的修改生效了,但是pig2也被修改了。代码中。已经生成了两个不同的对象,但是同样被影响,这样就跟预期不同了。
接下来使用debug

debug发现,对象是不同的对象,605和606,但是时间是同一个时间604,所以修改一个对象的时候,拷贝出来的对象也会被修改。这就是默认的实现方式 浅克隆
如果需要深克隆,则需要修改Pig类

    protected Object clone() throws CloneNotSupportedException {
        Pig pig = (Pig) super.clone();
        // 深克隆
        pig.birthday  = (Date) pig.birthday.clone();
        return pig;
    }

再次debug

debug发现,对象是不同的对象,605和606,时间也变成了不同的对象604,613
结果

Pig{name='佩奇1', birthday=Thu Jan 01 08:00:00 CST 1970}com.honor.design.pattern.creational.prototype.clone.Pig@6979e8cb
Pig{name='佩奇1', birthday=Thu Jan 01 08:00:00 CST 1970}com.honor.design.pattern.creational.prototype.clone.Pig@763d9750
Pig{name='佩奇1', birthday=Fri Jan 02 02:31:06 CST 1970}com.honor.design.pattern.creational.prototype.clone.Pig@6979e8cb
Pig{name='佩奇1', birthday=Thu Jan 01 08:00:00 CST 1970}com.honor.design.pattern.creational.prototype.clone.Pig@763d9750

结果也变得符合预期了。

克隆破坏单例

单例类

public class HungrySingleton implements Serializable ,Cloneable{

    private final static HungrySingleton hungrySingleton;

    static {
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton() {
        if (hungrySingleton != null) {
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

    private Object readResolve() {
        return hungrySingleton;
    }

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

测试

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        HungrySingleton hungrySingleton = HungrySingleton.getInstance();
        Method method = hungrySingleton.getClass().getDeclaredMethod("clone");
        method.setAccessible(true);
        HungrySingleton clone = (HungrySingleton) method.invoke(hungrySingleton);
        System.out.println(hungrySingleton);
        System.out.println(clone);

    }
}

结果

com.honor.design.pattern.creational.singleton.HungrySingleton@135fbaa4
com.honor.design.pattern.creational.singleton.HungrySingleton@45ee12a7

可以看出结果是两个不同的对象,单例模式被破坏。

  • 解决办法1:单例模式约定不实现Cloneable接口
  • 解决方法2:clone方法返回getInstance()
   @Override
    protected Object clone() throws CloneNotSupportedException {
        return getInstance();
    }

结果

com.honor.design.pattern.creational.singleton.HungrySingleton@135fbaa4
com.honor.design.pattern.creational.singleton.HungrySingleton@45ee12a7

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注