[Design Mode] Fun prototype mode: Prototype

prototype mode

The definition of the prototype (Prototype) pattern: using an already created instance as the prototype , by copying the prototype object to create a new object that is the same as or similar to the prototype .

Prototype instances specify the kinds of objects to create, and new objects are created by copying these prototypes.

For example, those who have used VMware to install virtual machines may know that we can install a template machine first, and then create many virtual machines by cloning the template machine. This method of copying greatly improves efficiency.

For another example, in the scenario of mass sending messages, we hope that the title of the things sent by the mass will change with the different sending objects. At this time, we can construct a message object, copy this object in the mass sending, and then customize the title.

The message is used as the prototype object. The specific object comes from copying the original object. To complete the copying of the object, the prototype class must implement the Cloneableinterface. The class diagram is as follows:

Shallow clone

Prototype pattern clones have 浅克隆and 深克隆, which we demonstrate with an example.

Assuming that the Message class has a title property of String type, a state property of int type, and a contact property of Contact reference type, the class diagram:

public class Message implements Cloneable {
    private String title;
    private int state;
    private Contact contact;

    public Message(String title, int state, Contact contact) {
        this.title = title;
        this.state = state;
        this.contact = contact;
    }

    @Override
    public String toString() {
        return "Message{" +
                "title='" + title + '\'' +
                ", state=" + state +
                ", contact=" + contact +
                '}';
    }

    public static void main(String[] args) {
        try {
            Contact contact = new Contact("[email protected]", "666");
            Message msg1 = new Message("张三", 1, contact);
            Message msg2 = (Message) msg1.clone();
            System.out.println("msg1:" + msg1);
            System.out.println("msg2:" + msg2);

            System.out.println("contact1 == contact2 ? " + (msg1.contact == msg2.contact));

            msg1.contact.setPhone("888");
            System.out.println("msg1:" + msg1);
            System.out.println("msg2:" + msg2);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

class Contact {
    private String email;
    private String phone;

    public Contact(String email, String phone) {
        this.email = email;
        this.phone = phone;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "Contact{" +
                "email='" + email + '\'' +
                ", phone='" + phone + '\'' +
                '}';
    }
}

operation result:

msg1:Message{title='张三', state=1, contact=Contact{email='[email protected]', phone='666'}}
msg2:Message{title='张三', state=1, contact=Contact{email='[email protected]', phone='666'}}
contact1 == contact2 ? true
msg1:Message{title='张三', state=1, contact=Contact{email='[email protected]', phone='888'}}
msg2:Message{title='张三', state=1, contact=Contact{email='[email protected]', phone='888'}}

As can be seen from the results, the contact of the object msg1 and the contact of msg2 are equal, that is, the same memory address is executed, and the contact attribute of msg1 is modified, and the contact of msg2 also changes!

This is a shallow clone, creating a new object. The attributes of the new object are exactly the same as the original object. For non-basic type attributes, it still points to the memory address of the object pointed to by the original attribute .

Let’s change it again. After msg2 is cloned, reset its title and see:

Contact contact = new Contact("[email protected]", "666");
Message msg1 = new Message("张三", 1, contact);
Message msg2 = (Message) msg1.clone();
//重新设置msg1的title
msg1.title = "李四";
System.out.println("msg1:" + msg1);
System.out.println("msg2:" + msg2);

System.out.println("contact1 == contact2 ? " + (msg1.contact == msg2.contact));

msg1.contact.setPhone("888");
System.out.println("msg1:" + msg1);
System.out.println("msg2:" + msg2);

result:

msg1:Message{title='李四', state=1, contact=Contact{email='[email protected]', phone='666'}}
msg2:Message{title='张三', state=1, contact=Contact{email='[email protected]', phone='666'}}
contact1 == contact2 ? true
msg1:Message{title='李四', state=1, contact=Contact{email='[email protected]', phone='888'}}
msg2:Message{title='张三', state=1, contact=Contact{email='[email protected]', phone='888'}}

The titles of msg1 and msg2 are different!

疑问来了,String也不是基本类型啊,msg1的title变了,为什么它就和我自定义的引用类型Contact不一样完全拷贝到msg2呢?

对于String类型,Java就希望你把它 认为是基本类型,它是没有clone方法的,处理机制也比较特殊,通过字符串常量池(stringpool)在需要的时候才在内存中创建新的字符串。

当定义完msg1,并根据msg1拷贝出msg2后,两个title都执行String常量池中的张三,而msg1.title="李四"执行后,msg1就指向了常量池中的李四;对于Contact类型,这是一个自定义的引用,在内存中就是那个地址,因此,msg1.contact==msg2.contact,即执行同一个内存地址!

深克隆

很多时候,我们当然不希望浅克隆,比如上面的案例中,每个msg对象的contact都不应该是一样的,这就需要深克隆的存在了。

深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

那么就让Contact类也实现Cloneable接口,同时Message类的clone方法要对引用也克隆一份:

public class Message implements Cloneable {
    private String title;
    private int state;
    private Contact contact;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Message msg = (Message) super.clone();
        //将引用对象也克隆一份
        msg.contact = (Contact) contact.clone();
        return msg;
    }
}
class Contact implements Cloneable {
    //...

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

执行如下代码:

Contact contact = new Contact("[email protected]", "666");
Message msg1 = new Message("张三", 1, contact);
Message msg2 = (Message) msg1.clone();

System.out.println("contact1 == contact2 ? " + (msg1.contact == msg2.contact));

msg1.contact.setPhone("888");
System.out.println("msg1:" + msg1);
System.out.println("msg2:" + msg2);

结果:

contact1 == contact2 ? false
msg1:Message{title='张三', state=1, contact=Contact{email='[email protected]', phone='888'}}
msg2:Message{title='张三', state=1, contact=Contact{email='[email protected]', phone='666'}}

contact1 == contact2为false了,因为它们指向了不同的内存地址了,此时修改msg1的contact属性,msg2随之而改变。

Prototype模式应用场景

原型模式通常适用于以下场景。

  • 对象之间相同或相似,即只是个别的几个属性不同的时候。
  • 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
  • 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
  • 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。

在Spring中,如果一个类被标记为prototype,每一次请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)都会产生一个新的bean实例。

小结

  • Java自带原型模式,Object类提供了clone方法
  • 要实现原型模式必须实现Cloneable接口
  • 重写clone方法,如果之重写clone,而未实现Cloneable接口,调用时会出现异常
  • 该模式用于对一个对象的属性已确定,需产生很多相同对象的时候
  • 注意区分深克隆与浅克隆

点个赞再走吧~

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

Guess you like

Origin juejin.im/post/7123108822951919653