根据实际案例分析原型模式的优点
在传统模式下,假设需要克隆对象,动态的获取原有对象的值(一个副本不是引用传递,修改克隆出的新对象不会影响原有的),例如:对现有的Persion对象进行克隆,需要创建副本对象,然后一个一个的获取原有对象属性值,赋值给副本对象,比较繁琐
Persion persion = new Persion("小明",18);
//1.传统模式下克隆对象(不影响原有对象,不是引用传递)
//创建对象,通过get,set方法或构造器获取原有对象的中的数据,赋值给新对象
//比较麻烦,当添加新属性时需要重新编写获取,赋值等
Persion clonP1 = new Persion(persion.getName(),persion.getAge());
原先模式也就是通过将需要克隆的类实现Cloneable接口,重写clone()方法,实现克隆功能(注意浅克隆与深克隆时的不同),或者通过流的方式实现克隆的功能
浅克隆
Persion类实现 Cloneable接口重写clone()方法实现,实现浅克隆
代码示例
//实现Cloneable接口
class Persion implements Cloneable{
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
private Dog dog;
public Persion(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public Persion(String name, Integer age, Dog dog) {
super();
this.name = name;
this.age = age;
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
//重写克隆方法(该方法适用与当前类中没有引用类型变量.引用类型不包括String)
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试分析
当Persion中的变量都是基本类型时,克隆出的副本是值传递,修改新的对象中的变量不会影响原型的值,
当Persion中存在引用变量时,引用变量的克隆是值传递,如果修改新对象中的引用类型数据,原型中的也会更改
深克隆
方式一: 需要克隆的原型,与原型中所包含的引用变量同时实现Cloneable接口并重写clone()方法,在原型的clone()方法中,首先克隆出原型的基本类型变量,然后通过原型中的引用变量调用自己重写的clone()方法,将引用变量自身克隆出来,然后赋值给克隆出的原型并返回
方式二: 通过流的方式,首先将原型本身以流的形式读取到内存中,然后通过输出流在转换会来,原型与原型中的引用类型都需要实现Serializable接口
代码示例
- Persion
//使用clone()方法实现克隆需要实现Clonable接口
//使用输入输出流实现克隆需要实现Serialziable接口
class Persion implements Cloneable, Serializable{
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
//引用类型变量
private Dog dog;
public Persion(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public Persion(String name, Integer age, Dog dog) {
super();
this.name = name;
this.age = age;
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
//实现深拷贝方式一: 重写clone()方法
@Override
protected Object clone() throws CloneNotSupportedException {
//1.首先进行克隆,获取到克隆出的副本obj,此时的obj与原型中的数据一致,只是
//修改obj中的引用类型变量由于引用类型是引用传递,会影响到原型中的变量数据
Object obj = super.clone();
//2.将克隆出的副本类型转换回Persion
Persion clonP = (Persion) obj;
//3.该类中的引用类型变量中也要重新clone方法
//引用类型变量调用自己的clone方法获取克隆出的副本
//注意点:如果引用变量中还有引用类型,该引用类中也需要向
//这个方式一样重新clone方法
if(null != dog) {
Dog clonD = (Dog) dog.clone();
//4.将克隆出的引用类型变量封装到对象中
clonP.setDog(clonD);
}
//5.返回封装好的克隆副本
return clonP;
}
//实现深拷贝方式二:
//通过对象的序列化(当前类,或类中的引用变量类需要实现Serializable接口)
public Persion persionClone() {
ByteArrayOutputStream byteOutStream = null;
ObjectOutputStream objectOutStream = null;
ByteArrayInputStream byteInStream = null;
ObjectInputStream objectInStream = null;
try {
//1.创建字节输出流
byteOutStream = new ByteArrayOutputStream();
//2.将字节输出流转换为对象输出流
objectOutStream = new ObjectOutputStream(byteOutStream);
//3.将当前类对象读取到输入流中
objectOutStream.writeObject(this);
//4.创建字节输入流
byteInStream = new ByteArrayInputStream(byteOutStream.toByteArray());
//5.将字节输出流转换为对象输出流
objectInStream = new ObjectInputStream(byteInStream);
//6.将第3步读取了当前对象的流输出为对象
Persion persion = (Persion) objectInStream.readObject();
return persion;
}catch (Exception e) {
e.printStackTrace();
return null;
}finally {
//7.关闭流
try {
byteOutStream.close();
objectOutStream.close();
byteInStream.close();
objectInStream.close();
}catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
- Dog,注意原型中包含的引用变量中是否还包含引用类型,如果还包含注意clone()实现深度克隆时引用变量中的的编写
class Dog implements Cloneable, Serializable{
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
public Dog(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
//Dog自己的clone方法,如果Dog中还有引用变量,则要修改为Persion中的那样
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试
public class DesignTest {
public static void main(String[]args) throws CloneNotSupportedException {
Persion persion = new Persion("小明",18);
//原型设计模式
Persion clonP2 = (Persion)persion.clone();
System.out.println(clonP2.getName());//小明
System.out.println(clonP2.getAge());//18
System.out.println(persion==clonP2);//false, 克隆出来的
//2.浅拷贝可能出现的问题
//假设此时Persion中有引用类型的成员变量Dog类
//在浅拷贝时,基本类型变量是值传递,引用类型则是引用传递
//如果修改克隆出的副本的引用数据,则原有的也会被影响
Persion p2 = new Persion("小红",17,new Dog("旺旺",3));
Persion cloPd = (Persion) p2.clone();
//3.深拷贝:修改重写的Cloneable中的clone()方法,
//或实现Serializable接口通过流的方式,读取对象,
//然后将对到的对象输出,修改克隆副本中的Dog变量
cloPd.getDog().setName("AA");
//在引用变量类Dog中实现Cloneable接口重写clone()方法,
//注意引用类型中是否还存在引用类型
//在Persion中修改clone()方法,方法中利用引用变量dog调用自己的
//clone()方法拷贝出来新的
//复制给Persion克隆出的副本,这样修改就不会有影响
System.out.println(cloPd.getDog().getName());//AA
System.out.println(p2.getDog().getName());//旺旺
//调用通过输入输出流方法实现克隆的方法
Persion clon2 = p2.persionClone();
Dog d = clon2.getDog();
d.setName("BB");
System.out.println(p2.getDog().getName());//"旺旺"
}
}
分析原型模式的优点
- 可以利用原型模式简化创建新对象,
- 可以动态获取不用对新对象重新赋值,
- 如果原型对象在程序运行中增加或减少属性,其克隆对象也会发生相应的变化
- 缺点: 注意深度克隆与浅克隆,每个类都需要配备一个克隆方法
Spring框架中原型模式案例
以Spring xml方式注入bean解释 : 在启动Spring时扫描XML配置文件,获取bean标签
通过 "scope"指定bean的创建方式,"prototype"为原型模式克隆也可指定为单利等
class test{
public static void main(String[]args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
Object obj1 = applicationContext.getBean("id");
/**
* 调用getBean("id值")方法获取对应的bean时,向下查看底层会调用一个doGetBean方法(),
* 该方法中会获取bean标签中的scope值,如果是"prototype"则使用调用
* createBean(beanName, mbd, args) 返回原型实例,
* 如果scope值是""则使用单利......
*/
Object obj2 = applicationContext.getBean("id");
//false 使用原型模式,两个obj中的值相等,但不是同一个对象
System.out.print(obj1 == obj2);
}
}
业务与原型模式的落地示例
- 电商平台中的商品SKU很多时候需要添加类似的属性和规格,这些属性和规格细节都差不多,但却需要重复添加,如果每次新建SKU都重新输入,那么工作量就会非常大。这个时候,使用原型设计模式可以将一个SKU作为原型,通过克隆的方式来创建新的SKU,从而避免了重复输入相同的信息,提高了工作效率
实际可以理解为创建一个base基类, 通过这个base基类减少一下重复属性的重复设置
- 定义一个商品SKU类,包含SKU的属性和规格。
public class Sku implements Cloneable {
private Long id;
private String name;
private String description;
private BigDecimal price;
private List<String> specs;
// 省略getters和setters
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
- 在SpringBoot中创建商品SKU时,可以将一个SKU作为原型,并通过克隆的方式来创建新的SKU。这个过程需要通过调用SKU类中的clone()方法来实现
@RestController
public class SkuController {
private final Sku skuPrototype = new Sku();
@PostMapping("/sku")
public ResponseEntity<String> createSku(@RequestBody Sku sku) throws CloneNotSupportedException {
// 从原型中克隆一个新的SKU对象
Sku newSku = (Sku) skuPrototype.clone();
// 将输入参数中不同的属性复制到新的SKU对象中
newSku.setId(sku.getId());
newSku.setName(sku.getName());
newSku.setDescription(sku.getDescription());
newSku.setPrice(sku.getPrice());
newSku.setSpecs(sku.getSpecs());
// 省略保存SKU对象到数据库或显示给用户的代码
return ResponseEntity.ok().body("SKU created successfully.");
}
}