第 7 章 原型模式

1、克隆羊问题

克隆羊问题描述

现在有一只羊tom, 姓名为: tom,年龄为: 1, 颜色为:白色,请编写程序创建和tom羊属性完全相同的10只羊

传统模式解决克隆羊问题

类图


代码实现

  1. Sheep:羊的实体类

    public class Sheep {private String name;private int age;private String color;
    
  2. Client:客户端,发出克隆羊的指令

    public class Client {public static void main(String[] args) {// 传统的方法Sheep sheep = new Sheep("tom", 1, "白色");Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());Sheep sheep5 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());// ....System.out.println(sheep);System.out.println(sheep2);System.out.println(sheep3);System.out.println(sheep4);System.out.println(sheep5);// ...}}
    

传统的方式的优缺点

  1. 优点是比较好理解,简单易操作
  2. 在创建新的对象时, 总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
  3. 总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活

改进思路

JavaObject类是所有类的根类, Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现cloneJava类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力 --> 原型模式

2、原型模式的介绍

  1. 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型, 创建新的对象
  2. 原型模式是一种创建型设计模式,允许一个对象在创建另外一个可定制的对象,无需知道如何创建的细节
  3. 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
  4. 形象的理解:孙大圣拔出猴毛, 变出其它孙大圣

3、原型模式的原理

原型模式原理结构图(uml类图)


原理结构图说明

  1. Prototype:原型类,在该类中声明一个克隆自己的接口
  2. ConcretePrototype:具体的原型类,实现一个克隆自己的操作
  3. Client:让一个原型对象克隆自己,从而创建一个新的对象(属性一样)

4、原型模式代码示例

原型模式解决克隆羊问题的应用实例:使用原型模式改进传统方式,让程序具有更高的效率和扩展性


代码实现

  1. Sheep:羊的实体类

    public class Sheep implements Cloneable {private String name;private int age;private String color;// 克隆该实例,使用默认的clone方法来完成@Overrideprotected Object clone() {Sheep sheep = null;try {sheep = (Sheep) super.clone();} catch (Exception e) {System.out.println(e.getMessage());}return sheep;}// ...
    
  2. Client:利用原型模式创建对象

    public class Client {public static void main(String[] args) {System.out.println("原型模式完成对象的创建");Sheep sheep = new Sheep("tom", 1, "白色");Sheep sheep2 = (Sheep) sheep.clone(); // 克隆Sheep sheep3 = (Sheep) sheep.clone(); // 克隆Sheep sheep4 = (Sheep) sheep.clone(); // 克隆Sheep sheep5 = (Sheep) sheep.clone(); // 克隆System.out.println("sheep2 =" + sheep2 + "sheep2.hashCoe=" + sheep2.hashCode());System.out.println("sheep3 =" + sheep3 + "sheep3.hashCoe=" + sheep3.hashCode());System.out.println("sheep4 =" + sheep4 + "sheep4.hashCoe=" + sheep4.hashCode());System.out.println("sheep5 =" + sheep5 + "sheep5.hashCoe=" + sheep5.hashCode());}}
    
  3. 程序运行结果

    原型模式完成对象的创建
    sheep2 =Sheep [name=tom, age=1, color=白色];sheep2.hashCoe=366712642
    sheep3 =Sheep [name=tom, age=1, color=白色];sheep3.hashCoe=1829164700
    sheep4 =Sheep [name=tom, age=1, color=白色];sheep4.hashCoe=2018699554
    sheep5 =Sheep [name=tom, age=1, color=白色];sheep5.hashCoe=1311053135
    

5、Spring 原型模式

准备工作

  1. 创建实体类 Monster

    public class Monster {private Integer id = 10;private String nickname = "牛魔王";private String skill = "芭蕉扇";public Monster() {System.out.println("monster 创建..");}
    
  2. 通过 XML 配置文件 配置 Bean

    <!-- 这里我们的 scope="prototype" 即 原型模式来创建 -->
    <bean id="id01" class="com.atguigu.spring.bean.Monster" scope="prototype" />
    
  3. 测试代码

    public class ProtoType {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");// 获取monster[通过id获取monster]Object bean = applicationContext.getBean("id01");System.out.println("bean" + bean); // 输出 "牛魔王" .....Object bean2 = applicationContext.getBean("id01");System.out.println("bean2" + bean2); // 输出 "牛魔王" .....System.out.println(bean == bean2); // false// ConfigurableApplicationContext}}
    
  4. 程序运行结果

    monster 创建..
    beanMonster [id=10, nickname=牛魔王, skill=芭蕉扇]
    monster 创建..
    bean2Monster [id=10, nickname=牛魔王, skill=芭蕉扇]
    false
    

Spring 源码追踪

  1. applicationContext.getBean("id01"); 方法中先调用 getBeanFactory() 获取 Bean 工厂,再调用 Bean 工厂的 getBean(name) 方法获取 Bean 实例

    @Override
    public Object getBean(String name) throws BeansException {assertBeanFactoryActive();return getBeanFactory().getBean(name);
    }
    
  2. getBean(name) 方法调用 doGetBean(name, null, null, false); 方法获取 Bean 实例

    @Override
    public Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);
    }
    
  3. 获取 Bean 实例时

    1. 判断 Bean 是否为单例对象:if (mbd.isSingleton()),若是,则调用 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); 获取单例对象
    2. 判断 Bean 是否为原型对象:else if (mbd.isPrototype()),若是,则调用 bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); 获取原型对象
    /*** Return an instance, which may be shared or independent, of the specified bean.* @param name the name of the bean to retrieve* @param requiredType the required type of the bean to retrieve* @param args arguments to use if creating a prototype using explicit arguments to a* static factory method. It is invalid to use a non-null args value in any other case.* @param typeCheckOnly whether the instance is obtained for a type check,* not for actual use* @return an instance of the bean* @throws BeansException if the bean could not be created*/
    @SuppressWarnings("unchecked")
    protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)throws BeansException {final String beanName = transformedBeanName(name);Object bean;// Eagerly check singleton cache for manually registered singletons.Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {if (logger.isDebugEnabled()) {if (isSingletonCurrentlyInCreation(beanName)) {logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference");}else {logger.debug("Returning cached instance of singleton bean '" + beanName + "'");}}bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);}else {// Fail if we're already creating this bean instance:// We're assumably within a circular reference.if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}// Check if bean definition exists in this factory.BeanFactory parentBeanFactory = getParentBeanFactory();if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {// Not found -> check parent.String nameToLookup = originalBeanName(name);if (args != null) {// Delegation to parent with explicit args.return (T) parentBeanFactory.getBean(nameToLookup, args);}else {// No args -> delegate to standard getBean method.return parentBeanFactory.getBean(nameToLookup, requiredType);}}if (!typeCheckOnly) {markBeanAsCreated(beanName);}try {final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);checkMergedBeanDefinition(mbd, beanName, args);// Guarantee initialization of beans that the current bean depends on.String[] dependsOn = mbd.getDependsOn();if (dependsOn != null) {for (String dependsOnBean : dependsOn) {if (isDependent(beanName, dependsOnBean)) {throw new BeanCreationException("Circular depends-on relationship between '" +beanName + "' and '" + dependsOnBean + "'");}registerDependentBean(dependsOnBean, beanName);getBean(dependsOnBean);}}// Create bean instance.if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {@Overridepublic Object getObject() throws BeansException {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}else if (mbd.isPrototype()) {// It's a prototype -> create a new instance.Object prototypeInstance = null;try {beforePrototypeCreation(beanName);prototypeInstance = createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);}else {String scopeName = mbd.getScope();final Scope scope = this.scopes.get(scopeName);if (scope == null) {throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");}try {Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {@Overridepublic Object getObject() throws BeansException {beforePrototypeCreation(beanName);try {return createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}}});bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);}catch (IllegalStateException ex) {throw new BeanCreationException(beanName,"Scope '" + scopeName + "' is not active for the current thread; " +"consider defining a scoped proxy for this bean if you intend to refer to it from a singleton",ex);}}}catch (BeansException ex) {cleanupAfterBeanCreationFailure(beanName);throw ex;}}// Check if required type matches the type of the actual bean instance.if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {try {return getTypeConverter().convertIfNecessary(bean, requiredType);}catch (TypeMismatchException ex) {if (logger.isDebugEnabled()) {logger.debug("Failed to convert bean '" + name + "' to required type [" +ClassUtils.getQualifiedName(requiredType) + "]", ex);}throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());}}return (T) bean;
    }
    
  4. final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); 其中的 mbd 对象有一个名为 scope 属性,用于指示 Bean 的生命周期

6、深拷贝与浅拷贝

浅拷贝的介绍

  1. 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象
  2. 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
  3. 前面我们克隆羊就是浅拷贝,浅拷贝是使用默认的clone()方法来实现:sheep = (Sheep) super.clone();

代码示例

  1. Sheep:羊的实体类

    public class Sheep implements Cloneable {private String name;private int age;private String color;private String address = "蒙古";public Sheep friend; // 对象克隆时会如何处理,默认是浅拷贝// 克隆该实例,使用默认的clone方法来完成@Overrideprotected Object clone() {Sheep sheep = null;try {sheep = (Sheep) super.clone();} catch (Exception e) {System.out.println(e.getMessage());}return sheep;}// ...
    
  2. Client:测试代码

    public class Client {public static void main(String[] args) {System.out.println("原型模式完成对象的创建");Sheep sheep = new Sheep("tom", 1, "白色");sheep.friend = new Sheep("jack", 2, "黑色");Sheep sheep2 = (Sheep) sheep.clone(); // 克隆Sheep sheep3 = (Sheep) sheep.clone(); // 克隆Sheep sheep4 = (Sheep) sheep.clone(); // 克隆Sheep sheep5 = (Sheep) sheep.clone(); // 克隆System.out.println("sheep2 =" + sheep2 + "sheep2.friend=" + sheep2.friend.hashCode());System.out.println("sheep3 =" + sheep3 + "sheep3.friend=" + sheep3.friend.hashCode());System.out.println("sheep4 =" + sheep4 + "sheep4.friend=" + sheep4.friend.hashCode());System.out.println("sheep5 =" + sheep5 + "sheep5.friend=" + sheep5.friend.hashCode());}}
    
  3. 程序运行结果:可以看到,所有 Sheepfriend 属性的 hashCode 均相同,说明使用 Object 类的 Clone 方法为浅拷贝

    原型模式完成对象的创建
    sheep2 =Sheep [name=tom, age=1, color=白色, address=蒙古]sheep2.friend=366712642
    sheep3 =Sheep [name=tom, age=1, color=白色, address=蒙古]sheep3.friend=366712642
    sheep4 =Sheep [name=tom, age=1, color=白色, address=蒙古]sheep4.friend=366712642
    sheep5 =Sheep [name=tom, age=1, color=白色, address=蒙古]sheep5.friend=366712642
    

深拷贝基本介绍

  1. 复制对象的所有基本数据类型的成员变量值
  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝
  3. 深拷贝实现方式 1:重写clone方法来实现深拷贝
  4. 深拷贝实现方式 2:通过对象序列化实现深拷贝(推荐)

深拷贝应用实例

  1. DeepCloneableTarget:该类将会以成员变量的形式出现在其他类中,我们要对其实现深拷贝

    public class DeepCloneableTarget implements Serializable, Cloneable {private static final long serialVersionUID = 1L;private String cloneName;private String cloneClass;// 构造器public DeepCloneableTarget(String cloneName, String cloneClass) {this.cloneName = cloneName;this.cloneClass = cloneClass;}// 因为该类的属性,都是String , 因此我们这里使用默认的clone完成即可@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
    }
    
  2. DeepProtoType:该类中演示了两种实现深拷贝的方法

    public class DeepProtoType implements Serializable, Cloneable {public String name; // String 属性public DeepCloneableTarget deepCloneableTarget;// 引用类型public DeepProtoType() {super();}// 深拷贝 - 方式 1 使用clone 方法@Overrideprotected Object clone() throws CloneNotSupportedException {Object deep = null;// 这里完成对基本数据类型(属性)和String的克隆deep = super.clone();     DeepProtoType deepProtoType = (DeepProtoType) deep;// 对引用类型的属性,进行单独处理deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();return deepProtoType;}// 深拷贝 - 方式2 通过对象的序列化实现 (推荐)public Object deepClone() {// 创建流对象ByteArrayOutputStream bos = null;ObjectOutputStream oos = null;ByteArrayInputStream bis = null;ObjectInputStream ois = null;try {// 序列化bos = new ByteArrayOutputStream();oos = new ObjectOutputStream(bos);oos.writeObject(this); // 当前这个对象以对象流的方式输出// 反序列化bis = new ByteArrayInputStream(bos.toByteArray());ois = new ObjectInputStream(bis);DeepProtoType copyObj = (DeepProtoType) ois.readObject(); // 从流中读入对象return copyObj;} catch (Exception e) {// TODO: handle exceptione.printStackTrace();return null;} finally {// 关闭流try {bos.close();oos.close();bis.close();ois.close();} catch (Exception e2) {System.out.println(e2.getMessage());}}}}
    
  3. Client:测试代码

    public class Client {public static void main(String[] args) throws Exception {DeepProtoType p = new DeepProtoType();p.name = "宋江";p.deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");// 方式1 完成深拷贝DeepProtoType p2 = (DeepProtoType) p.clone();System.out.println("p.name=" + p.name + ";p.deepCloneableTarget.hashCode=" + p.deepCloneableTarget.hashCode());System.out.println("p2.name=" + p2.name + ";p2.deepCloneableTarget.hashCode=" + p2.deepCloneableTarget.hashCode());// 方式2 完成深拷贝
    //      DeepProtoType p2 = (DeepProtoType) p.deepClone();
    //      System.out.println("p.name=" + p.name + ";p.deepCloneableTarget.hashCode=" + p.deepCloneableTarget.hashCode());
    //      System.out.println("p2.name=" + p2.name + ";p2.deepCloneableTarget.hashCode=" + p2.deepCloneableTarget.hashCode());}}
    

关于 String 的拷贝问题

  1. clone() 方法为浅拷贝方法,使用 clone() 方法不是说只能拷贝基本数据类型吗?那为什么 String 类型的成员变量无需单独进行拷贝?
  2. 我的理解是:因为 String 的不可变性,对象 c1 通过浅拷贝得到对象 c2,假设此时 c1c2 中有一个 String 变量指向堆中同一个 String 引用,c1c2 但凡其中有一个修改 String 对象的值,由于 String 的不可变性,便会新创建一个 String,导致 String 类型的变量无需单独做 clone() 操作

7、原型模式注意事项

原型模式的注意事项和细节

  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  2. 不用重新初始化对象,可以动态地获得对象运行时的状态
  3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
  4. 在实现深克隆的时候可能需要比较复杂的代码,其实使用序列化机制实现克隆的代码也不难
  5. 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp 原则

第 7 章 原型模式相关推荐

  1. 第六章 Caché 设计模式 原型模式

    文章目录 第六章 Caché 设计模式 原型模式 定义 使用场景 优点 结构图 描述 示例 初级写法 缺点 中级写法 缺点 高级写法 (浅复制) 浅复制 深复制 完整示例 简历类(复制类) 对象类(工 ...

  2. 《Android之大话设计模式》--设计模式 创建型模式 第五章:原型模式

    原型模式应用场景举例:  GG和MM经常在QQ上聊天,但是GG打字的速度慢如蜗牛爬行,每次MM在瞬间完成恢复或者问候是,GG都会很紧张的去尽力快速打字,尽管如此,还是让 MM有些不高心,MM说回复信息 ...

  3. Java二十三设计模式之-----原型模式

    一.原型模式(Prototype) 原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制.克隆,产生一个和原对象类似的新对象.本小结会通 ...

  4. 【设计模式】单例模式-生成器模式-原型模式

    前面的几种工厂模式,主要用于选择实现,这里的三种模式:单例模式.生成器模式.原型模式,主要用于生成对象,在GoF的划分中,这是创建型的五种模式(不包括简单工厂,前面提到过,这不是一个标准意义上的设计模 ...

  5. Java的二十三种设计模式(原型模式(Prototype))

    原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制.克隆,产生一个和原对象类似的新对象.本小结会通过对象的复制,进行讲解.在Java中 ...

  6. 大话设计模式—原型模式

    原型模式(Prototype Pattern),用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象:即用于创建重复的对象,同时又能保证性能.这种类型的设计模式属于创建型模式,它提供了一种创建 ...

  7. 对象的克隆——原型模式

    本文转载自 :http://blog.csdn.net/lovelion/article/details/7424559 张纪中版<西游记>以出乎意料的造型和雷人的台词遭到广大观众朋友的热 ...

  8. javascript中组合使用构造函数模式和原型模式创建对象

    首先来讲一下构造函数创建对象.ECMAScript中的构造函数可用来创建特定类型的对象.请看下面示例了解一下构造函数模式: function Person(name, age, job){this.n ...

  9. 第 22 章 备忘录模式

    第 22 章 备忘录模式 1.游戏角色状态恢复问题 游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力), 当大战Boss后攻击力和防御力下降, 然后从备忘录对象恢复到大战前的状 ...

最新文章

  1. 一步一步SharePoint 2007之十六:注册并配置一个网站用户
  2. 【Python】百度翻译的爬虫实现(后篇)
  3. C#开发微信门户及应用(7)-微信多客服功能及开发集成
  4. 应用10秒部署、成本降低50% 阿里云serverless容器改写云计算极限
  5. (zt)svn 随服务器启动
  6. [.NET] 《Effective C#》快速笔记(四)- 使用框架
  7. bzero等函数源代码实现
  8. Tableau画图初学者~新手教程~常见类型图
  9. Python中统一快速更换变量的名称
  10. 百位活跃天使投资人名单
  11. 20190625——特征抽取 主成分分析
  12. 元素偏移offset的常用属性
  13. 第十三届蓝桥杯《EDA设计与开发》赛后总结
  14. 游戏里经常有涉及用户排行榜(金币消费排行榜),怎么设计一个良好的排行榜。
  15. 考研英语 词根词缀单词81-88
  16. IT项目管理的六种错误思维
  17. html输入时从本文框的顶格输入,浙江省绍兴市越城区2017—2018学年八年级第二学期期末语文试卷(15页)-原创力文档...
  18. Matrixport,交易、借贷和托管,如何打造数字货币界的银行?
  19. C语言qsort排序
  20. photoshop 字体小_使用Fontea Photoshop插件将Google字体添加到Photoshop

热门文章

  1. 刘强东卸任京东 CEO,“二号位”徐雷接棒:三大电商巨头“二把手”正式集齐
  2. 2022 年“苹果学者”名单公布,4 位华人学生位列其中
  3. 窃取任意GitHub Actions敏感信息如此简单,只需要分支改个名?
  4. 确认无疑,.NET 6是迄今为止最快的.NET
  5. 130 行代码模仿火爆抖音的“蚂蚁呀嘿”特效,你学会了吗?
  6. 程序员的求生欲有所强?用Python花式哄女友
  7. B 站 Up 主自制秃头生成器,圆你秃头梦想可好?
  8. 曾遭周鸿祎全网封杀的 360 猛将 :草根打工到 36 岁身家上亿的逆袭!
  9. 25 岁的老 Delphi,还值得程序员入手吗?
  10. “程序员不会数据分析,有什么影响?”资深程序员:基本等于自废武功!