一篇文章带你了解设计模式之原型模式

步入正题之前可先预习一下之前有讲过的

设计模式七大原则

单例模式

工厂模式

说到设计模式之原型模式就不得不提最经典的一个问题,克隆羊的问题

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

传统方式解决克隆羊问题

代码实现

Sheep:

public class Sheep {
 private String name;
 private int age;
 private String color;
 public Sheep(String name, int age, String color) {
  super();
  this.name = name;
  this.age = age;
  this.color = color;
 }
 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;
 }
 public String getColor() {
  return color;
 }
 public void setColor(String color) {
  this.color = color;
 }
 @Override
 public String toString() {
  return "Sheep [name=" + name + ", age=" + age + ", color=" + color + "]";
 }
 
 
}

Client:

public class Client {
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  //传统的方法
  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);
  //...
 }

}

通过以上代码大家想必也发现了一些问题,

缺点:总是需要重新获取初始化对象,而不是动态的获取对象运行时的状态,不够灵活

改进思路:

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


接下来来给大家简单介绍一下原型模式

原型模式

UML类图:

一篇文章带你了解设计模式之原型模式
image-20210525112910387

说明:

Prototype:原型类,声明一个克隆自己的接口

ConcretePrototype:具体的原型类,实现一个克隆自己的操作

Client:让一个原型对象克隆自己,从而创建一个新的对象。

概念:

原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象

原型模式一种创建型设计模式,允许一个对象在创建另外一个可复制的对象,无需知道如何创建的细节

工作原理:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝他们自己来实现创建,即对象

实现Cloneable接口重写clone()方法(浅拷贝默认使用

接下来让我们使用原型模式来针对上面的案例(克隆羊)进行改进吧。

为了方便我就展示需要添加修改的核心代码,其余的不变:

public class Sheep implements Cloneable {
 private String name;
 private int age;
 private String color;
 private String address = "蒙古羊";
 public Sheep friend; //是对象, 克隆是会如何处理
 public Sheep(String name, int age, String color) {
  super();
  this.name = name;
  this.age = age;
  this.color = color;
 }
 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;
 }
 public String getColor() {
  return color;
 }
 public void setColor(String color) {
  this.color = color;
 }
 
 
 
 @Override
 public String toString() {
  return "Sheep [name=" + name + ", age=" + age + ", color=" + color + ", address=" + address + "]";
 }
 //克隆该实例,使用默认的clone方法来完成
 @Override
 protected Object clone()  {
  
  Sheep sheep = null;
  try {
   sheep = (Sheep)super.clone();
  } catch (Exception e) {
   // TODO: handle exception
   System.out.println(e.getMessage());
  }
  // TODO Auto-generated method stub
  return sheep;
 }
 
 
}

Client:

public class Client {

 public static void main(String[] args) {
  System.out.println("原型模式完成对象的创建");
  // TODO Auto-generated method stub
  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());
 }

}

💡简要说明

 (Sheep)sheep.clone(); //克隆

Client直接调用 clone方法 而不是直接new 获取初始化对象的方法,调用的是默认的父类的clone方法,这个clone会自己去把你当前对象的实例跟它一一对应并返回。


说到这里原型模式就告一段落了,那么问题来了,在spring中哪里用到了原型模式呢?我相信大家一定都有用过吗,但是你是否还记得呢?让我们来一起回忆一下吧。

原型模式在Spring框架中源码分析

当我们创建bean的时候,就是使用的原型模式,不知道大家是否还记得bean.xml文件

例如:

 <!-- 这里我们的 scope="prototype" 即 原型模式来创建 scope 也可是singleton -->
 <bean id="id01" class="com.atguigu.spring.bean.Monster" 
  scope="prototype"/>

当我们使用以下方式进行获取bean的信息:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// [通过id获取]
  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
其实获取的对象是不一样的,此时spring创建的bean对象就是按照原型模式进行创建的,因为最后结果是false。


💡我们简要看一下源码哪里使用到了原型模式

当我们调用getBean()方法时,

查看源码如图所示

通过 getBeanFactory().getBean(name);

一篇文章带你了解设计模式之原型模式

继续往里面追的话发现调用了
AbstractRefreshableApplicationContext类的getBeanFactory()方法。

如图所示

一篇文章带你了解设计模式之原型模式

继续放下发现调用了AbstractBeanFactory类的getBean()方法

如图所示

一篇文章带你了解设计模式之原型模式

讲到这里其实已经到了核心代码也就是doGetBean()方法

该方法进行了单例和原型模式的判断:

如图所示(只粘出了核心代码):

一篇文章带你了解设计模式之原型模式

真正返回bean的原型实例的是createBean()这里就不详细展开了,有兴趣的可以下去自己追一下源码

以上就是设计模式中的原型模式在spring中的应用。讲到这里就告一段落了。看完这篇文章的你不知是否有所收获呢?

想必心细的你是不是在sheep类中发现了一个属性为Sheep的成员变量呢?它与其他成员变量的不一样之处在于它本身是一个对象类型,那我们在原型拷贝克隆的时候,对象类型的成员变量又会是怎么样的变化呢?

💡思考:在原型拷贝克隆的时候,对象类型的属性它是拷贝一份呢?还是一个引用的指向呢?

小伙伴们可以自己运行一下代码测试一下的。我这里就直接说结果了。

结论:

克隆过后,对象类型的属性(成员变量)并没有复制一份,而是让你这个对象的引用指向了第一个对象的对象属性的指向空间

以上这种情况就是浅拷贝。

接下来简单说一下什么是浅拷贝:

对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。

对于数据类型是引用数据类型的成员变量,,比如说某个成员变量是某个数组,某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量指向的都是同一个实例,在这种情况下,在一个对象中修改该成员变量会影响到另外一个对象的该成员变量值。

深拷贝的基本介绍

复制对象的所有基本数据类型的成员变量值

为所有引用类型数据的成员变量申请存储空间,并复制每个引用数据类型的成员变量所引用的对象,知道该对象可达的所有对象。也就是说。对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝。

深拷贝的实现方式

  1. 使用clone方法实现深拷贝

  2. 使用序列化来实现深拷贝

深拷贝案例

public class DeepCloneableTarget implements SerializableCloneable {
 
 /**
  * 
  */

 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完成即可
 @Override
 protected Object clone() throws CloneNotSupportedException {
  return super.clone();
 }
}

核心代码:

public class DeepProtoType implements SerializableCloneable{
 
 public String name; //String 属性
 public DeepCloneableTarget deepCloneableTarget;// 引用类型
 public DeepProtoType() {
  super();
 }
 
 
 //深拷贝 - 方式 1 使用clone 方法
 @Override
 protected Object clone() throws CloneNotSupportedException {
  
  Object deep = null;
  //这里完成对基本数据类型(属性)和String的克隆
  deep = super.clone(); 
  //对引用类型的属性,进行单独处理
  DeepProtoType deepProtoType = (DeepProtoType)deep;
  deepProtoType.deepCloneableTarget  = (DeepCloneableTarget)deepCloneableTarget.clone();
  
  // TODO Auto-generated method stub
  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 exception
   e.printStackTrace();
   return null;
  } finally {
   //关闭流
   try {
    bos.close();
    oos.close();
    bis.close();
    ois.close();
   } catch (Exception e2) {
    // TODO: handle exception
    System.out.println(e2.getMessage());
   }
  }
  
 }
 
}

测试

public class Client {

 public static void main(String[] args) throws Exception {
  // TODO Auto-generated method stub
  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=" + p.deepCloneableTarget.hashCode());
//  System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
 
  //方式2 完成深拷贝
  DeepProtoType p2 = (DeepProtoType) p.deepClone();
  
  System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
  System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
 
 }

}

好了到这里关于原型模式的讲解就告一段落了,简单总结一下吧。

小结:

创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能提高效率

不用重新初始化对象,而是动态的获取对象运行时的状态

如果原始对象发生变化(增加或减少属性),其他克隆对象的也会发生相应的变化,无需修改代码

在实现深克隆的时候可能需要比较复杂的代码

缺点:

需要为每一个类配备一个克隆方法,这对全新的类来说并不是很难,但对已存在的类进行改造时,需要修改其源码,违背了ocp原则


以上就是设计模式中的原型模式的全部讲解了。你学会了了吗?关注我后续带你解析更多的设计模式

点赞关注一下吧。

原文始发于微信公众号(码上遇见你):一篇文章带你了解设计模式之原型模式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/79040.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!