原型模式 原型模式(Prototype Pattern)是指通过原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。调用者不需要知道任何创建细节,不调用构造函数。原型模式属于创建型模式。
适用场景
类初始化消耗资源较多
new创建一个对象需要繁琐的过程(数据准备、访问权限等)
构造函数比较复杂
循环体中产生大量对象时,可读性下降
示例代码 浅克隆 一个标准的原型模式代码设计过程应该是这样的,
先创建原型接口:
1 2 3 public interface ProtoType { ProtoType clone () ; }
创建需要具体克隆的对象ConcreteProtoTypeA:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class ConcreteProtoTypeA 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 ProtoType clone () { ConcreteProtoTypeA concreteProtoType = new ConcreteProtoTypeA(); concreteProtoType.setName(this .name); concreteProtoType.setAge(this .age); concreteProtoType.setHobbies(this .hobbies); return concreteProtoType; } }
创建Client对象:
1 2 3 4 5 public class Client { public ProtoType startClone (ProtoType concreteProtoType) { return (ProtoType) concreteProtoType.clone(); } }
测试类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ProtoTypeTest { public static void main (String[] args) { ConcreteProtoTypeA concreteProtoType = new ConcreteProtoTypeA(); concreteProtoType.setName("protoType" ); concreteProtoType.setAge(30 ); List<String> hobbies = new ArrayList<>(); concreteProtoType.setHobbies(hobbies); System.out.println(concreteProtoType); Client client = new Client(); ConcreteProtoTypeA concreteProtoTypeClone = (ConcreteProtoTypeA) client.startClone(concreteProtoType); System.out.println(concreteProtoTypeClone); System.out.println("原对象中引用类型地址: " + concreteProtoType.getHobbies()); System.out.println("克隆对象中引用类型地址: " + concreteProtoTypeClone.getHobbies()); System.out.println("对象地址比较: " + (concreteProtoType.getHobbies() == concreteProtoTypeClone.getHobbies())); } }
输出结果:
1 2 3 4 5 com.example.designpattern.prototype.shallow.ConcreteProtoTypeA@54a097cc com.example.designpattern.prototype.shallow.ConcreteProtoTypeA@5a61f5df 原对象中引用类型地址: [] 克隆对象中引用类型地址: [] 对象地址比较: true
从测试结果可知getHobbies()的引用地址是相同的,即复制的是引用地址而不是值。这种情况下,如果修改任意一个对象的属性值,所有引用对象的属性值都会改变(所有的引用对象仍指向原来的对象),即浅克隆,下面使用深度克隆改造。
深克隆 创建原型Monkey类:
1 2 3 4 5 public class Monkey { public int height; public int weight; public Date birthday; }
创建引用类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Stick implements Serializable { public float h = 100 ; public float d = 10 ; public void big () { this .h *= 2 ; this .d *= 2 ; } public void small () { this .h /= 2 ; this .d /= 2 ; } }
创建具体对象MonkeyKing类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class MonkeyKing extends Monkey implements Cloneable , Serializable { public Stick stick; public MonkeyKing () { this .birthday = new Date(); this .stick = new Stick(); } @Override protected Object clone () { return this .deepClone(); } public 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 (Exception e) { e.printStackTrace(); return null ; } } public MonkeyKing shallowClone (MonkeyKing target) { MonkeyKing monkeyKing = new MonkeyKing(); monkeyKing.height = target.height; monkeyKing.weight = target.weight; monkeyKing.stick = target.stick; monkeyKing.birthday = new Date(); return monkeyKing; } }
测试类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DeepCloneTest { public static void main (String[] args) { MonkeyKing monkeyKing = new MonkeyKing(); try { MonkeyKing deepClone = (MonkeyKing) monkeyKing.clone(); System.out.println("深拷贝:" + (deepClone.stick == monkeyKing.stick)); } catch (Exception e) { e.printStackTrace(); } MonkeyKing monkeyKing1 = new MonkeyKing(); MonkeyKing shallowClone = monkeyKing1.shallowClone(monkeyKing1); System.out.println("浅拷贝:" + (shallowClone.stick == monkeyKing1.stick)); } }
运行结果:
克隆破坏单例模式 如果深度克隆的目标是单例对象,那么就会破坏单例。要解决深度克隆破坏单例的问题,可以考虑禁止深度克隆
单例类不实现Cloneable接口
重写clone()方法,在clone方法中返回单例对象
代码实现如下:
1 2 3 4 @Override protected Object clone () throws CloneNotSupportedException { return INSTANCE; }
Cloneable源码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public Object clone () { try { ArrayList<?> v = (ArrayList<?>) super .clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0 ; return v; } catch (CloneNotSupportedException e) { throw new InternalError(e); } }
JDK实例
BeanUtils.copy()
arrayList.clone()
模式优点
原型模式比直接new一个对象性能高
简化了创建过程
模式缺点
必须配备克隆方法
对克隆复杂对象或对克隆出来的对象进行复杂改造时,容易带来风险
需要合理使用深拷贝、浅拷贝
建造者模式 建造者模式(Builder Pattern)是将一个复杂对象的构建过程与它的表示分离,使得同样的构建过程可以创建不同的表示,属于创建型模式。使用建造者模式对于用户而言,只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。
建造者模式适用于创建对象需要很多步骤,但步骤的顺序不一定固定。如果一个对象有非常复杂的内部结构,可以将复杂对象的创建和使用进行分离。类图如下:
建造者模式的设计中主要有四个角色:
产品(Product),要创建的产品类对象
抽象的建造者(Builder),建造者的抽象类,规范产品对象的各个组成部分的建造,一般由子类实现具体的建造过程
具体的建造者(ConcreteBuilder),具体的Builder类,根据不同的业务逻辑,具体化对象的各个组成部分的创建
调用者(Director),调用具体的创建者来创建对象的各个部分,在调用者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建
适用场景 建造者模式适用于一个具有较多的零件的复杂产品的创建过程,由于需求的变化,组成这个复杂产品的各个零部件经常变化,但他们的组合方式却相对稳定。适用场景包括:
相同的方法,不同的执行顺序,产生不同的结果时
多个部件可以装配到一个对象中,但是产生的结果又不同
产品类非常复杂或者产品类中的调用顺序不同产生不同的作用
当初始化一个对象很复杂,有很多参数且大多有默认值
示例代码 https://github.com/chenpenghui93/tutorial-design-pattern
源码示例 JDK中的StringBuilder,MyBatis中的CacheBuilder、SqlSessionFactoryBuilder
模式优点
封装性好,创建和使用分离
扩展性好,建造类之间独立、一定程度上解耦
模式缺点
产生多余的Builder对象
产品内部发生变化,建造者都要修改,成本较大
建造者模式与工厂模式对比:
建造者模式更加注重方法的调用顺序,工厂模式注重创建对象
创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的都一样
关注重点不一样,建造者模式不仅要创建对象,还要知道对象由哪些部件组成;工厂模式只需要把对象创建出来就可以
建造者模式根据建造过程中的顺序不一样,最终的对象部件组成也不一样
参考