原型模式小试

同为创建型模式的原型模式与单例模式是密不可分的,这也是最常用的设计模式之一。

原型模式是一种非常简单的设计模式。这里除了基本介绍和演示,还详细介绍了Java中原型模式的本质。

一、介绍

  同样,先来看一下《研磨设计模式》的定义——用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

  原型模式的本质——克隆生成对象。

  那么原型模式是什么意思呢?说白了就是克隆自身。我们知道Java中没有引用这个概念,Java用变量名代表引用。像 Apple a =
new Apple();

我们知道,想要操作这个Apple对象,那么就是去操作"a"这个变量名,实质就是去操作“a”这个引用所指向的内存地址上的Apple对象。通常而言,赋值操作符“=”的本质就是将“=”右边的地址赋值给左边的引用。如果我们希望创建一个跟这个对象a一样的Apple对象,同时在操作这个新对象的时候,对象a无任何影响。Java新手可能下意识觉得使用Apple
b = a;
很明显a和b指向的是同一片内存空间。

  原型模式就是为了解决这样的对象复制的问题。

  Java中的原型模式实现起来其实很简单,在对象的接口中添加一个复制自身的抽象方法,然后对象实现这个方法,复制自身即可。使用的时候直接调用接口方法即可。

二、我的实现

1、我们有一个水果接口,如下:


1 public interface Fruit {
2
3 public double getPrice();
4
5 public void setPrice(double price);
6
7 //克隆接口
8 public Fruit cloneFruit();
9 }

2、一个简单实现类:


 1 public class Apple implements Fruit,Cloneable {
2
3 // 价格
4 private double price;
5 // 平均尺寸
6 private double avgSize;
7 // 产地
8 private String productionArea;
9
10 public Apple(double price, double avgSize, String productionArea) {
11 super();
12 this.price = price;
13 this.avgSize = avgSize;
14 this.productionArea = productionArea;
15 }
16
17 public double getAvgSize() {
18 return avgSize;
19 }
20
21 public void setAvgSize(double avgSize) {
22 this.avgSize = avgSize;
23 }
24
25 public String getProductionArea() {
26 return productionArea;
27 }
28
29 public void setProductionArea(String productionArea) {
30 this.productionArea = productionArea;
31 }
32
33 @Override
34 public void setPrice(double price) {
35 // TODO Auto-generated method stub
36 this.price = price;
37 }
38
39 @Override
40 public double getPrice() {
41 // TODO Auto-generated method stub
42 return price;
43 }
44
45 @Override
46 public String toString() {
47 return "Apple [avgSize=" + avgSize + ", price=" + price
48 + ", productionArea=" + productionArea + "]";
49 }
50
51 //克隆自身的实现
52 @Override
53 public Fruit cloneFruit()
54 {
55 return new Apple(price, avgSize, productionArea);
56 }
57
58 }

3、然后就可以测试了,如下:


 1 package prototype.myPrototype;
2
3 public class Client {
4
5 public static void main(String[] args)
6 {
7 Fruit fruit = new Apple(1,2,"红富士");
8 System.out.println(fruit);
9
10 //根据原对象克隆
11 Fruit cloneFruit = fruit.cloneFruit();
12 System.out.println(cloneFruit);
13 System.out.println("两个水果是同一个吗?"+(fruit==cloneFruit));
14
15 }
16 }

4、结果如下:

Apple [avgSize=2.0, price=1.0, productionArea=红富士]
Apple [avgSize=2.0, price=1.0, productionArea=红富士]
两个水果是同一个吗?false

如上,简单的实现了原型模式。也实现了接口与实现分离的克隆。在完全不知道对象类型的情况下完成了复制。

三、Java的原型模式(Object的clone()方法)

(1)、介绍

  Object类有一个clone()方法,这是java为实现原型模式准备的。

  要实现Java的克隆方法,要满足三个条件:

  1、调用对象实现了cloneable接口

  2、调用对象重写了public Object
clone();方法。

  3、重写clone()时,调用super.clone();

  我们来看一下clone方法的原型

  protected native java.lang.Object clone()
throws java.lang.CloneNotSupportedException

  我们可以看到,这个方法是protected访问控制,同时是一个本地方法。也就是说,任何Object的子类都可以调用这个方法,但是Object对象自身不能调用这个方法。

  这个方法执行的时候,是使用RTTI(run-time type
identification)的机制,动态得找到目前正在调用clone方法的那个reference,根据它的大小申请内存空间,然后进行bitwise的复制,将该对象的内存空间完全复制到新的空间中去,从而达到shallowcopy的目的。(这句话摘自百度知道)

(2)、问题:

  1、为什么调用对象要实现cloneable接口?

  因为clone方法执行的时候,会判断当前对象是否实现了Cloneable这个标识接口,如果没有实现,就抛出CloneNotSupportedException异常。

  2、为什么调用对象要重写public Object
clone();方法,为什么要调用super.clone()?

  如果调用对象不重写public Object
clone()方法,那么clone()方法都不会显示出来。调用clone()方法,直接编译错误。可是clone()方法不是protected的吗?

  我们来看一下clone();方法的API:

By convention, the returned object should be obtained by calling super.clone. If a class and all of its superclasses (except Object) obey this convention,
it will be the case that x.clone().getClass() == x.getClass().
--按照惯例,返回的对象应该通过调用 super.clone 获得。如果一个类及其所有的超类(Object 除外)都遵守此约定,则 x.clone().getClass() == x.getClass()

  因为只有重写clone()方法,我们才能通过使用super.clone(),才能真正调用Object类的本地方法clone();

  从这里我们可以看到,clone()方法是很特殊的。不重写,会编译错误,应该涉及到编译器的优化。

(3)、实现:  

我们来Java的原型模式简单改一下上面的示例:

1、将Fruit接口中的克隆功能去掉,这里不列出了。

2、Apple类实现Fruit接口,也实现Cloneable接口,并重写clone()方法,如下:


 1 public class Apple implements Fruit, Cloneable {
2
3 // 价格
4 private double price;
5 // 平均尺寸
6 private double avgSize;
7 // 产地
8 private String productionArea;
9
10 public Apple(double price, double avgSize, String productionArea)
11 {
12 super();
13 this.price = price;
14 this.avgSize = avgSize;
15 this.productionArea = productionArea;
16 }
17
18 public double getAvgSize()
19 {
20 return avgSize;
21 }
22
23 public void setAvgSize(double avgSize)
24 {
25 this.avgSize = avgSize;
26 }
27
28 public String getProductionArea()
29 {
30 return productionArea;
31 }
32
33 public void setProductionArea(String productionArea)
34 {
35 this.productionArea = productionArea;
36 }
37
38 @Override
39 public void setPrice(double price)
40 {
41 // TODO Auto-generated method stub
42 this.price = price;
43 }
44
45 @Override
46 public double getPrice()
47 {
48 // TODO Auto-generated method stub
49 return price;
50 }
51
52 // 重写了Object类的clone()方法
53 @Override
54 public Object clone()
55 {
56 // TODO Auto-generated method stub
57 Object cloneApple = null;
58 // 直接调用父类的克隆方法即可
59 try
60 {
61 cloneApple = super.clone();
62 } catch (CloneNotSupportedException e)
63 {
64 e.printStackTrace();
65 }
66 return cloneApple;
67 }
68
69 @Override
70 public String toString()
71 {
72 return "Apple [avgSize=" + avgSize + ", price=" + price + ", productionArea=" + productionArea + "]";
73 }
74
75 }

3、测试一下:


 1 public class Client {
2
3 static Fruit fruit = new Apple(90, 5, "新疆");
4
5 public static void main(String[] args) {
6 System.out.println("Fruit:" + fruit);
7 Fruit newFruit = (Fruit) ((Apple)fruit).clone();
8 System.out.println("new Fruit : " + newFruit);
9 System.out.println("前后是否为同一对象?" + (fruit == newFruit));
10 }
11 }

4、结果:

Fruit:Apple [avgSize=5.0, price=90.0, productionArea=新疆]
new Fruit : Apple [avgSize=5.0, price=90.0, productionArea=新疆]
前后是否为同一对象?false

个人认为,方便而言,还不如自己手动实现原型模式。这里再补充一点:

我们来看,static Fruit fruit = new Apple(90, 5, "新疆");

对于对象fruit,它的编译时类型是Fruit,运行时类型是Apple,所以fruit对象只能显式调用Fruit接口所有的方法。而这其中包含了Object类除了clone()方法之外的所有方法。Java中,接口和类是一个并列的概念

我们可以认为接口是一种特殊的类,它默认实现了Object中除了clone()之外的像toString()、getClass等一系列Object方法。

四、深度克隆和浅度克隆

  克隆分为深度克隆和浅度克隆。假设我们现在有一个对象,这个对象有一个引用属性是另一个对象。我们知道,引用属性的本质就是一个内存地址。按照上文的复制,我们会将复制对象的引用属性也一同复制,这样一来,复制对象和被复制对象的该属性都指向同一内存地址了。这就违背了我们克隆的初衷。而这个就是浅度克隆。

  相对的,深度克隆就是克隆之后,复制对象和被复制对象是完全不想干的。即对于对象的引用属性,我们重新克隆一次。

  下面是Java克隆、深度克隆的简单示例:

1、首先是一个包含刚才Fruit类的Businessman对象:


 1 public class Businessman implements Cloneable {
2
3 private String name;
4 private Fruit fruit;
5
6 @Override
7 public String toString()
8 {
9 return "Businessman [name=" + name + ", fruit=" + fruit + "]";
10 }
11
12 public Businessman(String name, Fruit fruit)
13 {
14 super();
15 this.name = name;
16 this.fruit = fruit;
17 }
18
19 public String getName()
20 {
21 return name;
22 }
23
24 public void setName(String name)
25 {
26 this.name = name;
27 }
28
29 public Fruit getFruit()
30 {
31 return fruit;
32 }
33
34 public void setFruit(Fruit fruit)
35 {
36 this.fruit = fruit;
37 }
38 //克隆方法
39 public Object clone(){
40 //先克隆一下引用变量
41 Fruit f = (Fruit) ((Apple)fruit).clone();
42 Businessman man = new Businessman(name,f);
43 return man;
44 }
45 }

2、测试一下:


 1 package prototype;
2
3 public class Client {
4
5 public static void main(String[] args) {
6 //水果类
7 Fruit fruit = new Apple(90, 5, "新疆");
8 //商人
9 Businessman man1 = new Businessman("张三",fruit);
10
11 System.out.println("man1:" + man1);
12 //克隆
13 Businessman man2 = (Businessman) man1.clone();
14
15 System.out.println("man2 : " + man2);
16 System.out.println("man1和man2前后是否为同一对象?" + (man1 == man2));
17 System.out.println("两者的属性呢?"+(man1.getFruit()==man2.getFruit()));
18 }
19 }

3、结果如下:

man1:Businessman [name=张三, fruit=Apple [avgSize=5.0, price=90.0, productionArea=新疆]]
man2 : Businessman [name=张三, fruit=Apple [avgSize=5.0, price=90.0, productionArea=新疆]]
man1和man2前后是否为同一对象?false
两者的属性呢?false

原型模式小试

时间: 2024-11-05 14:45:48

原型模式小试的相关文章

设计模式之原型模式(Prototype)

1.初识原型模式 大家都知道连锁机构是现在灰常流行的商业模式,比如咖啡之翼,那么假设咖啡之翼要在长春新建立一个分店,所经营的产品和以前在其他的城市已经存在的店经营的产品差不多,那么面向对象开发的角度怎么解决这个问题呢?难道要重新的实例化一个咖啡之翼的店??这显然不太好吧,咖啡之翼里面经营的产品(假设是属性吧)都需要重新写,这就是在做大量的重复工作啊,这显然是不符合OO开发思想的.遇到这样的情况,并不是重新建立一个类来解决这样的问题,而是通过设计模式中的"原型模式"来解决这种问题.是这种

设计模式学习笔记(十三:原型模式)

1.1概述 用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象.这就是原型模式的定义. 在某些情况下,可能不希望反复使用类的构造方法创建许多对象,而是希望使用该类创建一个对象后,以该对象为原型得到该对象的若干个复制品.也就是说,将一个对象定义为原型对象,要求改原型对象提供一个方法,使该原型对象调用此方法可以复制一个和自己有完全相同状态的同类型对象,即该方法"克隆"原型对象得到一个新对象.原型对象和以它为原型"克隆"出的新对象可以分别独立地变化,也就是说,

原型模式

1.与工厂模式类似,都是创建对象的 2与工厂模式不同,原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象,这样就免去了类创建时重复的初始化操作 3.比较适合用于大对象的创建 4.原型模式.解决对象的重复利用,比如一个大对象很多成员有默认参数初始化他们需要很大的时间片,显然创建一个初始化一次是一种浪费, 原型模式就解决了这个问题,php 的clone 是直接吧那片内存拷贝过来,省去了很多时间 例子 假设init里面循环200次为成员赋值 传统编程方法: 原型模式:

设计模式--原型模式C++实现

原型模式C++实现 1定义 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 2类图 3实现 class Prototype { protected: Prototype(); public: virtual Prototype* Clone() const = 0; vitual ~Prototype() = 0; }; class PrototypeClass:public Prototype { public: PrototypeClass(); PrototypeClass

大话设计模式笔记 原型模式

原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的细节. 基本类型与引用类型的区别于关系在efficient java. http://www.cnblogs.com/linkarl/p/4785329.html 浅复制:对象的引用仍然指向原来的对象 深复制:把引用对象的变量指向复制过的新对象 我认为一般深复制比较常用. package prototype; public

设计模式(四) 原型模式(Prototype)

1.定义 原型模式属于一种创建型模式,与其他创建型模式不同,原型模式不是直接构造对象,而是通过复制一个已经存在的实例返回新的实例. 2.适用性 为何要拷贝而不直接生成?我的理解是有些时候直接构造实例花费比较大,比如在构造对象的时候需要做大量的数据库查询,这样如果构造许多类似的对象还重复地查询数据库则开销很大,很没效率.直接拷贝现有的实例,在需要情况下做一些小的修改会显得高效许多. 3.结构 Prototype: 声明一个克隆自身的接口 ConcretePrototype:实现一个克隆自身的操作

设计模式--原型模式

原型模式 Prototype Pattern 意图:使用原型实例定义/指定被创建对象种类/类型,通过拷贝原型实例创建新对象 应用场景: 1)当某个类指定在运行时实例化 2)减少子类数量 3)降低昂贵类型新对象创建成本 好处或缺点: 1)clone方法可能实现比较困难 2)不适用于含循环引用的类结构 类结构: 参与者:client ,Prototype,ConcretePrototype prototype:定义原型的行为 //关键代码 class Notification implements

《JAVA与模式》之原型模式

在阎宏博士的<JAVA与模式>一书中开头是这样描述原型(Prototype)模式的: 原型模式属于对象的创建模式.通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象.这就是选型模式的用意. 原型模式的结构 原型模式要求对象实现一个可以"克隆"自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例.这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新

设计模式之原型模式

原型模式(prototype)为创建型对象模式,它是用原型实例来指定创建对象的类型的,并通过拷贝这些原型来创建新的对象.也就是说,这次我们创建新的对象和以前创建对象的方法有些不同,以前创建新的对象是用new方法作用于类上来实现的,现在我们不这样做了,给定一个类的实例,我们通过克隆这个类的实例来创建新的实例,显然,这个类本身要有能够实现"克隆"机制的方法才行,这样用该类创建出的对象才能拥有"克隆"自己的能力.通过查看java API文档可知,Java语言本身具有克隆对