架构师内功心法,只是单纯听说过的原型模式详解
一、原型模式的应用场景
你一定遇到过这样的代码场景,有大量的getter、setter赋值的场景。例如这样的代码片段:
private SafetyStockMessage createKafkaMessage(SafetyStock safetyStock, HttpServletRequest request) { SafetyStockMessage safetyStockMessage = new SafetyStockMessage(); safetyStockMessage.setId(safetyStock.getId()); safetyStockMessage.setProvinceCode(safetyStock.getProvinceCode()); safetyStockMessage.setRequestId(CodeConstants.REQUEST_ID); safetyStockMessage.setRequestIp(CodeConstants.REQUEST_IP); safetyStockMessage.setSerial(IdMakerUtil.make32Id()); safetyStockMessage.setStockMax(safetyStock.getStockMax()); safetyStockMessage.setStockMin(safetyStock.getStockMin()); safetyStockMessage.setProvince(safetyStock.getProvince()); safetyStockMessage.setCategoryName(safetyStock.getCategoryName()); safetyStockMessage.setUpdateTime(new Date()); safetyStockMessage.setUpdateBy(getLoginUser(request)); return safetyStockMessage; }
代码看起来非常工整,命名也很规范,大家觉得这样的代码优雅吗?这样的代码属于纯体力劳动。如果使用原型模式,可以帮助我们解决这样的问题。
原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这样原型创建新的对象。
原型模式主要适用于以下场景:
1、类初始化消耗的资源较多;
2、new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等);
3、构造函数比较复杂;
4、循环体中生产大量对象。
原型模型的类结构图:
二、简单克隆
一个标准的原型模式代码,应该是这样的设计的。先创建原型Prototype接口:
public interface Prototype { Prototype clone(); }
创建具体需要克隆的对象ConcretePrototype:
public class ConcretePrototype implements Prototype { private String name; private int age; private List<String> hobbies; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } @Override public ConcretePrototype clone() { ConcretePrototype concretePrototype = new ConcretePrototype(); concretePrototype.setName(this.name); concretePrototype.setAge(this.age); concretePrototype.setHobbies(this.hobbies); return concretePrototype; } }
创建Client对象:
public class Client { private Prototype prototype; public Client(Prototype prototype) { this.prototype = prototype; } public Prototype startClone(Prototype concretePrototype) { return (Prototype)concretePrototype.clone(); } }
测试代码:
public static void main(String[] args) { //创建一个具体的需要克隆的对象 ConcretePrototype concretePrototype = new ConcretePrototype(); //填充属性,准备测试 concretePrototype.setName("Kevin"); concretePrototype.setAge(18); List<String> hobbies = new ArrayList<>(); concretePrototype.setHobbies(hobbies); System.out.println(concretePrototype); //创建Client对象,准备进行克隆 Client client = new Client(concretePrototype); ConcretePrototype concretePrototypeClone = (ConcretePrototype) client.startClone(concretePrototype); System.out.println(concretePrototypeClone); System.out.println("克隆对象中的引用类型地址值是:" + concretePrototypeClone.getHobbies()); System.out.println("原对象中的引用类型地址值是:" + concretePrototype.getHobbies()); System.out.println("对象地址比较:" + (concretePrototypeClone.getHobbies() == concretePrototype.getHobbies())); }
运行结果:
从测试结果看出hobbies的引用地址是相同的,意味着不是复制值,而复制的是引用的地址。如果我们修改任何一个对象的属性值,concretePrototype和concretePrototypeClone的hobbies的值都会改变。这就是我们常说的浅克隆。只是完整复制了值类型数据,没有复制引用对象。换言之,所有的引用对象还是指向原来的对象,显然不是我们想要的结果。
下面我们继续改造代码,使用深度克隆。
三、深度克隆
我们来换一个场景,大家都知道齐天大圣孙悟空。首先它是一只猴子,有着七十二般变化,把一根毫毛放在嘴里一吹就变出千万个泼猴,手里还拿着金箍棒,金箍棒可变大变小。这就是我们耳熟能详的原型模式的经典体现。
创建原型猴子 Monkey 类:
public class Monkey { public int height; public int weight; public Date birthday; }
创建引用对象金箍棒GoldenCudgel类:
public class GoldenCudgel implements Serializable { public float h = 100f; public float d = 10f; public void changeBig() { this.d *= 2; this.h *= 2; } public void changeSmall() { this.d /= 2; this.h /= 2; } }
创建具体的对象齐天大圣孙悟空MonkeyKing类:
public class MonkeyKing extends Monkey implements Cloneable, Serializable { public GoldenCudgel goldenCudgel; public MonkeyKing() { this.birthday = new Date(); this.goldenCudgel = new GoldenCudgel(); } @Override protected Object clone() { return this.deepClone(); } /** * 深克隆 * @return */ protected Object deepClone() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); MonkeyKing copy = (MonkeyKing) ois.readObject(); copy.birthday = new Date(); return copy; } catch (IOException e) { e.printStackTrace(); return null; } catch (ClassNotFoundException e) { e.printStackTrace(); return null; } } /** * 浅克隆 */ public MonkeyKing shallowClone(MonkeyKing target) { MonkeyKing monkeyKing = new MonkeyKing(); monkeyKing.height = target.height; monkeyKing.weight = target.weight; monkeyKing.goldenCudgel = target.goldenCudgel; monkeyKing.birthday = new Date(); return monkeyKing; } }
测试代码:
public static void main(String[] args) throws Exception { MonkeyKing monkeyKing = new MonkeyKing(); MonkeyKing clone = (MonkeyKing)monkeyKing.clone(); System.out.println("深克隆:" + (monkeyKing.goldenCudgel == clone.goldenCudgel)); MonkeyKing shallow = new MonkeyKing(); MonkeyKing newMonkeyKing = shallow.shallowClone(shallow); System.out.println("浅克隆" + (shallow.goldenCudgel = newMonkeyKing.goldenCudgel)); }
运行结果:
- 克隆破坏单例模式
如果我们克隆的目标是单例模式创建的对象,那么意味着深克隆会破坏单例模式。如何防止克隆破坏单例,禁止深克隆便可。我们在单例的类中不实现Cloneable接口,在重写clone()方法中返回单例对象即可,代码如下:
@Override protected Object clone() throws CloneNotSupportedException { return INSTANCE; }
- Cloneable 源码分析
先看我们常用的 ArrayList 就实现了 Cloneable 接口,来看代码clone()方法的实现:
public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn‘t happen, since we are Cloneable throw new InternalError(e); } }