专栏首页程序员小灰漫画:什么是 “原型模式” ?

漫画:什么是 “原型模式” ?

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

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

假如有一天,小灰被外星人抓走了,外星人要拿小灰做实验,想了解小灰在吃得好、睡得好、玩得开心的场景下,与现实中小灰的生存状态有什么区别。

于是,外星人克隆了几个一模一样的小灰:

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

过了一段时间,我们来观察一下本体与分身的生存状态:

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

但需要注意的是,clone()方法并不是Cloneable接口里的,而是Object类里的,Cloneable是一个标识接口,标识这个类的对象是可被拷贝的,如果没有实现Cloneable接口,却调用了clone()方法,就会报错。

// protected native Object clone() throws CloneNotSupportedException;protected Object clone() throwsCloneNotSupportedException { 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)拷贝对象,这个对象有着原始对象属性值的一份精确拷贝。我们结合应用场景分析一下,还是刚才的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();}

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

本文分享自微信公众号 - 程序员小灰(chengxuyuanxiaohui),作者:东风玖哥,小灰

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-07-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 漫画:什么是 “代理模式” ?

    在上面的代码中,代理类和业务类继承了相同的接口,并且重写了添加/删除学生的方法。

    小灰
  • 漫画:什么是 “设计模式” ?

    这本书是软件研发领域重要的里程碑,合著此书的四位作者 Erich Gamma,Richard Helm,Ralph Johnson ,John Vlisside...

    小灰
  • 漫画:什么是 “建造者模式” ?

    建造者模式与简单工程模式的区别,在于建造者模式多出一个Builder类,使得创建对象的灵活性大大增加,适用于如下场景:

    小灰
  • 漫画:什么是 “模因” ?

    他是一位武功高手,生平最得意的武功是降龙十八掌,可是他的儿女资质不佳,没有人能领悟这套武功,因此他的生平绝学眼看就要失传了。

    小灰
  • 漫画:什么是 “抽象工厂模式” ?

    所谓“工厂模式”,是三种常见设计模式的统称,它们分别是简单工厂模式、工厂方法模式、抽象工厂模式。

    小灰
  • 漫画:什么是 “抽象工厂模式” ?

    所谓“工厂模式”,是三种常见设计模式的统称,它们分别是简单工厂模式、工厂方法模式、抽象工厂模式。

    程序员的时光001
  • 漫画设计模式:什么是 “装饰器模式” ?

    在我们上面的例子中,Component接口相当于汽车接口,所有的被包装类、包装类,都继承于这个接口。

    肉眼品世界
  • 漫画设计模式:什么是 “装饰器模式” ?

    在我们上面的例子中,Component接口相当于汽车接口,所有的被包装类、包装类,都继承于这个接口。

    小灰
  • 漫画设计模式:什么是 “职责链模式” ?

    有一天,公司新来的产品经理有一个新需求,但她不知道这个需求应该由谁来负责。于是,她首先找到了小A:

    小灰
  • 漫画:什么是单例模式?(整合版)

    ————— 第二天 ————— 单例模式第一版: public class Singleton { private Singleton() {} ...

    程序猿DD
  • 漫画:什么是分布式锁?

    利用Memcached的add命令。此命令是原子性操作,只有在key不存在的情况下,才能add成功,也就意味着线程得到了锁。

    后端技术探索
  • 漫画:什么是 “图”?

    举个栗子,大家一定都用过微信,假设你的微信朋友圈中有若干好友:张三、李四、王五、赵六、七大姑、八大姨。

    小灰
  • 漫画:什么是中台?

    在传统IT企业,项目的物理结构是什么样的呢?无论项目内部的如何复杂,都可分为“前台”和“后台”这两部分。

    Java3y
  • 漫画:什么是 “跳表” ?

    如果数组的长度是n,二分查找的时间复杂度是O(logn),比起从左到右逐个遍历元素进行查找的方式,大大提升了查找性能。

    小灰
  • 漫画:什么是中台?

    在传统IT企业,项目的物理结构是什么样的呢?无论项目内部的如何复杂,都可分为“前台”和“后台”这两部分。

    数据森麟
  • 漫画:什么是中台?

    在传统IT企业,项目的物理结构是什么样的呢?无论项目内部的如何复杂,都可分为“前台”和“后台”这两部分。

    程序员小强
  • 漫画:什么是中台?

    在传统IT企业,项目的物理结构是什么样的呢?无论项目内部的如何复杂,都可分为“前台”和“后台”这两部分。

    ConardLi
  • 漫画:什么是中台?

    在传统IT企业,项目的物理结构是什么样的呢?无论项目内部的如何复杂,都可分为“前台”和“后台”这两部分。

    Python数据科学
  • 漫画:什么是中台?

    在传统IT企业,项目的物理结构是什么样的呢?无论项目内部的如何复杂,都可分为“前台”和“后台”这两部分。

    互扯程序

扫码关注云+社区

领取腾讯云代金券