9.2 解 决 方 案

9.2.1 使用原型模式来解决问题

用来解决上述问题的一个合理的解决方案就是原型模式(Prototype)。那么什么是原型模式呢?

1.原型模式的定义

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

2.应用原型模式来解决问题的思路

仔细分析上面的问题,在saveOrder方法里面,已经有了订单接口类型的对象实例,是从外部传入的,但是这里只是知道这个实例对象的种类是订单的接口类型,并不知道其具体的实现类型,也就是不知道它到底是个人订单还是企业订单,但是现在需要在这个方法里面创建一个这样的订单对象,看起来就像是要通过接口来创建对象一样。

原型模式就可以解决这样的问题。原型模式会要求对象实现一个可以“克隆”自身的接口,这样就可以通过拷贝或者是克隆一个实例对象本身来创建一个新的实例。如果把这个方法定义在接口上,看起来就像是通过接口来创建了新的接口对象。

这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,也不关心它的具体实现,只要它实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。

9.2.2 原型模式的结构和说明

原型模式的结构如图9.1所示。

图片

图9.1 原型模式结构示意图

■ Prototype:声明一个克隆自身的接口,用来约束想要克隆自己的类,要求它们都要实现这里定义的克隆方法。

■ ConcretePrototype:实现Prototype接口的类,这些类真正实现了克隆自身的功能。

■ Client:使用原型的客户端,首先要获取到原型实例对象,然后通过原型实例克隆自身来创建新的对象实例。

9.2.3 原型模式示例代码

(1)先来看看原型接口的定义。示例代码如下。

141d10dc0d0e4eac803239dac6e2cb16

(2)接下来看看具体的原型实现对象。示例代码如下:

7e4923d0207b49aab8cb890a0fba5bd4

e6bca8bf17c24774b24cb6db0f40b0a2

为了跟上面原型模式的结构示意图保持一致,因此这两个具体的原型实现对象。都没有定义属性。事实上,在实际使用原型模式的应用中,原型对象多是有属性的,克隆原型的时候也是需要克隆原型对象的属性的,特此说明一下。

(3)再看看使用原型的客户端。示例代码如下:

0311ca0f4ee6430ea7b25ea950c02ef9

2ba588ee612b4570b66992cd28a369ec

9.2.4 使用原型模式重写示例

要使用原型模式来重写示例,先要在订单的接口上定义出克隆的接口,然后要求各个具体的订单对象克隆自身,这样就可以解决:在订单处理对象里面通过订单接口来创建新的订单对象的问题。

使用原型模式来重写示例的结构如图9.2所示:

图片

图9.2 使用原型模式来重写示例的结构示意图

下面一起来看看具体的实现。

1.复制谁和谁来复制的问题

有了一个对象实例,要快速地创建和它一样的实例,最简单的办法就是复制?这里又有两个小的问题:

■ 复制谁呢?当然是复制这个对象实例,复制实例的意思是连带着数据一起复制。

■ 谁来复制呢?应该让这个类的实例自己来复制,自己复制自己。

可是每个对象不会那么听话,自己去实现复制自己的。于是原型模式决定对这些对象实行强制要求,给这些对象定义一个接口,在接口里面定义一个方法,这个方法用来要求每个对象实现自己复制自己。

由于现在存在订单的接口,因此就把这个要求克隆自身的方法定义在订单的接口里面。示例代码如下:

883946ec46dd4d178b8cb5737e9ac588

15f82006b23b405fa52181d068c0e8a7

2.如何克隆

定义好了克隆的接口,那么在订单的实现类里面,就得让它实现这个接口,并具体地实现这个克隆方法。新的问题出来了,如何实现克隆呢?

很简单,只要先new一个自己对象的实例,然后把自己实例中的数据取出来,设置到新的对象实例中去,就可以完成实例的复制,复制的结果就是有了一个同自身一模一样的实例。

有的朋友可能会说:不用那么费劲吧,直接返回本实例不就可以了?例如:

4236d3a5fb40454f877444a44c3c1867

注意

请注意,这是一种典型的错误,这么做,每次克隆,客户端获取的其实都是同一个实例,都是指向同一个内存空间的,对克隆出来的对象实例的修改会影响到原型对象实例。

那么应该怎么克隆呢?最基本的做法就是新建一个类实例,然后把所有属性的值复制到新的实例中。

先看看个人订单对象的实现。示例代码如下:

a56ff23985ce4e55859a94622e0bcf5e

7e89fcb53c3c4638bb071741299becd5

接下来看看企业订单的具体实现。示例代码如下:

072dc8161abf4ebeb00d6bdee56477cb

381a79ab7f1b4f8fbfebac643d5f228c

3.使用克隆方法

这里使用订单接口的克隆方法的是订单的处理对象,也就是说,订单的处理对象就相当于原型模式结构中的Client。

当然,客户端在调用clone方法之前,还需要先获得相应的实例对象,有了实例对象,才能调用该实例对象的clone方法。

注意

 这里使用克隆方法的时候,和标准的原型实现有一些不同,在标准的原型实现的示例代码里面,客户端是持有需要克隆的对象,而这里变化成了通过方法传入需要使用克隆的对象,这点大家注意一下。

示例代码如下:

2f5dec193aa344b8a9e84240ba43b590

20d5b64ce83245bf878d74043ba741b2

客户端的测试代码和前面的示例是完全一样的,这里就不再赘述。运行一下,看看运行的效果,享受一下克隆的乐趣。

在上面的例子中,在订单处理对象的保存订单方法里面的这句话“OrderApi newOrder=order.cloneOrder();”,就用一个订单的原型实例来指定了对象的种类,然后通过克隆这个原型实例来创建出了一个新的对象实例。

看到这里,可能有些朋友会认为:Java的Object里面本身就有clone方法,还用搞得这么麻烦吗?

虽然Java里面有clone方法,上面这么做还是很有意义的,可以更好地、更完整地体会原型设计模式。当然,后面会讲述如何使用Java里面的clone方法来实现克隆。