软件风向标

新闻

栏目

原型体2攻略

2023-05-29 11:46:21



————— 第二天 —————


————————————


如果有一天,小灰被外星人抓住了,外星人应该用小灰做实验,想知道小灰在吃得好、睡得好、玩得开心的场景中和小灰的生活状态有什么区别。


因此,外星人克隆了几个完全相同的小灰:



这样,小灰的原型就留在了现实中,三种复制体提供了三种不同的环境:吃得好、睡得好、玩得开心。小灰的原型不受三种复制体的影响。


过了一段时间,我们来观察一下本体和分离的生活状态:





在Java语言中,Object类实现了Cloneable接口,一个对象可以通过调用Clone()来生成对象,这是原型模式的典型应用。


但需要注意的是,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());}


Log打印结果如下:


调用前x值:10

调用后x值:10

调用前user的值:User{name='大黄, age=20}

调用后user的值:User{name='小灰, age=18}


传递基本类型的方法(updateValue()流程图:


传递引用类型的方法(updateUser()流程图:



这也包括例外,如String类型和大小不超过127的Long类型。虽然它们也是参考类型,但它们不受基本类型的影响。这是因为他们将首先比较常量池维护的值,这涉及到VM的内容。今天我不会讨论太多。


浅拷贝是按位的(bit)复制对象,该对象具有原始对象属性值的精确复制。让我们结合应用场景进行分析,或者刚才的优步。我们添加了存储地址的内部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();}


通过ASM工具查看bytecode,可以看到两者对栈资源的消耗:


// 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.保护性拷贝,防止外部需要修改只读对象。



相关文章

图文推荐

猜你喜欢

  • 原型

  • 攻略

原型[共226款]更多>>

攻略[共110930款]更多>>