原型模式解读

简介: 原型模式解读

模式引进问题

克隆羊问题:现在有一只羊 tom,姓名为: tom, 年龄为:1,请编写程序创建和 tom 羊 属性完全相同的 10只羊。

传统方式解决克隆羊问题

1. class Sheep{
2. private String name;
3. private  int age;
4. 
5. public Sheep(String name, int age) {
6. this.name = name;
7. this.age = age;
8.     }
9. 
10. public String getName() {
11. return name;
12.     }
13. 
14. public void setName(String name) {
15. this.name = name;
16.     }
17. 
18. public int getAge() {
19. return age;
20.     }
21. 
22. public void setAge(int age) {
23. this.age = age;
24.     }
25. }

操作代码

1. public class Prototype {
2. public static void main(String[] args) {
3. Sheep sheep = new Sheep("tom",1);
4. Sheep sheep1 = new Sheep(sheep.getName(),sheep.getAge());
5. Sheep sheep2 = new Sheep(sheep.getName(),sheep.getAge());
6. Sheep sheep3 = new Sheep(sheep.getName(),sheep.getAge());
7. //.....
8.     }
9. }

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

改进的思路分析:
思路:Java 中 Object 类是所有类的根类,Object 类提供了一个 clone()方法,该方法可以将一个 Java 对象复制一份,但是需要实现 clone 的 Java 类必须要实现一个接口 Cloneable,该接口表示该类能够复制且具有复制的能力

1. public interface Cloneable {
2. }

类实现接口, Cloneable 以向 Object.clone() 该方法指示该方法创建该类实例的逐字段副本是合法的。
在未实现 Cloneable 接口的实例上调用对象的克隆方法会导致引发异常 CloneNotSupportedException  

按照约定,实现此接口的类应使用公共方法重写 Object.clone (受保护)。

请注意,此接口不包含克隆方法。因此,不可能仅仅凭借它实现此接口的事实来克隆对象。即使以反射方式调用克隆方法,也不能保证它会成功

原型模式

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

原型模式原理结构图-uml 类图

原理结构图说明

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

原型模式解决克隆羊问题的应用实例

实现接口重写clone方法

1. @Override
2. protected Sheep clone()  {
3.         Sheep sheep=null;
4. try {
5.             sheep = (Sheep) super.clone();
6.         }catch (CloneNotSupportedException e){
7.             e.printStackTrace();
8.         }
9. return  sheep;
10.     }

操作代码

1. Sheep sheep = new Sheep("tom",1);
2. Sheep sheep1 = sheep.clone();

深拷贝和浅拷贝

浅拷贝的介绍

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

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

深拷贝基本介绍

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

3) 深拷贝实现方式 1:重写 clone 方法来实现深拷贝
4) 深拷贝实现方式 2:通过对象序列化实现深拷贝(推荐)

重写 clone 方法来实现深拷贝

现在假设有一个节点类如下:

1. public class Node implements  Cloneable,Serializable{
2. private int id;
3. private Sheep sheep;
4. 
5. public Node(int id, Sheep sheep) {
6. this.id = id;
7. this.sheep = sheep;
8.     }
9. 
10. public int getId() {
11. return id;
12.     }
13. 
14. public void setId(int id) {
15. this.id = id;
16.     }
17. 
18. public Sheep getSheep() {
19. return sheep;
20.     }
21. 
22. public void setSheep(Sheep sheep) {
23. this.sheep = sheep;
24.     }
25. 
26. 
27. }

重写方法

1. @Override
2. protected Object clone() throws CloneNotSupportedException {
3. Node clone = (Node)super.clone();//基本数据类型拷贝
4. Sheep clone1 = clone.getSheep().clone();//引用数据类型拷贝
5.         clone.setSheep(clone1);
6. return  clone;
7.     }

通过对象的序列化实现实现深拷贝(推荐)

1. @Override
2. protected Object clone() throws CloneNotSupportedException {
3. ByteArrayOutputStream bos = null;
4. ObjectOutputStream oos = null;
5. ByteArrayInputStream bis = null;
6. ObjectInputStream ois = null;
7. try {
8. //序列化
9.             bos = new ByteArrayOutputStream();
10.             oos = new ObjectOutputStream(bos);
11.             System.out.println("123");
12.             oos.writeObject(this);
13.             System.out.println("123");
14. //反序列化
15.             bis = new ByteArrayInputStream(bos.toByteArray());
16.             ois = new ObjectInputStream(bis);
17. Node  clone = (Node) ois.readObject();
18. return  clone;
19.         } catch (Exception e) {
20.             e.printStackTrace();
21. return  null;
22.         }finally {
23. try {
24.                 bos.close();
25.                 oos.close();
26.                 bis.close();
27.                 ois.close();
28.             }catch (Exception e){
29.                 e.printStackTrace();
30.             }
31.         }
32.     }

注意:还需要在Sheep类实现 Serializable接口

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

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


相关文章
|
域名解析 Linux Apache
Linux Apache服务详解——虚拟网站主机功能实战
Linux Apache服务详解——虚拟网站主机功能实战
321 5
|
定位技术
百度地图开发:map.setViewport让标注显示在最佳视野内
百度地图开发:map.setViewport让标注显示在最佳视野内
606 0
|
资源调度 分布式计算 Oracle
实时计算 Flink版操作报错合集之flink on yarn job manager 可以启动, 但不给分配slot ,是什么原因
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
375 0
|
JSON 前端开发 Java
【Java笔记+踩坑】SpringMVC基础
springmvc简介、入门案例、bean加载控制、PostMan工具的使用、普通和JSON和日期格式请求参数传递、响应JSON或jsp或文本、Rest风格
【Java笔记+踩坑】SpringMVC基础
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android 消息处理机制估计都被写烂了,但是依然还是要写一下,因为Android应用程序是通过消息来驱动的,Android某种意义上也可以说成是一个以消息驱动的系统,UI、事件、生命周期都和消息处理机制息息相关,并且消息处理机制在整个Android知识体系中也是尤其重要,在太多的源码分析的文章讲得比较繁琐,很多人对整个消息处理机制依然是懵懵懂懂,这篇文章通过一些问答的模式结合Android主线程(UI线程)的工作原理来讲解,源码注释很全,还有结合流程图,如果你对Android 消息处理机制还不是很理解,我相信只要你静下心来耐心的看,肯定会有不少的收获的。
577 3
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
监控 Java API
如何将不同业务模块产生的日志 分多文件记录
如何将不同业务模块产生的日志 分多文件记录
165 0
|
监控 持续交付 开发工具
Flow
Flow
466 6
|
C# 图形学
Winform控件优化之Paint事件实现圆角组件(提取绘制圆角的扩展方法)
Paint事件方法中实现圆角控件不要通过事件参数`e.ClipRectangle`获取控件区域范围,原因见最后介绍;注意设置控件背景透明(参见[Winform控件优化之背景透明那些事2...
999 0
Winform控件优化之Paint事件实现圆角组件(提取绘制圆角的扩展方法)
|
机器学习/深度学习 自然语言处理 知识图谱
探索深度学习中的迁移学习:知识融合与创新应用
迁移学习在深度学习领域中扮演着重要角色,但其应用依然充满挑战与机遇。本文从知识融合和创新应用两个方面探讨迁移学习的发展趋势与前景,通过深入分析相关理论与实践案例,揭示迁移学习在解决现实问题中的潜力与局限,并展望其在未来的发展方向。
490 1
|
存储 安全 算法
十种接口安全方案!!!
日常开发中,如何保证接口数据的安全性呢?接口数据安全的保证过程,主要体现在这几个方面:一个就是数据传输过程中的安全,还有就是数据到达服务端,如何识别数据,最后一点就是数据存储的安全性。介绍下保证接口数据安全的10个方案。数据加签:用Hash算法(如MD5,或者SHA-256)把原始请求参数生成报文摘要,然后用私钥对这个摘要进行加密,就得到这个报文对应的数字签名sign(这个过程就是加签通常来说呢,请求方会把数字签名和报文原文一并发送给接收方。验签:接收方拿到原始报文和数字签名(sign)后,用。
499 1