前言
前面学习了工厂模式,今天给大家带来另一种Java设计模式:原型设计模式。这个模式较比于工厂模式,我用克隆羊的Java案例来进行讲解,就比较容易理解和使用,内容不多,希望大家喜欢💕
前提引用
假设有一只羊,叫做“多莉”(就是高中学的那个克隆羊多莉),年龄是3岁,颜色是白色。现在用编程实现对多莉的克隆:即克隆一只跟它一模一样的小羊(名字、年龄和颜色相同)
💨解决方式
一、🍂传统方式
🔥设计代码
先创建多莉这个小羊:
package java设计模式.prototype; public class Sheep { private String name; private int age; private String color; public Sheep(String name, int age, String color) { this.name = name; this.age = age; this.color = color; } 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 String getColor() { return color; } public void setColor(String color) { this.color = color; } @Override public String toString() { return "Sheep [name=" + name + ", age=" + age + ", color=" + color + "]"; } }
创建类Client
当做生物学家,操作克隆的整个过程
package java设计模式.prototype; public class Client { public static void main(String[] args) { //原型小羊 Sheep oldSheep = new Sheep("多莉", 3, "白色"); //开始克隆 Sheep newSheep = new Sheep(oldSheep.getName(), oldSheep.getAge(), oldSheep.getColor()); System.out.println(oldSheep); System.out.println(newSheep); } }
是如何操作的呢?由代码可以看出,当我们要克隆一个新的小羊的时候,我们在构造器中直接引用了原型小羊的getAge()
,getName()
,getColor()
的方法,实现了全部的克隆。
运行结果
🔥优缺点比较
**优点:**
优点是比较好理解,简单易操作
**缺点:**
在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活
二、🍂原型模式
🔥思路分析
那么,该如何避免传统方式的缺点呢?
我们知道,Java中Object类是所有类的父类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力,由此来引出我们的原型模式。
🔥基本介绍
• 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象 • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象, 无需知道如何创建的细节 • `工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建 的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()`
🔥类图分析
原理结构说明: Prototype:原型类,声明一个克隆自己的接口 ConcreteProtptype:具体的原型类,实现一个克隆自己的操作 Client:让一个原型对象克隆自己,从而创建一个新对象(属性一样)
🔥代码设计
让上面的Sheep类
实现Cloneable接口
后重写clone()方法
,代码如下:
package java设计模式.prototype.improve; public class Sheep implements Cloneable{ private String name; private int age; private String color; public Sheep(String name, int age, String color) { this.name = name; this.age = age; this.color = color; } 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 String getColor() { return color; } //克隆该实例,使用默认的clone方法完成 @Override protected Object clone() { Sheep sheep = null; try { sheep= (Sheep) super.clone(); } catch (Exception e) { System.out.println(e.getMessage()); } return sheep; } public void setColor(String color) { this.color = color; } @Override public String toString() { return "Sheep [name=" + name + ", age=" + age + ", color=" + color + "]"; } }
最后建一个Client类
,用来测试:
package java设计模式.prototype.improve; public class Client { public static void main(String[] args) { Sheep oldSheep = new Sheep("多莉", 3, "白色"); Sheep sheep1 = (Sheep) oldSheep.clone(); Sheep sheep2 = (Sheep) oldSheep.clone(); System.out.println(sheep1); System.out.println(sheep2); } }
💨方式比较
<font color=#AAA00>那么小伙伴们可能就要问了,这不跟方式一一样吗?没什么简便的地方啊,那么问题来了,假如那只多莉小羊来自于北京,我的克隆羊也必须来自于北京,用方式一的办法,是不是还需要从构造器中手动创建?如果要克隆一百只,一万只,一千万只小羊呢?用方式一是不是很麻烦!而用方式二,设定好需要克隆的小羊后,只需要使用clone()
方法就可以一键克隆小羊了,再多的小羊,也不怕!
原型模式在Spring中的源码分析
浅拷贝
深拷贝
public class Sheep implements Cloneable { private String name; private int age; private String color; private String address="内蒙古"; private Sheep friend; //.... } public class Client { public static void main(String[] args) { Sheep sheep1 = new Sheep("Tom", 3, "白色"); sheep1.setFriend(new Sheep("Bob", 2, "黑色")); Sheep sheep2 = (Sheep) sheep1.clone(); Sheep sheep3 = (Sheep) sheep1.clone(); //... System.out.println(sheep1.getFriend().hashCode()); System.out.println(sheep2.getFriend().hashCode()); System.out.println(sheep3.getFriend().hashCode()); } }
方式一:重新clone()方法(这里懒得写set、get了,就直接把变量声明为public)
package design_partten.prototype.type1.improve; class Bird implements Cloneable{ public String name; public Bird(String name) { this.name = name; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class Sheep implements Cloneable { public String name; public int age; public String color; public Bird friend; public Sheep(String name, int age, String color, Bird friend) { this.name = name; this.age = age; this.color = color; this.friend = friend; } @Override protected Object clone() throws CloneNotSupportedException { //1. 先拷贝基本数据类型的成员变量 Sheep sheep = (Sheep) super.clone(); //2. 拷贝引用类型的成员变量(即对引用类型成员再进行一次拷贝,然后赋值给刚刚拷贝的克隆羊) Bird friend = (Bird) this.friend.clone(); sheep.friend = friend; return sheep; } }
测试:
public class Client { public static void main(String[] args) throws CloneNotSupportedException { Sheep sheep1 = new Sheep("Tom", 3, "白色", new Bird("鸟")); Sheep sheep2 = (Sheep) sheep1.clone(); Sheep sheep3 = (Sheep) sheep1.clone(); //... System.out.println(sheep1.friend.hashCode()); System.out.println(sheep2.friend.hashCode()); System.out.println(sheep3.friend.hashCode()); } }
结果:
评价:通过重新clone()方法来实现深拷贝,虽然看上去简单,代码较少,但这是因为我们成员变量只有一个是引用类型,如果有很多成员变量都是引用类型,那么我们每个变量都要去写。不推荐。
方式二:通过对象的序列化方式实现(推荐)
//在Sheep类添加该方法,且Sheep和Bird实现Serialized接口 public Object deepClone(){ //创建字节数组输出流对象 ByteArrayOutputStream bos = null; ObjectOutputStream oos = null; ByteArrayInputStream bis = null; ObjectInputStream ois = null; try { //序列化 bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this);//当前这个对象以对象流的方式输出 //反序列化 bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); Sheep sheep = (Sheep)ois.readObject(); return sheep; }catch (Exception e){ e.getStackTrace(); return null; }finally { //关闭流 try { ois.close(); bis.close(); oos.close(); bos.close(); } catch (IOException e) { e.printStackTrace(); } } }
📕总结
创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
不用重新初始化对象,而是动态地获得对象运行时的状态
如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
在实现深克隆的时候可能需要比较复杂的代码
缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则,这点需要特别注意.