设计模式【5】-- 原型模式
開局一張圖,剩下全靠寫…
設計模式文章集合:http://aphysia.cn/categories/designpattern
前言
接觸過 Spring 或者 Springboot 的同學或許都了解, Bean 默認是單例的,也就是全局共用同一個對象,不會因為請求不同,使用不同的對象,這里我們不會討論單例,前面已經討論過單例模式的好處以及各種實現,有興趣可以了解一下:http://aphysia.cn/archives/designpattern1。除了單例以外,Spring還可以設置其他的作用域,也就是scope="prototype",這就是原型模式,每次來一個請求,都會新創建一個對象,這個對象就是按照原型實例創建的。
原型模式的定義
原型模式,也是創建型模式的一種,是指用原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象,簡單來說,就是拷貝。一般適用于:
- 實例比較復雜,完全創建成本高,直接復制比較簡單
- 構造函數比較復雜,創建可能產生很多不必要的對象
優點:
- 隱藏了創建實例的具體細節
- 創建對象效率比較高
- 如果一個對象大量相同的屬性,只有少量需要特殊化的時候,可以直接用原型模式拷貝的對象,加以修改,就可以達到目的。
原型模式的實現方式
一般來說,原型模式就是用來復制對象的,那么復制對象必須有原型類,也就是Prototype,Prototype需要實現Cloneable接口,實現這個接口才能被拷貝,再重寫clone()方法,還可以根據不同的類型來快速獲取原型對象。
我們先定義一個原型類Fruit:
public abstract class Fruit implements Cloneable{String name;float price;public String getName() {return name;}public void setName(String name) {this.name = name;}public float getPrice() {return price;}public void setPrice(float price) {this.price = price;}public Object clone() {Object clone = null;try {clone = super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return clone;}@Overridepublic String toString() {return "Fruit{" +"name='" + name + '\'' +", price=" + price +'}';} }以及拓展了Fruit類的實體類Apple,Pear,Watermelon:
public class Apple extends Fruit{public Apple(float price){name = "蘋果";this.price = price;} } public class Pear extends Fruit{public Pear(float price){name = "雪梨";this.price = price;} } public class Watermelon extends Fruit{public Watermelon(float price){name = "西瓜";this.price = price;} }創建一個獲取不同水果類的緩存類,每次取的時候,根據不同的類型,取出來,拷貝一次返回即可:
public class FruitCache {private static ConcurrentHashMap<String,Fruit> fruitMap =new ConcurrentHashMap<String,Fruit>();static {Apple apple = new Apple(10);fruitMap.put(apple.getName(),apple);Pear pear = new Pear(8);fruitMap.put(pear.getName(),pear);Watermelon watermelon = new Watermelon(5);fruitMap.put(watermelon.getName(),watermelon);}public static Fruit getFruit(String name){Fruit fruit = fruitMap.get(name);return (Fruit)fruit.clone();} }測試一下,分別獲取不同的水果,以及對比兩次獲取同一種類型,可以發現,兩次獲取的同一種類型,不是同一個對象:
public class Test {public static void main(String[] args) {Fruit apple = FruitCache.getFruit("蘋果");System.out.println(apple);Fruit pear = FruitCache.getFruit("雪梨");System.out.println(pear);Fruit watermelon = FruitCache.getFruit("西瓜");System.out.println(watermelon);Fruit apple1 = FruitCache.getFruit("蘋果");System.out.println("是否為同一個對象" + apple.equals(apple1));} }結果如下:
Fruit{name='蘋果', price=10.0} Fruit{name='雪梨', price=8.0} Fruit{name='西瓜', price=5.0} false再測試一下,我們看看里面的name屬性是不是同一個對象:
public class Test {public static void main(String[] args) {Fruit apple = FruitCache.getFruit("蘋果");System.out.println(apple);Fruit apple1 = FruitCache.getFruit("蘋果");System.out.println(apple1);System.out.println("是否為同一個對象:" + apple.equals(apple1));System.out.println("是否為同一個字符串對象:" + apple.name.equals(apple1.name));} }結果如下,里面的字符串確實還是用的是同一個對象:
Fruit{name='蘋果', price=10.0} Fruit{name='蘋果', price=10.0} 是否為同一個對象:false 是否為同一個字符串對象:true這是為什么呢?因為上面使用的clone()是淺拷貝!!!不過有一點,字符串在Java里面是不可變的,如果發生修改,也不會修改原來的字符串,由于這個屬性的存在,類似于深拷貝。如果屬性是其他自定義對象,那就得注意了,淺拷貝不會真的拷貝該對象,只會拷貝一份引用。
這里不得不介紹一下淺拷貝與深拷貝的區別:
- 淺拷貝:沒有真正的拷貝數據,只是拷貝了一個指向數據內存地址的指針
- 深拷貝:不僅新建了指針,還拷貝了一份數據內存
如果我們使用Fruit apple = apple1,這樣只是拷貝了對象的引用,其實本質上還是同一個對象,上面的情況雖然對象是不同的,但是Apple屬性的拷貝還屬于同一個引用,地址還是一樣的,它們共享了原來的屬性對象name。
**那如何進行深拷貝呢?**一般有以下方案:
-
直接 new 對象,這個不用考慮了
-
序列化與反序列化:先序列化之后,再反序列化回來,就可以得到一個新的對象,注意必須實現Serializable接口。
-
自己重寫對象的clone()方法
序列化實現深拷貝
序列化實現代碼如下:
創建一個Student類和School類:
import java.io.Serializable;public class Student implements Serializable {String name;School school;public Student(String name, School school) {this.name = name;this.school = school;} } import java.io.Serializable;public class School implements Serializable {String name;public School(String name) {this.name = name;} }序列化拷貝的類:
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable;public class CloneUtil {public static <T extends Serializable> T clone(T obj) {T result = null;try {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);objectOutputStream.writeObject(obj);objectOutputStream.close();ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);// 返回生成的新對象result = (T) objectInputStream.readObject();objectInputStream.close();} catch (Exception e) {e.printStackTrace();}return result;} }測試類:
public class Test {public static void main(String[] args) {School school = new School("東方小學");Student student =new Student("小明",school);Student student1= CloneUtil.clone(student);System.out.println(student.equals(student1));System.out.println(student.school.equals(student1.school));} }上面的結果均是false,說明確實不是同一個對象,發生了深拷貝。
clone實現深拷貝
前面的Student和School都實現Cloneable接口,然后重寫clone()方法:
public class Student implements Cloneable {String name;School school;public Student(String name, School school) {this.name = name;this.school = school;}@Overrideprotected Object clone() throws CloneNotSupportedException {Student student = (Student) super.clone();student.school = (School) school.clone();return student;} } public class School implements Cloneable {String name;public School(String name) {this.name = name;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();} }測試類:
public class Test {public static void main(String[] args) throws Exception{School school = new School("東方小學");Student student =new Student("小明",school);Student student1= (Student) student.clone();System.out.println(student.equals(student1));System.out.println(student.school.equals(student1.school));} }測試結果一樣,同樣都是false,也是發生了深拷貝。
總結
原型模式適用于創建對象需要很多步驟或者資源的場景,而不同的對象之間,只有一部分屬性是需要定制化的,其他都是相同的,一般來說,原型模式不會單獨存在,會和其他的模式一起使用。值得注意的是,拷貝分為淺拷貝和深拷貝,淺拷貝如果發生數據修改,不同對象的數據都會被修改,因為他們共享了元數據。
【作者簡介】:
秦懷,公眾號【秦懷雜貨店】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。個人寫作方向:Java源碼解析,JDBC,Mybatis,Spring,redis,分布式,劍指Offer,LeetCode等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花里胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查找資料。遺漏或者錯誤之處,還望指正。
劍指Offer全部題解PDF
2020年我寫了什么?
開源編程筆記
關注公眾號 ”秦懷雜貨店“ 可以領取劍指 Offer V1版本的 PDF解法,V2版本增加了題目,還在哼哧哼哧的更新中,并且為每道題目增加了C++解法,敬請期待。
總結
以上是生活随笔為你收集整理的设计模式【5】-- 原型模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#高斯平滑算法 :二维高斯卷积代码实例
- 下一篇: asp.net ajax控件工具集 Au