1 实际场景 1.1 订单处理系统 考虑实现一个订单处理系统,为了方便订单的后续处理,系统需要在订单保存的时候,每当订单的预定产品数量超过 1000
时,就需要将订单拆成两份订单来保存,若订单拆分后,数量仍然超过 1000
,则持续拆分,直到每份订单的预定产品数量不超过 1000
。
其中根据当前业务,订单的类型分为两种:一种是个人订单;一种是公司订单;
1.2 不使用设计模式的方案 1、定义订单接口
要实现通用的订单处理,不用关心具体的订单类型,则订单处理的对象应该面向一个订单的接口或者是一个通用的订单对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public interface OrderApi { public int getOrderProductNum () ; public void setOrderProductNum (int num) ; }
2、个人订单
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 41 42 43 44 45 46 47 public class PersonalOrder implements OrderApi { private String customerName; private String productId; private int orderProductNum = 0 ; public int getOrderProductNum () { return this .orderProductNum; } public void setOrderProductNum (int num) { this .orderProductNum = num; } public String getCustomerName () { return customerName; } public void setCustomerName (String customerName) { this .customerName = customerName; } public String getProductId () { return productId; } public void setProductId (String productId) { this .productId = productId; } public String toString () { return "本个人订单的订购人是=" + this .customerName + ",订购产品是=" + this .productId + ",订购数量为=" + this .orderProductNum; } }
3、企业订单
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 41 42 43 44 45 46 47 48 public class EnterpriseOrder { private String enterpriseName; private String productId; private int orderProductNum = 0 ; public int getOrderProductNum () { return this .orderProductNum; } public void setOrderProductNum (int num) { this .orderProductNum = num; } public String getEnterpriseName () { return enterpriseName; } public void setEnterpriseName (String enterpriseName) { this .enterpriseName = enterpriseName; } public String getProductId () { return productId; } public void setProductId (String productId) { this .productId = productId; } public String toString () { return "本企业订单的订购企业是=" + this .enterpriseName + ",订购产品是=" + this .productId + ",订购数量为=" + this .orderProductNum; } }
注:
以上代码主要示意作用,实际场景远比这复杂,为了方便演示,未抽象共同父类
4、实现通用的订单处理
先定义出订单处理的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class OrderBusiness { public void saveOrder (OrderApi order) { } }
实现拆分订单逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class OrderBusiness { public void saveOrder (OrderApi order) { while (order.getOrderProductNum() > 1000 ) { OrderApi newOrder = null ; } } }
其中,拆分订单时,需要将一个订单拆分为多个,则需要新建相应的订单对象,但是由于其中传入的参数是订单接口,则无法确定订单的具体类型,所以无法创建对象,进而无法实现订单的拆分功能。
5、粗暴的方法
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 41 42 43 44 45 46 47 48 49 50 51 52 public class OrderBusiness { public void saveOrder (OrderApi order) { while (order.getOrderProductNum() > 1000 ) { OrderApi newOrder = null ; if (order instanceof PersonalOrder) { PersonalOrder p2 = new PersonalOrder (); PersonalOrder p1 = (PersonalOrder) order; p2.setCustomerName(p1.getCustomerName()); p2.setProductId(p1.getProductId()); p2.setOrderProductNum(1000 ); newOrder = p2; } else if (order instanceof EnterpriseOrder) { EnterpriseOrder e2 = new EnterpriseOrder (); EnterpriseOrder e1 = (EnterpriseOrder) order; e2.setEnterpriseName(e1.getEnterpriseName()); e2.setProductId(e1.getProductId()); e2.setOrderProductNum(1000 ); newOrder = (OrderApi) e2; } order.setOrderProductNum(order.getOrderProductNum() - 1000 ); System.out.println("拆分生成订单==" + newOrder); } System.out.println("订单==" + order); } }
6、客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class OrderClient { public static void main (String[] args) { PersonalOrder op = new PersonalOrder (); op.setOrderProductNum(2925 ); op.setCustomerName("张三" ); op.setProductId("P0001" ); OrderBusiness ob = new OrderBusiness (); ob.saveOrder(op); } }
运行结果:
1 2 3 拆分生成订单==本个人订单的订购人是=张三,订购产品是=P0001,订购数量为=1000 拆分生成订单==本个人订单的订购人是=张三,订购产品是=P0001,订购数量为=1000 订单==本个人订单的订购人是=张三,订购产品是=P0001,订购数量为=925
1.3 存在的问题 在上面的实现中,看似完成了需求且没有关心订单的具体类型。但事实上,在实现订单处理的时候,上面的实现是按照订单的类型和具体实现来处理的,就是 instanceof
的那一段。会导致几个问题:
既然想要实现通用的订单处理,那么对于订单处理的实现对象,是不应该知道订单的具体实现的,更不应该依赖订单的具体实现。 但是上面的实现中,很明显订单处理的对象依赖了订单的具体实现对象。
难以扩展新的订单类型 。假如现在要加入一个大客户专用订单的类型,那么就需要修改订单处理的对象,要在里面添加对新的订单类型的支持,代码无法适配。
2 解决方案 2.1 原型模式 2.1.1 定义 原型模式(Prototype
)是创建型模式:用原型实例指定创建对象的种类,并通过拷贝原型来创建新对象。
2.1.2 原型模式解决方案思路 通过分析上述案例,在方法 saveOrder
中,已经有了订单接口类型的对象实例,是从接口的参数传入的,但是参数封装的是订单的接口类型,并不是具体的实现类型。现在的需求是,需在在 saveOrder
方法中创建订单对象,看起来就像是通过接口的方法来创建。
原型模式就可以解决此类问题:原型模式通过要求对象实现一个可以克隆
自身的接口,这样就可以通过拷贝或者克隆一个实例对象本身,来达到创建一个新的实例。使用时只需要调用克隆
的接口方法,使用上看就像是通过接口来创建了一个新的对象。 如此,通过原型实例创建新的对象,而无需关注这个实例本身的类型,也不用关心对象的具体实现,只要它实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过 new
来创建。
2.1.3 结构说明
Prototype:声明一个克隆自身的接口 ,用来约束想要克隆自己的类,要求它们都要实现这里定义的克隆方法。
ConcretePrototype:实现 Prototype 接口的类 ,这些类真正实现了克隆自身的功能。
Client:使用原型的客户端 ,首先要获取到原型实例对象,然后通过原型实例克隆自身来创建新的对象实例。
2.1.4 实例代码 1、原型接口定义
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface Prototype { Prototype clone () ; }
2、具体原型实现对象
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 public class ConcretePrototype1 implements Prototype { @Override public Prototype clone () { Prototype prototype = new ConcretePrototype1 (); return prototype; } } public class ConcretePrototype2 implements Prototype { @Override public Prototype clone () { Prototype prototype = new ConcretePrototype2 (); return prototype; } }
注:
为了示例方便,这两个具体的原型实现对象,都没有定义属性。事实上,在实际使用原型模式的应用中,原型对象多是有属性的,克隆原型的时候也是需要克隆原型对象的属性 。
3、客户端
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 public class Client { private Prototype prototype; public Client (Prototype prototype) { this .prototype = prototype; } public void operation () { Prototype newPrototype = prototype.clone(); } }
2.2 重写方案 使用原型模式来重写示例,先要在订单的接口上定义出克隆的接口,然后要求各个具体的订单对象克隆自身,这样就可以解决:在订单处理对象里面通过订单接口来创建新的订单对象的问题。
通过接口来强制要求对象有复制的方法,利用这个方法实现对象对自己的复制。
1、订单接口
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 public interface OrderApi { int getOrderProductNum () ; void setOrderProductNum (int num) ; OrderApi cloneOrder () ; }
注:当前例子中,已经有订单的接口存在,就无需重新创建一个原型接口来声明克隆接口;
2、订单对象定义
克隆拷贝对象时要求将数据也要复制
个人订单:
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class PersonalOrder implements OrderApi { private String customerName; private String productId; private int orderProductNum = 0 ; public int getOrderProductNum () { return this .orderProductNum; } public void setOrderProductNum (int num) { this .orderProductNum = num; } public String getCustomerName () { return customerName; } public void setCustomerName (String customerName) { this .customerName = customerName; } public String getProductId () { return productId; } public void setProductId (String productId) { this .productId = productId; } public String toString () { return "本个人订单的订购人是=" + this .customerName + ",订购产品是=" + this .productId + ",订购数量为=" + this .orderProductNum; } @Override public OrderApi cloneOrder () { PersonalOrder order = new PersonalOrder (); order.setCustomerName(this .customerName); order.setProductId(this .productId); order.setOrderProductNum(this .orderProductNum); return order; } }
企业订单:
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class EnterpriseOrder implements OrderApi { private String enterpriseName; private String productId; private int orderProductNum = 0 ; public int getOrderProductNum () { return this .orderProductNum; } public void setOrderProductNum (int num) { this .orderProductNum = num; } public String getEnterpriseName () { return enterpriseName; } public void setEnterpriseName (String enterpriseName) { this .enterpriseName = enterpriseName; } public String getProductId () { return productId; } public void setProductId (String productId) { this .productId = productId; } public String toString () { return "本企业订单的订购企业是=" + this .enterpriseName + ",订购产品是=" + this .productId + ",订购数量为=" + this .orderProductNum; } @Override public OrderApi cloneOrder () { EnterpriseOrder order = new EnterpriseOrder (); order.setEnterpriseName(this .enterpriseName); order.setProductId(this .productId); order.setOrderProductNum(this .orderProductNum); return order; } }
3、订单处理对象
这里使用订单接口的克隆方法,是订单的处理对象,也就是说,订单的处理对象就相当于原型模式结构中的 Client
。
当然,客户端在调用 clone
方法之前,还需要先获得相应的实例对象,有了实例对象,才能调用该实例对象的 clone
方法。
这里使用克隆方法,跟标准的原型实现有一些不同,在标准的原型实现的示例代码里面,客户端是持有需要克隆的对象,而这里是通过方法传入需要使用克隆的对象 ,
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 public class OrderBusiness { public void saveOrder (OrderApi order) { while (order.getOrderProductNum() > 1000 ) { OrderApi newOrder = order.cloneOrder(); newOrder.setOrderProductNum(1000 ); order.setOrderProductNum(order.getOrderProductNum() - 1000 ); System.out.println("拆分生成订单==" + newOrder); } System.out.println("订单==" + order); } }
注:
虽然 Java
里面有 clone
方法,上面这么做还是很有意义的,可以让我们更好的、更完整的体会原型这个设计模式。关于 Java
的 clone
方法,后续会讲解。
3 模式讲解 3.1 认识模型模式 模型模式的功能实际包含两个功能:
通过克隆(clone)
的方法来创建新的对象实例;
对克隆出来的新对象实例复制原实例属性的值;
一般来讲,新创建出来的实例的数据是和原型实例一样的 。但是具体如何实现克隆,需要由代码自行实现,原型模式并没有统一的要求和实现算法。
克隆(clone)
与 new
对象的区别:
克隆类似于 new
,但是不等于 new
;
new
一个对象实例,一般属性是没有值的,或者是只有默认值;
如果是克隆得到的一个实例,通常属性是有值的,属性的值就是原型对象实例在克隆的时候,原型对象实例的属性的值 。
所以,通过原型模式生成的新对象实例,实际上与原来的对象实例是没有关联的,即,对象实例在不同的内存空间。
3.2 Java 中的拷贝 Java
中已经提供了 clone
的接口方法,定义在 Object
类中,利用 Java
的 clone
方法来实现上面的案例。
1、订单接口定义
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 public interface OrderApi { int getOrderProductNum () ; void setOrderProductNum (int num) ; }
2、实现 java.lang.Cloneable
接口的订单对象,这里就实现一个类作为示例
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public class PersonalOrder implements OrderApi , Cloneable { private String customerName; private String productId; private int orderProductNum = 0 ; public int getOrderProductNum () { return this .orderProductNum; } public void setOrderProductNum (int num) { this .orderProductNum = num; } public String getCustomerName () { return customerName; } public void setCustomerName (String customerName) { this .customerName = customerName; } public String getProductId () { return productId; } public void setProductId (String productId) { this .productId = productId; } public String toString () { return "本个人订单的订购人是=" + this .customerName + ",订购产品是=" + this .productId + ",订购数量为=" + this .orderProductNum; } @Override protected Object clone () throws CloneNotSupportedException { return super .clone(); } }
3、客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Client { public static void main (String[] args) throws CloneNotSupportedException { PersonalOrder oa1 = new PersonalOrder (); oa1.setOrderProductNum(100 ); System.out.println("这是第一次获取的对象实例===" + oa1.getOrderProductNum()); PersonalOrder oa2 = (PersonalOrder) oa1.clone(); oa2.setOrderProductNum(80 ); System.out.println("输出克隆出来的实例===" + oa2.getOrderProductNum()); System.out.println("再次输出原型实例===" + oa1.getOrderProductNum()); } }
运行结果:
1 2 3 这是第一次获取的对象实例===100 输出克隆出来的实例===80 再次输出原型实例===100
3.3 浅拷贝与深拷贝 前面提到了 clone
,正好引申一下浅拷贝和深拷贝的概念和区别。
浅拷贝:只负责拷贝按值传递的数据(比如:基本数据类型、String
类型)
深拷贝:除了浅拷贝要拷贝的值外,还负责拷贝引用类型的数据,基本上就是被拷贝实例所有的属性的数据都会被克隆出来
注:深拷贝还有一个特点,如果被克隆的对象里面的属性数据是引用类型,也就是属性的类型也是对象,那么需要一直递归的克隆下去。 这也意味着,要想深拷贝成功,必须要整个克隆所涉及的对象都要正确实现克隆方法,如果其中有一个没有正确实现克隆,那么就会导致深拷贝失败。
上一小结使用的就是浅拷贝。
深拷贝案例 1 不使用 Java 的 Cloneble 方式 继续上面的案例,实现一个引用对象给订单对象
1、订单对象的属性所引用的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface ProductPrototype { ProductPrototype cloneProduct () ; }
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 41 42 43 public class Product implements ProductPrototype { private String productId; private String name; public String getName () { return name; } public void setName (String name) { this .name = name; } public String getProductId () { return productId; } public void setProductId (String productId) { this .productId = productId; } public String toString () { return "产品编号=" + this .productId + ",产品名称=" + this .name; } public ProductPrototype cloneProduct () { Product product = new Product (); product.setProductId(this .productId); product.setName(this .name); return product; } }
2、新的订单对象实现,增加的上面实现的引用对象
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 41 42 43 44 45 46 47 48 49 50 51 52 public class PersonalOrder implements OrderApi { private String customerName; private int orderProductNum = 0 ; private Product product = null ; public int getOrderProductNum () { return this .orderProductNum; } public void setOrderProductNum (int num) { this .orderProductNum = num; } public String getCustomerName () { return customerName; } public void setCustomerName (String customerName) { this .customerName = customerName; } public Product getProduct () { return product; } public void setProduct (Product product) { this .product = product; } public String toString () { return "订购产品是=" + this .product.getName() + ",订购数量为=" + this .orderProductNum; } public OrderApi cloneOrder () { PersonalOrder order = new PersonalOrder (); order.setCustomerName(this .customerName); order.setOrderProductNum(this .orderProductNum); order.setProduct((Product) this .product.cloneProduct()); return order; } }
3、客户端代码 - 验证深拷贝
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 public class Client { public static void main (String[] args) { PersonalOrder oa1 = new PersonalOrder (); Product product = new Product (); product.setName("产品1" ); oa1.setProduct(product); oa1.setOrderProductNum(100 ); System.out.println("这是第一次获取的对象实例=" + oa1); PersonalOrder oa2 = (PersonalOrder) oa1.cloneOrder(); oa2.getProduct().setName("产品2" ); oa2.setOrderProductNum(80 ); System.out.println("输出克隆出来的实例=" + oa2); System.out.println("再次输出原型实例=" + oa1); } }
运行结果:
1 2 3 这是第一次获取的对象实例=订购产品是=产品1 ,订购数量为=100 输出克隆出来的实例=订购产品是=产品2 ,订购数量为=80 再次输出原型实例=订购产品是=产品1 ,订购数量为=100
深拷贝的实现不复杂,但是比较麻烦,比如多个对象的属性都是引用类型的话,需要循环去实现为 clone
方法,并且每个都要正确实现此方法,如果有一个遗漏,都会引起深拷贝的报错。
2 使用 Java 的 Cloneable 方式 1、新增产品类的实现,主要将 ProductPrototype
接口改成 Cloneable
接口实现
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 public class Product implements Cloneable { private String productId; private String name; public String getName () { return name; } public void setName (String name) { this .name = name; } public String getProductId () { return productId; } public void setProductId (String productId) { this .productId = productId; } public String toString () { return "产品编号=" + this .productId + ",产品名称=" + this .name; } public Object clone () { Object obj = null ; try { obj = super .clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return obj; } }
2、订单类的实现
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 41 42 43 44 45 46 47 48 49 50 51 public class PersonalOrder implements OrderApi , Cloneable { private String customerName; private Product product = null ; private int orderProductNum = 0 ; public int getOrderProductNum () { return this .orderProductNum; } public void setOrderProductNum (int num) { this .orderProductNum = num; } public String getCustomerName () { return customerName; } public void setCustomerName (String customerName) { this .customerName = customerName; } public Product getProduct () { return product; } public void setProduct (Product product) { this .product = product; } public String toString () { return "订购产品是=" + this .product.getName() + ",订购数量为=" + this .orderProductNum; } public Object clone () { PersonalOrder obj = null ; try { obj = (PersonalOrder) super .clone(); obj.setProduct((Product) this .product.clone()); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return obj; } }
特别注意:
不可缺少 obj.setProduct((Product)this.product.clone());
原因在于调用 super.clone()
方法的时候,Java
是先开辟一块内存的空间,然后把实例对象的值原样拷贝过去,对于基本数据类型这样做是没有问题的,而属性 product
是一个引用类型,把值拷贝过去的意思就是把对应的内存地址拷贝过去了,也就是说克隆后的对象实例的 product
和原型对象实例的 product
指向的是同一块内存空间,是同一个产品实例。
因此要想正确的执行深度拷贝,必须手工的对每一个引用类型的属性进行克隆,并重新设置,覆盖掉 super.clone()
所拷贝的值 。
3.4 原型管理器 有了原型管理器后,一般情况下,除了向原型管理器里面添加原型对象的时候是通过 new
来创造的对象,其余时候都是通过向原型管理器来请求原型实例,然后通过克隆方法来获取新的对象实例,这就可以实现动态管理、或者动态切换具体的实现对象实例。
在很多实际应用场景中,系统的原型数据是不固定的,其中原型是可以被动态的创建和销毁,这种情况下,就需要在系统中维护一个当前可用的原型注册表,这个注册表称为原型管理器 。
若原型是一个资源的话, 此时原型管理器就相当于是一个资源管理器,系统启动时,管理器就初始化,然后运行期间可以动态的添加资源和销毁资源,类似于一个资源缓存池。
案例说明 1、定义原型接口
1 2 3 4 5 6 7 8 9 10 11 12 public interface Prototype { Prototype clone () ; String getName () ; void setName (String name) ; }
2、原型实现
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 public class ConcretePrototype1 implements Prototype { private String name; @Override public Prototype clone () { ConcretePrototype1 prototype = new ConcretePrototype1 (); prototype.setName(this .name); return prototype; } @Override public String getName () { return name; } @Override public void setName (String name) { this .name = name; } @Override public String toString () { return "Now in Prototype1,name=" + name; } }
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 public class ConcretePrototype2 implements Prototype { private String name; @Override public Prototype clone () { ConcretePrototype2 prototype2 = new ConcretePrototype2 (); prototype2.setName(this .name); return prototype2; } @Override public String getName () { return name; } @Override public void setName (String name) { this .name = name; } @Override public String toString () { return "Now in Prototype2,name=" + name; } }
3、原型管理器
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 41 42 43 44 45 46 47 48 49 50 51 52 public class PrototypeManager { private static Map<String, Prototype> map = new HashMap <String, Prototype>(); private PrototypeManager () { } public synchronized static void setPrototype (String prototypeId, Prototype prototype) { map.put(prototypeId, prototype); } public synchronized static void removePrototype (String prototypeId) { map.remove(prototypeId); } public synchronized static Prototype getPrototype (String prototypeId) throws Exception { Prototype prototype = map.get(prototypeId); if (prototype == null ) { throw new Exception ("您希望获取的原型还没有注册或已被销毁" ); } return prototype; } }
原型管理器是类似一个工具类的实现方式,而且对外的几个方法都是加了同步的,这主要是因为如果在多线程环境下使用这个原型管理器的话,map
属性容易成为竞争的资源,因此需要加上同步 。
4、客户端
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 public class Client { public static void main (String[] args) { try { Prototype p1 = new ConcretePrototype1 (); PrototypeManager.setPrototype("Prototype1" , p1); Prototype p3 = PrototypeManager.getPrototype("Prototype1" ).clone(); p3.setName("张三" ); System.out.println("第一个实例:" + p3); Prototype p2 = new ConcretePrototype2 (); PrototypeManager.setPrototype("Prototype1" , p2); Prototype p4 = PrototypeManager.getPrototype("Prototype1" ).clone(); p4.setName("李四" ); System.out.println("第二个实例:" + p4); PrototypeManager.removePrototype("Prototype1" ); Prototype p5 = PrototypeManager.getPrototype("Prototype1" ).clone(); p5.setName("王五" ); System.out.println("第三个实例:" + p5); } catch (Exception err) { System.err.println(err.getMessage()); } } }
运行结果:
1 2 3 第一个实例:Now in Prototype1,name=张三 第二个实例:Now in Prototype2,name=李四 您希望获取的原型还没有注册或已被销毁
3.5 原型模式的优缺点 1、对客户端隐藏具体的实现类型 原型模式的客户端,只知道原型接口的类型,并不知道具体的实现类型,从而减少了客户端对这些具体实现类型的依赖。
2、在运行时动态改变具体的实现类型 原型模式可以在运行期间,有客户端来注册符合原型接口实现类型,也可同台的改变具体的实现类型,看起来接口没有任何变化,但是其实运行的已经是另外一个类的实例了,因为克隆一个原型就类似于实例化一个类。
3、深拷贝实现比较麻烦 原型模式最大的缺点就在于每个原型的子类都必须实现 clone
的操作,尤其在包含引用类型的对象时,clone
方法会比较麻烦,必须要能够递归的让所有的相关对象都要正确的实现克隆。
3.6 思考 1、原型模式的本质 本质:克隆生成对象,克隆是手段,目的还是生成新的对象实例。
原型模式也可以用来解决 “只知接口而不知实现的问题”,使用原型模式,可以出现一种独特的 “接口造接口” 的景象,这在面向接口编程中很有用。同样的功能也可以考虑使用工厂来实现。
需要注意的是,原型模式的重心还是在创建新的对象实例 ,至于创建出来的对象,其属性的值是否一定要和原型对象属性的值完全一样,这个并没有强制规定,在大多数实现中,克隆出来的对象和原型对象的属性值是一样的。
2、使用场景
当一个系统想要独立于它想要使用的对象时,可以使用原型模式,系统只需要面向接口编程,在系统需要新的对象时,通过克隆原型来得到;
当需要实例化的类是在运行时刻动态指定时
3.7 相关模式 1、原型模式和抽象工厂模式 功能上有些相似,都是用来获取一个新的对象实例。不同之处在于:
原型模式主要关注如何创建出示例对象,选择的方案是克隆;
抽象模式主要关注的如何创建产品簇,并不关注具体如何创建出产品簇中的每个对象实例;
2、原型模式和生成器模式 两种模式可以配合使用。生成器模式关注的是构建的过程,在构建过程中,可能需要某个部件的实例,此时可以搭配原型模式使用,通过原型模式来得到部件的实例。