博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
原型模式小试
阅读量:7097 次
发布时间:2019-06-28

本文共 8738 字,大约阅读时间需要 29 分钟。

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

原型模式是一种非常简单的设计模式。这里除了基本介绍和演示,还详细介绍了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     @Override34     public void setPrice(double price) {35         // TODO Auto-generated method stub36         this.price = price;37     }38 39     @Override40     public double getPrice() {41         // TODO Auto-generated method stub42         return price;43     }44 45     @Override46     public String toString() {47         return "Apple [avgSize=" + avgSize + ", price=" + price48                 + ", productionArea=" + productionArea + "]";49     }50 51     //克隆自身的实现52     @Override53     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     @Override39     public void setPrice(double price)40     {41         // TODO Auto-generated method stub42         this.price = price;43     }44 45     @Override46     public double getPrice()47     {48         // TODO Auto-generated method stub49         return price;50     }51 52     // 重写了Object类的clone()方法53     @Override54     public Object clone()55     {56         // TODO Auto-generated method stub57         Object cloneApple = null;58         // 直接调用父类的克隆方法即可59         try60         {61             cloneApple = super.clone();62         } catch (CloneNotSupportedException e)63         {64             e.printStackTrace();65         }66         return cloneApple;67     }68 69     @Override70     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

个人认为,方便而言,还不如自己手动实现原型模式。

由于Java的原型使用RTTI的机制,速度块,若需要克隆重量级或复杂的对象时适合使用。不过要注意的是,Java的原型模式属于浅度克隆。下面我们会讲如何使用Java的原型模式实现深度克隆。

这里再补充一点:

我们来看,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

 

 

转载于:https://www.cnblogs.com/zrtqsk/p/3712096.html

你可能感兴趣的文章
excel自定义函数添加和使用方法
查看>>
C# 压缩组件介绍与入门
查看>>
结对学习心得感想及创意照
查看>>
sug
查看>>
windows 环境变量
查看>>
input checked取值
查看>>
快速幂取模(当数很大时,相乘long long也会超出的解决办法)
查看>>
EF+Code First+Database First+Model First,EF开发流程
查看>>
HttpWebRequest的常见错误使用TcpClient可避免
查看>>
报表技术
查看>>
java基础---多线程---volatile详解
查看>>
eclipse中tomcat启动成功,浏览器访问失败
查看>>
中文乱码(Python、WEB、ajax)
查看>>
mysql 开发进阶篇系列 43 逻辑备份与恢复(mysqldump 的基于时间和位置的不完全恢复)...
查看>>
Go开发之路 -- 流程控制
查看>>
bootstrap:按钮下拉菜单
查看>>
git diff命令
查看>>
LeetCode:Climbing Stairs(DP)
查看>>
STC12C5A60S2笔记7(定时器)
查看>>
[HNOI2004]宠物收养场 BZOJ1208 splay tree
查看>>