原型设计模式
1.传统方式解决克隆羊
1.1需求入手
现在具有一个需求,有一只羊,姓名为tom,年龄为1,颜色为白色,编写程序进行克隆出10只一样的羊。
1.2传统方式实现方式
1.2.1UML类图设计
1.2.2Sheep类的设计
/*** 绵羊类*/
public class Sheep {private String name;private int age;private String 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;}@Overridepublic String toString() {String str = "name = " + name + ", age = " + age + " color = " + color + " 。";return str;}}
1.2.3Client客户端的设计
客户端主要是通过setter/构造器进行设置属性的方式,进行新创建对象,这样进行完成克隆。
/*** 测试类*/
public class Test {public static void main(String[] args) {Sheep sheep = new Sheep();sheep.setName("tom");sheep.setAge(10);sheep.setColor("白色");ArrayList<Sheep> list = new ArrayList<>();for (int index = 0; index < 10; index++) {Sheep sheepClone = new Sheep();sheepClone.setName(sheep.getName());sheepClone.setAge(sheep.getAge());sheepClone.setColor(sheep.getColor());list.add(sheepClone);}System.out.println(sheep);for (Sheep item : list) {System.out.println(item);}}}
1.3传统方法解决克隆羊问题的优缺点
1.3.1优点
比较好理解,简单容易操作。
1.3.2缺点
1.在创建新对象的时候,总是需要重新获取原始对象的属性,再进行设置/构造新对象,如果创建的
对象比较复杂,那么效率会比较低下。
要点:效率低下。
2.总是需要重新初始化对象,而不是动态地获取对象运行时的状态,不够灵活。
要点:克隆时必须重新走一遍对象的生命周期,不能直接把一个有运行时状态的对象赋值为一个新的。
1.4改进思路
利用JDK中原生的Object类中的clone克隆机制。
Object中提供了一个clone方法,这个方法进行提供了浅拷贝机制,想要调用这个方法的类必须进行
实现Cloneable接口,这个接口是一个Mark Interface,标识这个类的实例化对象具有被clone的能力。
2.原型模式
2.1基本介绍
1.原型模式(Prototype模式):用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新
的对象。
2.原型模式时一种创建型设计模式,允许一个对象(以这个对象作为一个原型)再创建另外一个可
定制的对象,无需知道如何创建的细节。
3.原型设计模式的工作原理:通过将一个原型对象传给那个需要发动创建的对象,这个要发动创建
的对象通过请求原型模式拷贝它们自己来实施创建,即对象.clone()
2.2UML类图的设计
以下就是UML类图的设计。
Prototype:原型类,声明一个克隆自己的接口 => 原生情况一般是Object类。
ConcreatePrototype1,ConcreatePrototype2:具体的原型类,实现一个克隆自己的操作(一般就是重写自己的类)
Client:去调用实现了Prototype接口的原型类进行克隆出实现了相应克隆方法的原型对象。

2.3使用原型设计模式实现对象克隆
2.3.1Sheep类
在这里,Sheep其实是默认继承Object的(默认就是这样的)
Obejct在这里就是一个进行定义Prototype克隆接口的类,Sheep其实就是一个克隆方法实现类。
/*** 绵羊类*/
public class Sheep {private String name;private int age;private String 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;}@Overridepublic String toString() {String str = "name = " + name + ", age = " + age + " color = " + color + " 。";return str;}}
2.3.2Client客户端
直接调用原型模式实现对象的clone方法进行克隆,这样就实现了原型模式。
原型模式调用的是JDK底层的clone方法,这个方法和上面传统的进行获取被克隆对象中的属性,进行初始化克隆对象并设置属性不同,这个是JDK底层提供的方法,会进行将对象中运行时状态进行复制出来一个对象进行使用。
import java.util.ArrayList;public class Client {public static void main(String[] args) throws CloneNotSupportedException {Sheep sheep = new Sheep();sheep.setName("tom");sheep.setAge(10);sheep.setColor("白色");ArrayList<Sheep> list = new ArrayList<>();for (int index = 0; index < 10; index++) {Sheep sheepClone = (Sheep) sheep.clone();list.add(sheepClone);}System.out.println(sheep);for (Sheep item : list) {System.out.println(item);}}}
3.原型模式在Spring中的应用
3.1应用场景
原型模式主要是在SpringBean创建过程中进行使用到的,SpringIOC进行托管SpringBean对象的时候进行使用到了原型设计模式,将SpringBean注入Bean时的Scope更改为Prototype即可进行原型模式。
3.2测试SpringBean的不同创建模式
3.2.1默认模式(Scope = Singleton)
配置Configuration,进行Bean包扫描。
import org.springframework.context.annotation.ComponentScan; 1
import org.springframework.context.annotation.Configuration; 2
3
@Configuration 4
@ComponentScan("com.yangge.prototype.spring") 5
public class SpringConfig {
} 7
进行配置Bean包
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** 绵羊类*/
@Component
public class Sheep implements Cloneable {@Value("tom")private String name;@Value("18")private int age;@Value("black")private String 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;}@Overrideprotected Object clone() throws CloneNotSupportedException {Sheep sheep = null;sheep = (Sheep) super.clone();return sheep;}@Overridepublic String toString() {String str = "name = " + name + ", age = " + age + " color = " + color + " 。";return str;}}
进行编写测试代码:
其实就是使用AnnotationConfigApplicationContext进行读取配置类,并进行获取Bean对象,进行比 较多次获取Bean对象是相同的类对象嘛。
import org.springframework.context.annotation.AnnotationConfigApplicationContext;/*** 测试Spring中的原型设计模式*/
public class Test {public static void main(String[] args) {AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);Sheep bean = annotationConfigApplicationContext.getBean(Sheep.class);System.out.println(bean);Sheep newBean = annotationConfigApplicationContext.getBean(Sheep.class);System.out.println(newBean);System.out.println(bean == newBean);}}
测试结果:
可以发现默认情况下的Sington模式,创建出来的对象是一样的,是单例设计模式的体现。

3.2.2原型模式(Scope = prototype)
使用Scope = prototype进行将创建出来的对象的作用域更改为prototype原型模式
在Bean上面进行使用@Bean进行注解。
/*** 绵羊类*/
@Component
@Scope("prototype")
public class Sheep implements Cloneable {
测试代码和默认模式的时候是一样的,进行测试:
可以发现这次获取到的Bean对象就是完全不同的Bean对象了,底层设计是原型设计模式。

3.3SpringBean创建的流程分析
4.原型设计模式
4.1浅拷贝的问题所在
4.1.1问题描述
Object默认进行提供的是浅拷贝的代码。
浅拷贝是纯的变量拷贝,基本数据类型和引用数据类型进行拷贝都是进行值拷贝,也就是说引用数据类型进行拷贝的时候也仅仅是将变量进行拷贝过去而已,拷贝之后两个对象的引用字段进行指向的都是同一个引用变量,这就是最大的问题。
4.1.2问题测验
4.1.2.1校验代码
Sheep类还是刚刚哪个类:
/*** 绵羊类*/
public class Sheep implements Cloneable {private String name;private int age;private String color;private Sheep friend;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;}public Sheep getFriend() {return friend;}public void setFriend(Sheep sheep) {friend = sheep;}@Overrideprotected Object clone() throws CloneNotSupportedException {Sheep sheep = null;sheep = (Sheep) super.clone();return sheep;}@Overridepublic String toString() {String str = "name = " + name + ", age = " + age + " color = " + color + " 。";return str;}}
测试代码:
主要是进行检验引用变量进行拷贝后的指向。
import java.util.ArrayList;/*** 测试浅拷贝的问题*/
public class Client {public static void main(String[] args) throws CloneNotSupportedException {Sheep sheep = new Sheep();sheep.setName("tom");sheep.setAge(10);sheep.setColor("白色");ArrayList<Sheep> list = new ArrayList<>();for (int index = 0; index < 10; index++) {Sheep sheepClone = (Sheep) sheep.clone();list.add(sheepClone);}System.out.println(sheep);for (Sheep item : list) {System.out.println(item.getFriend() == sheep.getFriend());}}}
4.1.2.2校验结果
4.2深拷贝对问题的解决
深拷贝对于基本数据类型和浅拷贝的处理方式是一样的,都是对数据的值进行拷贝。 但是深拷贝对对象的处理方式是不一样的。
深拷贝对对象的处理方式:
深拷贝会为所有引用数据类型的成员变量进行申请存储空间,并进行复制每个引用数据类型成员变 量锁引用的对象,知道该对象可达的所有对象(对于嵌入的对象处理方式是不一样,进行克隆对象中的对象字段的数据的时候,是根据克隆的对象字段的数据进行选择使用深拷贝还是浅拷贝)
深拷贝对象的实现方式:
深拷贝的实现方式1:重写clone方法实现深拷贝。
深拷贝的实现方式2:通过对象序列化实现深拷贝。
4.3实现深拷贝
4.3.1使用重写clone方法实现深拷贝
4.3.1.1定义内部嵌套引用对象类
想要使用重写clone方法进行实现深拷贝,里面嵌套的对象也要进行实现Cloneable接口,重写clone 方法。
import java.io.Serializable;public class DeepCloneableTarget implements Serializable, Cloneable {private static final long serialVersionUID = 4548645106533515943L;private String cloneName;private String cloneClass;public DeepCloneableTarget(String cloneName, String cloneClass) {this.cloneName = cloneName;this.cloneClass = cloneClass;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}}
4.3.1.2定义外部类 => 内部进行引用对象字段
引用了DeepCloneableTarget类型的字段。
进行重写了clone规则,其实深拷贝的原理很简单,内部有需要进行深拷贝的对象字段的时候,调用对象相应的clone方法,进行克隆出来一个对象,并对外部对象的对象字段进行赋值。
要点:其实就是多层调用clone,不是简简单单的指针转换。
import java.io.Serializable;public class DeepProtoType implements Serializable, Cloneable {private static final long serialVersionUID = -1909828171731398327L;// 名称public String name;// 引用数据类型public DeepCloneableTarget deepCloneableTarget;public DeepProtoType() {super();}// 深拷贝 - 方式1使用clone@Overrideprotected Object clone() throws CloneNotSupportedException {Object deep = null;// 这里完成对基本数据类型deep = super.clone();// 对引用类型的属性, 进行单独处理DeepProtoType deepProtoType = (DeepProtoType) deep;deepProtoType.deepCloneableTarget = (DeepCloneableTarget) this.deepCloneableTarget.clone();// 返回出去生产出的对象return deep;}
}
4.3.1.3进行测试深拷贝
测试代码:
/*** 测试客户端*/
public class Client {public static void main(String[] args) throws CloneNotSupportedException {DeepProtoType deepProtoType = new DeepProtoType();deepProtoType.name = "大哈哈哈";DeepCloneableTarget deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");deepProtoType.deepCloneableTarget = deepCloneableTarget;// 方式一完成深拷贝DeepProtoType clone = (DeepProtoType) deepProtoType.clone();System.out.println(deepProtoType == clone);System.out.println(deepProtoType.deepCloneableTarget == clone.deepCloneableTarget);}}
测试结果:

4.3.2使用Serializable序列化的方式
这种方式是进行使用的JDK原生的序列化方式。
4.3.2.1在外部类中新增deepClone方法
序列化反序列化进行初始化的方案其实就是先进性使用out流将对象进行序列化,然后使用input流对对象进行反序列化,这样得到的对象就是深拷贝后的对象。
public Object deepClone() throws IOException {// 创建流对象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 copyObject = (DeepProtoType) ois.readObject();return copyObject;} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} finally {bos.close();oos.close();bis.close();ois.close();}return null;
}
4.3.2.2进行测试深拷贝
测试代码:
import java.io.IOException;/*** 测试序列化深拷贝*/
public class TestClient {public static void main(String[] args) throws IOException {DeepProtoType deepProtoType = new DeepProtoType();deepProtoType.name = "大哈哈哈";DeepCloneableTarget deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");deepProtoType.deepCloneableTarget = deepCloneableTarget;// 方式二完成深拷贝DeepProtoType clone = (DeepProtoType) deepProtoType.deepClone();System.out.println(deepProtoType == clone);System.out.println(deepProtoType.deepCloneableTarget == clone.deepCloneableTarget);}}
测试结果:

4.3.3使用JSON序列化框架进行深拷贝
其实这个思路和使用JDK默认的Serializable进行序列化时一样的思路,都是进行先序列化,再进行反序列化创建出新对象进行使用。
使用JSON序列化框架进行操作,其实也是序列化的思路,将对象先进行序列化为Json数据,再进行将Json数据反序列化进行创建出新对象,这个反序列化创建出来的对象时一个获取了被克隆对象运行时状态的新对象。
4.3.3.1引入JSON序列化框架 => GSON
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version>
</dependency>
4.3.3.2为外部对象编写深克隆方法
public Object deepCloneJson() {Gson gson = new Gson();String json = gson.toJson(this);return gson.fromJson(json, DeepProtoType.class);
}
4.3.3.3进行测试深拷贝
// 方式三完成深拷贝
DeepProtoType jsonClone = (DeepProtoType) deepProtoType.deepCloneJson();
System.out.println(deepProtoType == jsonClone);
System.out.println(deepProtoType.deepCloneableTarget == jsonClone.deepCloneableTarget);
5.总结原型设计模式
5.1原型模式的要点
1.简化对象创建的利器
创建新的对象比较复杂时,可以使用原型模式简化对象的创建过程,提高效率。
2.动态获取对象状态
无需重新初始化对象,重新走生命周期,而是动态的获取对象运行时的状态,直接进行克隆出来一个新对象。
3.深克隆时较为复杂
实现深克隆的逻辑比较复杂,但是也是没办法的事。
5.2缺点
需要为每一个类都进行装配一个克隆方法,对于从新开始设计的类来说不是很难,但是对已有的类进行改造时,需要进行修改其源代码,违反了OCP原则(因为这个clone方法时固定的,没办法进行扩展,只能进行修改,这就导致论了无法做到对使用关闭的规则,违反了OCP开放原则)
要点:使用序列化的方法不会出现这种情况哦。