主文章(所有java设计模式的目录) |
---|
https://blog.csdn.net/grd_java/article/details/122252696 |
源码位置 |
---|
码云:https://gitee.com/yin_zhipeng/design_mode.git |
GitHub: |
学习设计模式前必须知道的东西 |
---|
看待设计模式,要站在更大的角度(代码重用性、可读性、可扩展性、可靠性、程序高内聚,低耦合)来综合考虑看待,而不是功能实现的角度看待,不要觉得实现一个功能没必要这么麻烦
文章中给出的设计模式类图都是标准的实现方式,并不一定要完全遵守标准,所以只要设计思想符合,一个设计模式有多种实现方式,尤其是看别人源码的时候,不要用标准类图死扣
1. 原型模式(Prototype Pattern)
思考一个问题 |
---|
- 现在有一只羊类(sheep类),有姓名tom,年龄1,颜色白色,这三个属性,编写程序创建和tom羊属性完全相同的10只羊
传统方法实现代码:com/yzpnb/design_mode/prototype_pattern/tradition包下 |
---|
- 创建Sheep类
- 复制10个
- 优缺点
- 优点仅仅是比较好理解,简单易操作,但是冗余度高
- 在创建新对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
- 总是需要初始化(new)对象,而不是动态地获取对象运行时的状态,不够灵活
如何改进呢:代码在:com/yzpnb/design_mode/prototype_pattern/improved |
---|
- java中Object类,提供了clone()方法,可以将一个java对象复制一份,但是需要实现clone的java类必须实现Cloneable接口,该接口表示该类能够复制且具有复制能力====》原型模式
- 原型模式:
- 用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()
- 就像孙悟空,拔出自己身上的毛,变出其它猴子
- 类图
1. 原型模式之浅拷贝
- 浅拷贝就是基本数据类型的成员变量,直接值传递,将属性复制一份给新对象
- 引用类型,例如数组,类对象等,只会进行引用传递,也就是将内存地址复制一份给新对象
- 当一个对象改变引用类型的变量时,其它对象都会受到影响
- 接下来我们介绍的克隆羊就是浅拷贝
- 通过默认的clone方法实现(
sheep = (Sheep)super.clone();
)
- Sheep类继承Cloneable接口,实现clone()方法,调用Object类clone方法实现拷贝
- 调用clone方法,获取拷贝对象(浅拷贝)
为什么说它是浅拷贝呢? |
---|
- 我们给Sheep加一个引用对象friend,表示他俩的朋友
- 测试克隆后两个对象的朋友是否是一个对象(浅拷贝就是一个对象,否则朋友也会克隆一个)
可见,这是浅拷贝,如果是深拷贝,朋友也应该拷贝一份,而这里只是将引用复制了过去 |
---|
2. 深拷贝
- 克隆所有基本数据类型成员变量的值
- 为所有引用数据类型变量申请空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是深拷贝,对整个对象拷贝,
刨根问底的克隆所有
- 实现方式1:重写clone方法实现深拷贝----人肉搜索
- 就是自己找到所有引用类型对象,统统继承Cloneable 实现clone方法
- 然后自己的clone方法中,把所有引用对象,统统人肉调用clone方法克隆
- 实现方式2:通过对象序列化实现深拷贝
- 序列化后,反序列化就完事了
1. 重写clone实现
实现方式一重写clone()实现:代码:com/yzpnb/design_mode/deepclone包下 |
---|
- 搞一个
专门作为引用对象的类
,也需要实现clone()
,如果没有引用类型,直接实现浅拷贝即可
- 搞一个我们需要深拷贝引用类型的类,重写clone方法,先浅拷贝自己(记为P1),然后手动浅拷贝自己引用的对象(记为C1、C2…),将拷贝后的自己引用的对象,赋值给自己(C1、C2…赋值给P1),深拷贝完成
- 测试
2. 通过反序列化实现
实现方式二反序列化实现:代码:com/yzpnb/design_mode/deepclone包下 |
---|
- 搞一个
专门作为引用对象的类
,继承序列化接口,指定序列id
- 搞一个我们需要深拷贝引用类型的类,同样需要实现序列化接口,然后编写克隆方法,进行序列化,然后反序列化克隆对象
- 测试
3. 源码
Spring源码中,用到了原形模式 |
---|
- 测试Spring源码和UML类图全部放在SpringSource模块中
- Spring中原形bean的创建,就有原型模式的应用
- 准备一个bean对象
- beans.xml
- ProtoType.java 类,获取两个bean实例,查看是否一样
- 进入getBean方法查看源码
- doGetBean,官方注释说明,使用了原型模式
2. 序列化和反序列化
具体序列化如何做,参考上面深拷贝,通过反序列化实现深拷贝,的代码 |
---|
序列化是干嘛的 |
---|
- 序列化的本意是
将对象变成字节序列
,方便持久化到磁盘
,避免程序运行结束后对象从内存消失,字节序列便于网络运输和传播
序列化和反序列化 |
---|
- 序列化:把Java对象转换为字节序列
- 反序列化:把字节序列恢复为原先的Java对象
为什么实现序列化需要继承Serializable并指定序列化ID |
---|
- 首先
Serializable接口是个空接口
,如果序列化对象不加implements Serializable会报错NotSerializableException,所以我们看看为什么会报这个错
- 异常在ObjectOutputStream类的writeObject0()方法抛出,我们发现如果
它不属于字符串、数组、枚举,也没实现Serializable接口
,就会抛出NotSerializableException异常
!所以,Serializable接口只是做标记作用
- 结论:
实现Serializable接口的类,表示标记自己是可以被序列化的。Serializable接口是空接口,真正的序列化动作不靠它完成
serialVersionUID号 |
---|
- 代码中经常定义如下代码变量serialVersionUID
serialVersionUID是序列化前后的唯一标识符
如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个
,如何类
的结构或者信息发生变化
,则类的serialVersionUID也会跟着变
化反序列化时
,JVM会
把字节流中的序列号ID和被序列化类中的序列号ID做比对
,只有两者一致
,才能重新反序列化
,否则就会报InvalidClassException异常
来终止反序列化的过程- 所以,
凡是implements Serializable的类,都最好人为显式地为它声明一个serialVersionUID
transient修饰符 |
---|
- 序列化对象时,不希望某个字段被序列化(比如密码等),可以用transient修饰符来修饰该字段
被transient修饰的字段,不会被序列化
不会被序列化的东西 |
---|
- 被
static修饰的字段不会被序列化
(因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域)- 被transient修饰符修饰的字段不会被序列化
序列化的受控和加强,B站程序羊那偷学来的,原文链接:https://r2coding.com/ |
---|
- 约束性加持
序列化和反序列化的过程
其实有漏洞
,序列化到反序列化有一个中间过程
,被别人拿到中间字节流
,就可以伪造或篡改
,反序列化出来的对象就有风险
。- 我们希望在反序列化时,进行受控的对象反序列化动作,自行编写readObject()函数,用于对象的反序列化构造,从而提供约束性
假设一个Student类,有一个score字段保存成绩,我们可以控制score保证在0到100之间 |
---|
private void readObject( ObjectInputStream objectInputStream ) throws IOException, ClassNotFoundException {
// 调用默认的反序列化函数
objectInputStream.defaultReadObject();
// 手工检查反序列化后学生成绩的有效性,若发现有问题,即终止操作!
if( 0 > score || 100 < score ) {
throw new IllegalArgumentException("学生分数只能在0到100之间!");
}
}
单例模式的增强 |
---|