原型模式定义:
原型模式(prototype pattern)是指原型实例指定创建对象的种类,并通过克隆这些原型创建新的对象,不调用构造函数,属于创建型模式。
示例1: 分析浅克隆带来的问题
示例2: 序列化深度克隆
示例3: 还原克隆破坏单例的车祸现场
使用场景:
1.类初始化消耗资源较多
2.new产生的一个对象需要非常繁琐的过程(数据准备,访问权限等)
3.构造函数比较复杂
4.循环体中生产大量对象时
优点:
1.jdk自带的原型模型是基于内存二进制流拷贝,相比直接new对象性能有所提升。
2.可以使用深克隆方式保存对象的状态,不用频繁使用构造函数或get set设置参数,简化了创建过程。
缺点:
1.必须配备一个克隆方法。
2.当对已有类进行改造的时候需要修改代码,违反开闭原则。
示例1:
/**
* 浅克隆
* 创建具体原型类,实现jdk自有的克隆接口
* 以下分 ①,② 线
*/
@Data
public class ConcretePrototype implements Cloneable{
// ① 基础基本数据类型
private Integer age;
private String name;
// ② 后加入引用型
private List<String> ella;
/**
* 重写父类的克隆方法,省去手动get set
* @return
*/
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
", ella=" + ella +
'}';
}
}
public class MainExcute {
private static final Logger logger = Logger.getLogger(MainExcute.class);
public static void main(String[] args) {
// 原型对象
ConcretePrototype prototype = new ConcretePrototype();
// ①
prototype.setName("张三");
prototype.setAge(20);
// ② 后加入
List<String> ella = new ArrayList();
ella.add("吉他");
prototype.setElla(ella);
logger.info("原型对象: " + prototype);
// 克隆对象
ConcretePrototype clonePrototype = prototype.clone();
// ② 注: 正常来说给克隆对象修改后应该于原型对象不一样了,因为原型对象并没有增加美术这门爱好
clonePrototype.getElla().add("美术");
logger.info("克隆对象: " + clonePrototype);
// ② 问题: 但把原型对象输出后却发现原型竟然跟着克隆产生变化了
logger.info("原型对象: " + prototype);
// false 两个对象进行比较后又确实是不同的对象,但为什么输出结果是一样的? 要知道原型是并没有做修改的
logger.info(prototype == clonePrototype);
// true 继续比较两个对象的ella, 神奇发现ella值是相同的
logger.info(prototype.getElla() == clonePrototype.getElla());
/**
* 得出结论: 原型跟克隆对象的内存地址不一样,但是他们共用了ella值的内存地址。
* 揪出原因: age, name 属于基本跟特殊数据类型,而ella是属于引用类型,我们的clone方法最终只会把引用类型内存地址的值
* 拷贝过去,本没有把具体元素做复制处理,所以当修改克隆对象时导致原型也产生变化,这样显然不符合我们的预期。
*
* 解决: 实现深度克隆
*/
}
}
输出结果:
// ① 没有加入引用类型时克隆是正常的
原型对象: ConcretePrototype{age=20, name='张三'}
克隆对象: ConcretePrototype{age=20, name='张三'}
// ② 增加引用型后输出表面看似没有问题
原型对象: ConcretePrototype{age=20, name='张三', ella=[吉他]}
克隆对象: ConcretePrototype{age=20, name='张三', ella=[吉他]}
// ② 但是当克隆对象进行修改后原型竟然也跟着变化了
克隆对象: ConcretePrototype{age=20, name='张三', ella=[吉他, 美术]}
原型对象: ConcretePrototype{age=20, name='张三', ella=[吉他, 美术]}
示例2:
/**
* 从例1 延申序列化深度克隆,需要实现序列化接口
*/
@Data
public class ConcretePrototype implements Cloneable,Serializable{
private Integer age;
private String name;
private List<String> ella;
/**
* 为了遵循里氏替换原则,不改变父类方法本身具有的含义(默认浅克隆),
* 因此定义新的方法,通过序列化与反序列化实现深克隆。
*/
public ConcretePrototype deepClone() {
try {
// 将对象变成字节流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 将字节流变成java能够识别的字节
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化得到我们的对象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (ConcretePrototype)ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 默认浅克隆
* 重写父类的克隆方法,省去手动get set
* @return
*/
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
", ella=" + ella +
'}';
}
}
public class MainExcute {
private static final Logger logger = Logger.getLogger(MainExcute.class);
public static void main(String[] args) {
// 原型对象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setName("张三");
prototype.setAge(20);
List<String> ella = new ArrayList();
ella.add("吉他");
prototype.setElla(ella);
// * 换成调用深克隆函数,输出结果符合期望值
ConcretePrototype clonePrototype = prototype.deepClone();
clonePrototype.getElla().add("美术");
logger.info("克隆对象: " + clonePrototype);
logger.info("原型对象: " + prototype);
// false
logger.info(prototype == clonePrototype);
// false
logger.info(prototype.getElla() == clonePrototype.getElla());
}
}
输出结果:
克隆对象: ConcretePrototype{age=20, name='张三', ella=[吉他, 美术]}
原型对象: ConcretePrototype{age=20, name='张三', ella=[吉他]}
示例3:
/**
* 假设原型本身就是一个单例,而原型的创建又不需要构造方法,
* 那么单列的构造方法是不是私有的就跟原型没有半毛钱关系,因为原型不走构造方法,
* 而单例是通过构造方法创建的,所以导致冲突
*/
@Data
public class ConcretePrototype implements Cloneable,Serializable{
// 演示: 创建单例
private static ConcretePrototype instance = new ConcretePrototype();
private ConcretePrototype(){}
public static ConcretePrototype getInstance(){
return instance;
}
/**
* 克隆方法根本不走构造函数,构造私有变得无意义
* @return ConcretePrototype
*/
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
public class MainExcute {
private static final Logger logger = Logger.getLogger(MainExcute.class);
public static void main(String[] args) {
ConcretePrototype prototype = ConcretePrototype.getInstance();
ConcretePrototype clonePrototype = prototype.clone();
// * false 克隆出来得对象竟然不一样,明显单例已经被破坏
logger.info(prototype == clonePrototype);
/**
* 最终结果: 发现他们是互斥的,如果对象是单例,则不能是原型
*/
}
}
image.png