漫画:什么是 “原型模式” ?
扫描二维码
随时随地手机看文章
————— 第二天 —————
————————————
假如有一天,小灰被外星人抓走了,外星人要拿小灰做实验,想了解小灰在吃得好、睡得好、玩得开心的场景下,与现实中小灰的生存状态有什么区别。
于是,外星人克隆了几个一模一样的小灰:
就这样,小灰的原型被留在现实中,而三个复制体分别提供了吃得好、睡得好、玩得开心三种不同环境,小灰的原型则不受三个复制体的影响。
过了一段时间,我们来观察一下本体与分身的生存状态:
但需要注意的是,clone()方法并不是Cloneable接口里的,而是Object类里的,Cloneable是一个标识接口,标识这个类的对象是可被拷贝的,如果没有实现Cloneable接口,却调用了clone()方法,就会报错。
// protected native Object clone() throws CloneNotSupportedException;protected Object clone() throws CloneNotSupportedException { if (!(this instanceof Cloneable)) { throw new CloneNotSupportedException( "Class " getClass().getName() " doesn't implement Cloneable"); } return internalClone(); } // Native helper method for cloning. private native Object internalClone(); |
Java中的数据类型,分为基本类型和引用类型。在一个方法里的变量如果是基本类型的话,变量就直接存储在这个方法的栈帧里,例如int、long等;而引用类型则在栈帧里存储这个变量的指针,指向堆中该实体的地址,例如String、Array等。深拷贝和浅拷贝是只针对引用数据类型的。
比如一个方法有一个基本类型参数和一个引用类型参数,在方法体里对参数重新赋值,会影响传入的引用类型参数,而不会影响基本类型参数,因为基本类型参数是值传递,而引用类型参数是引用传递。
先定义一个用户类:
// 这是一个非常简单的用户类 public class User { private String name; private int age; public User(String name, int age) { this.name=name; this.age=age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{name='" name ", age=" age '}'; } } |
private int x=10; public void updateValue(int value){ value = 3 * value; } private User user= new User("大黄",20); public void updateUser(User student){ student.setName("小灰"); student.setAge(18); } public void test(){ System.out.println("调用前x的值:" x); updateValue(x); System.out.println("调用后x的值:" x); System.out.println("调用前user的值:" user.toString()); updateUser(user); System.out.println("调用后user的值:" user.toString()); } |
调用前x的值:10 调用后x的值:10 调用前user的值:User{name='大黄, age=20} 调用后user的值:User{name='小灰, age=18} |
传递基本类型的方法(updateValue())流程图:
传递引用类型的方法(updateUser())流程图:
这其中也包含着例外,比如String类型和大小不超过127的Long类型,虽然也是引用类型,却像基本类型一样不受影响。这是因为它们会先比较常量池维护的值,这涉及VM的内容,今天不做过多讨论。
浅拷贝是在按位(bit)拷贝对象,这个对象有着原始对象属性值的一份精确拷贝。我们结合应用场景分析一下,还是刚才的User类,我们增加一个存放地址的内部类Address,我们需要用户信息可以被其他module查询,但是不允许它们被其他module修改,新增代码如下:
// 这是一个稍微复杂的、支持拷贝的用户类 public class User implements Cloneable { // ……省略上文代码…… private Address address; @NonNull @NotNull @Override public User clone() { try{ return (User)super.clone(); }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public class Address{ // 地市 public String city; // 区县 public String county; // 乡镇街道 public String street; } }
|
// 这是一个更复杂的、支持深拷贝的用户类 public class User implements Cloneable { // ……省略上文代码…… @NonNull @NotNull @Override public User clone() { try{ User newUser = (User)super.clone(); newUser.setName(this.name); newUser.setAddress(this.address.clone()); return newUser; }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public class Address implements Cloneable{ // ……省略上文代码…… @NonNull @NotNull @Override public Address clone() { try{ Address newAddress = (Address)super.clone(); newAddress.city = this.city; newAddress.county = this.county; newAddress.street = this.street; return newAddress; }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } } } |
需要注意的是,上面代码的深拷贝其实并不彻底,因为彻底的深拷贝几乎是不可能实现的,那样不但可能存在引用关系非常复杂的情况,也可能存在引用链的某一级上引用了一个没有实现Cloneable接口的第三方对象的情况。
绝大多数设计模式都是牺牲性能提升开发效率的,原型模式则是为数不多的牺牲开发效率提升性能的设计模式。
private User user= new User("大黄",20); public void testNew(){ User user1 = new User("小灰",18); } public void testClone(){ User user2 = user.clone(); } |
// access flags 0x1 public testNew()V ……省略…… MAXSTACK = 4 MAXLOCALS = 2 // access flags 0x1 public testClone()V ……省略…… MAXSTACK = 1 MAXLOCALS = 2 |
@Override public Object clone() { return new Intent(this); } |
最后我们来总结一下原型模式的核心用途:
1.解决构建复杂对象的资源消耗问题,提升创建对象的效率。
2.保护性拷贝,防止外部对只读对象进行需修改。