Java设计模式之原型模式
原型模式(Prototype Pattern)属于对象创建型模式,通过克隆指定原型对象创建新对象。
优点:
- 效率高:原型模式可以简化创建对象的创建过程,通过克隆一个已有的实例可以提高新实例的创建效率;
- 逃避构造函数的约束:通过原型模式克隆对象时,并没有调用构造函数;而是直接使用本地方法clone()。
缺点:
- 每个原型类必须实现Cloneable接口;
- 每个类都要配备一个clone()方法,而且需要对类的功能进行通盘考虑。这对新的类不是很难,但是对已有的类不一定容易,同时如果对已有的类进行修改的话,就违背了“开闭原则”。
应用场景:
- 原型模式很少单独出现,一般是和工厂模式一起出现,通过clone方法创建一个对象,然后由工厂方法提供给调用者;
- 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式;
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝对个对象供调用者使用;
- 资源优化场景等等;
分类:
- 浅克隆;
- 深克隆。
浅克隆
在浅克隆中,如果原型对象的成员变量是基本类型,String类型,包装类,则把成员变量复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
例:
原型类
1 /** 2 * 浅克隆 3 * 创建一个People类实现Cloneable接口 4 */ 5 public class People implements Cloneable{ 6 private int id; 7 private String name; 8 private Date currentDate = new Date(); //引用对象 9 10 public void setId(int id) { 11 this.id = id; 12 } 13 14 public int getId() { 15 return id; 16 } 17 18 public void setName(String name) { 19 this.name = name; 20 } 21 22 public String getName() { 23 return name; 24 } 25 26 public void setCurrentDate(Date currentDate) { 27 this.currentDate = currentDate; 28 } 29 30 public Date getCurrentDate() { 31 return currentDate; 32 } 33 34 //clone()方法其实是Object类中的方法。并且在Cloneable接口中都没有定义这个clone方法 35 @Override 36 protected People clone() throws CloneNotSupportedException { 37 People obj = (People) super.clone(); 38 return obj; 39 } 40 }
测试类
1 public class TestPeople { 2 public static void main(String[] args) throws CloneNotSupportedException { 3 People people = new People(); 4 people.setId(1); 5 people.setName("mali"); 6 people.setCurrentDate(new Date(1234567890L)); 7 System.out.println("****************初始化******************"); 8 System.out.println(people); 9 System.out.println(people.getId()+"\t"+people.getName()+"\t\t"+people.getCurrentDate()); 10 System.out.println("****************浅克隆******************"); 11 People clonePeople = people.clone(); 12 System.out.println(clonePeople); 13 System.out.println(clonePeople.getId()+"\t"+clonePeople.getName()+"\t\t"+clonePeople.getCurrentDate()); 14 System.out.println("克隆和被克隆中的日期对象地址是否相同:"+(people.getCurrentDate() == clonePeople.getCurrentDate())); 15 } 16 }
测试结果
****************初始化****************** testprototype.People@1540e19d 1 mali Thu Jan 15 14:56:07 CST 1970 ****************浅克隆****************** testprototype.People@135fbaa4 1 mali Thu Jan 15 14:56:07 CST 1970 克隆和被克隆中的日期对象地址是否相同:true //由此可以原型类中的引用成员变量和克隆类的相同成员变量所指地址相同
深克隆
在深克隆中,无论原型对象的成员变量是任何类型,都复制一份给克隆对象;也就是说原型对象中引用类型的成员变量和克隆对象中的相同成员变量所指地址不同。
例:
原型类
1 /** 2 * 深克隆 3 * 把原型类中的引用类型(String类,Integer包装类除外)也克隆 4 */ 5 public class Tree implements Cloneable{ 6 private int treeAge; 7 private String treeName; 8 private Date currentDate; 9 10 public void setTreeAge(int treeAge) { 11 this.treeAge = treeAge; 12 } 13 14 public int getTreeAge() { 15 return treeAge; 16 } 17 18 public void setTreeName(String treeName) { 19 this.treeName = treeName; 20 } 21 22 public String getTreeName() { 23 return treeName; 24 } 25 26 public void setCurrentDate(Date currentDate) { 27 this.currentDate = currentDate; 28 } 29 30 public Date getCurrentDate() { 31 return currentDate; 32 } 33 34 @Override 35 protected Tree clone() throws CloneNotSupportedException { 36 Object obj = super.clone(); 37 //深克隆操作 38 Tree tree = (Tree) obj; 39 tree.currentDate = (Date) this.currentDate.clone(); //克隆Date类 40 return tree; 41 } 42 }
测试类
1 public class TestTree { 2 public static void main(String[] args) throws CloneNotSupportedException { 3 Tree tree = new Tree(); 4 tree.setTreeAge(100); 5 tree.setTreeName("杨树"); 6 tree.setCurrentDate(new Date(1234567890123L)); 7 System.out.println("*******************初始化******************"); 8 System.out.println(tree.getTreeAge()+"\t\t"+tree.getTreeName()+"\t"+tree.getCurrentDate()); 9 System.out.println("*******************深克隆******************"); 10 Tree cloneTree = tree.clone(); 11 System.out.println(cloneTree.getTreeAge()+"\t\t"+cloneTree.getTreeName()+"\t"+cloneTree.getCurrentDate()); 12 System.out.println("克隆和被克隆的String地址是否相同: "+(tree.getTreeName() == cloneTree.getTreeName())); 13 System.out.println("克隆和被克隆中的日期对象地址是否相同:"+(tree.getCurrentDate() == cloneTree.getCurrentDate())); 14 System.out.println("*******************修改后******************"); 15 tree.setTreeAge(200); //原型类修改年龄 16 tree.setTreeName("柏树"); //原型类修改名称 17 tree.setCurrentDate(new Date(123456789012L)); //原型类修改当前时间 18 cloneTree.setCurrentDate(new Date()); //克隆类修改当前时间 19 System.out.println("Tree: "+tree.getTreeAge()+"\t\t"+tree.getTreeName()+"\t"+tree.getCurrentDate()); 20 System.out.println("cloneTree: "+cloneTree.getTreeAge()+"\t\t"+cloneTree.getTreeName()+"\t"+cloneTree.getCurrentDate()); 21 System.out.println("克隆和被克隆的String地址是否相同: "+(tree.getTreeName() == cloneTree.getTreeName())); 22 System.out.println("克隆和被克隆中的日期对象地址是否相同:"+(tree.getCurrentDate() == cloneTree.getCurrentDate())); 23 } 24 }
测试结果
*******************初始化****************** 100 杨树 Sat Feb 14 07:31:30 CST 2009 *******************深克隆****************** 100 杨树 Sat Feb 14 07:31:30 CST 2009 克隆和被克隆的String地址是否相同: true //由此可知原型类中的引用成员变量和克隆类的相同成员变量所指地址相同 克隆和被克隆中的日期对象地址是否相同:false //由此可知原型类中的引用成员变量和克隆类的相同成员变量所指地址不同 *******************修改后****************** Tree: 200 柏树 Fri Nov 30 05:33:09 CST 1973 cloneTree: 100 杨树 Tue Aug 06 11:50:18 CST 2019 克隆和被克隆的String地址是否相同: false 克隆和被克隆中的日期对象地址是否相同:false
序列化和反序列化克隆
把对象写到流中是序列化过程,从流中读取对象是反序列化过程。这种克隆方式也属于深克隆。需要克隆的类需要实现Cloneable接口和Serializable接口。
原型类
1 /** 2 * 序列化和反序列化进行深克隆 3 * 必须实现Serializable接口 4 */ 5 public class Car implements Cloneable, Serializable{ 6 private int price; 7 private String color; 8 private Date buyTime; 9 10 public int getPrice() { 11 return price; 12 } 13 14 public void setPrice(int price) { 15 this.price = price; 16 } 17 18 public String getColor() { 19 return color; 20 } 21 22 public void setColor(String color) { 23 this.color = color; 24 } 25 26 public Date getBuyTime() { 27 return buyTime; 28 } 29 30 public void setBuyTime(Date buyTime) { 31 this.buyTime = buyTime; 32 } 33 34 protected Object serClone() throws CloneNotSupportedException{ 35 ByteArrayOutputStream baos = null; 36 ObjectOutputStream oos = null; 37 ByteArrayInputStream bais = null; 38 ObjectInputStream ois = null; 39 Object obj = null; 40 try { 41 baos = new ByteArrayOutputStream(); 42 oos = new ObjectOutputStream(baos); 43 oos.writeObject(this); 44 bais = new ByteArrayInputStream(baos.toByteArray()); 45 ois = new ObjectInputStream(bais); 46 obj = ois.readObject(); 47 } catch (IOException e) { 48 e.printStackTrace(); 49 } catch (ClassNotFoundException e) { 50 e.printStackTrace(); 51 } 52 return obj; 53 } 54 }
测试类
1 public class TestCar { 2 public static void main(String[] args) throws CloneNotSupportedException { 3 Car car = new Car(); 4 car.setPrice(120000); 5 car.setColor("red"); 6 car.setBuyTime(new Date(1234567890123L)); 7 System.out.println("***************初始化**************"); 8 System.out.println(car.getPrice()+"\t"+car.getColor()+"\t\t"+car.getBuyTime()); 9 System.out.println("***************克隆后**************"); 10 Car cloneCar = (Car) car.serClone(); 11 System.out.println(cloneCar.getPrice()+"\t"+cloneCar.getColor()+"\t\t"+cloneCar.getBuyTime()); 12 System.out.println("克隆和被克隆中的日期对象地址是否相同:"+(car.getBuyTime() == cloneCar.getBuyTime())); 13 System.out.println("***************修改后**************"); 14 car.setPrice(200000); 15 car.setColor("blue"); 16 car.setBuyTime(new Date(12345678901234L)); 17 cloneCar.setBuyTime(new Date()); 18 System.out.println("car: "+car.getPrice()+"\t"+car.getColor()+"\t\t"+car.getBuyTime()); 19 System.out.println("cloneCar: "+cloneCar.getPrice()+"\t"+cloneCar.getColor()+"\t\t"+cloneCar.getBuyTime()); 20 System.out.println("克隆和被克隆中的日期对象地址是否相同:"+(car.getBuyTime() == cloneCar.getBuyTime())); 21 } 22 }
测试结果
***************初始化************** 120000 red Sat Feb 14 07:31:30 CST 2009 ***************克隆后************** 120000 red Sat Feb 14 07:31:30 CST 2009 克隆和被克隆中的日期对象地址是否相同:false ***************修改后************** car: 200000 blue Wed Mar 22 03:15:01 CST 2361 cloneCar: 120000 red Tue Aug 06 12:03:07 CST 2019 克隆和被克隆中的日期对象地址是否相同:false
结语:
在实际应用中,原型模式其实一般是和工厂方法一起使用,所以还是要码一下代码。而如果大家要是看懂了上面的代码,就可以轻松的码出需要的代码。