原型模式

根据实际案例分析原型模式的优点

在传统模式下,假设需要克隆对象,动态的获取原有对象的值(一个副本不是引用传递,修改克隆出的新对象不会影响原有的),例如:对现有的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接口

代码示例

  1. 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();
			}
		}
	}
}
  1. 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());//"旺旺"
	}
}

分析原型模式的优点

  1. 可以利用原型模式简化创建新对象,
  2. 可以动态获取不用对新对象重新赋值,
  3. 如果原型对象在程序运行中增加或减少属性,其克隆对象也会发生相应的变化
  4. 缺点: 注意深度克隆与浅克隆,每个类都需要配备一个克隆方法

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); 
	}
}

业务与原型模式的落地示例

  1. 电商平台中的商品SKU很多时候需要添加类似的属性和规格,这些属性和规格细节都差不多,但却需要重复添加,如果每次新建SKU都重新输入,那么工作量就会非常大。这个时候,使用原型设计模式可以将一个SKU作为原型,通过克隆的方式来创建新的SKU,从而避免了重复输入相同的信息,提高了工作效率

实际可以理解为创建一个base基类, 通过这个base基类减少一下重复属性的重复设置

  1. 定义一个商品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();
    }
}
  1. 在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.");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值