簡介
原型模式(Prototype Pattern)是用于建立重複的對象,同時又能保證性能。這種類型的設計模式屬于建立型模式,它提供了一種建立對象的最佳方式。
這種模式是實作了一個原型接口,該接口用于建立目前對象的克隆。當直接建立對象的代價比較大時,則采用這種模式。
克隆
原型模式對某個對象進行克隆,在最原始的古老方法就是通過new出執行個體,使用與之相同的參數,但是每次建立新的對象時候需要重新擷取一下屬性,效率不佳。 例子如下:
①、古老辦法
本次使用lombok,省的還需要寫get,set,toString;坐标導入如下:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
實體類,使用lombok的@Data注解
package com.lyd.demo.entity;
import lombok.Data;
/**
* @Author: lyd
* @Description:
* @Date: 2022-08-25
*/
@Data
public class Car {
private String type;
private String color;
public Car(String type, String color) {
this.type = type;
this.color = color;
}
}
使用古老辦法,使用new一個一個指派,很明顯有些缺點,每次都需要調用對象去擷取屬性
package com.lyd.demo.ancient;
import com.lyd.demo.entity.Car;
/**
* @Author: lyd
* @Description:
* @Date: 2022-08-25
*/
public class Test {
public static void main(String[] args) {
Car car = new Car("奔馳", "black");
// 複制更多對象
Car c1 = new Car(car.getType(), car.getColor());
Car c2 = new Car(car.getType(), car.getColor());
Car c3 = new Car(car.getType(), car.getColor());
System.out.println("car: " + car + " hashcode: " + car.hashCode());
System.out.println("c1: " + c1 + " hashcode: " + c1.hashCode());
System.out.println("c2: " + c2 + " hashcode: " + c2.hashCode());
System.out.println("c3: " + c3 + " hashcode: " + c3.hashCode());
}
}
運作結果:可見雖然是複制成功了,但是效率明顯很低。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iYjJDN5MWMkFTN3YjZ5gDMlJzN5IjZjlTOkZmNyEWO08CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
②、原型模式
用原型執行個體指定建立對象的種類,通過拷貝執行個體建立新的對象。 例子如下代碼: 需要讓car實作Cloneable并且實作clone方法。
package com.lyd.demo.entity;
import lombok.Data;
/**
* @Author: lyd
* @Description:
* @Date: 2022-08-25
*/
@Data
public class Car implements Cloneable {
private String type;
private String color;
public Car(String type, String color) {
this.type = type;
this.color = color;
}
// 克隆執行個體
@Override
protected Object clone() throws CloneNotSupportedException {
Car car = null;
car = (Car) super.clone();
return car;
}
}
測試:
package com.lyd.demo.entity;
/**
* @Author: lyd
* @Description:
* @Date: 2022-08-25
*/
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Car car = new Car("紅旗", "red");
Car c1 = (Car) car.clone();
Car c2 = (Car) car.clone();
Car c3 = (Car) car.clone();
System.out.println("car: " + car + " hashcode: " + car.hashCode());
System.out.println("c1: " + c1 + " hashcode: " + c1.hashCode());
System.out.println("c2: " + c2 + " hashcode: " + c2.hashCode());
System.out.println("c3: " + c3 + " hashcode: " + c3.hashCode());
}
}
運作結果:
定義一個原型類,聲明出克隆自己的接口。克隆的時候隻要調用clone方法就可以實作對象克隆。
淺拷貝
對于資料類型是基本資料類型的成員變量,淺拷貝會直接進行值傳遞,也就是将該屬性複制一份給新的對象。因為是兩份不同的資料,是以對其中一的對象的成員變量值進行修改,不會影響另一個對象拷貝得到的資料。 對于資料類型是引用類型的成員變量,比如說成員變量是某個數組,某個類的對象等,那麼淺拷貝會進行引用傳遞,也就是隻是将該成員變量的引用指(記憶體位址)複制一份給新的對象。因為實際上兩個對象的該成員變量都指向同一個執行個體。在這種情況下,在一個對象中修改該成員變量會影響到另一個對象的該成員變量值。
在Car類中加一個屬性
public Car NewEnergy;
編寫測試淺拷貝類
package com.lyd.demo.entity;
/**
* @Author: lyd
* @Description: 淺拷貝
* @Date: 2022-08-25
*/
public class ShallowCopyTest {
public static void main(String[] args) throws CloneNotSupportedException {
Car car = new Car("紅旗", "red");
car.NewEnergy = new Car("奔馳", "blue");
// 克隆
Car c1 = (Car) car.clone();
Car c2 = (Car) car.clone();
Car c3 = (Car) car.clone();
System.out.println("car: " + car + " hashcode: " + car.hashCode() + " NewEnergy: " + car.NewEnergy + " hashcode: " + car.NewEnergy.hashCode());
System.out.println("c1: " + c1 + " hashcode: " + c1.hashCode() + " NewEnergy: " + c1.NewEnergy + " hashcode: " + c1.NewEnergy.hashCode());
System.out.println("c2: " + c2 + " hashcode: " + c2.hashCode() + " NewEnergy: " + c2.NewEnergy + " hashcode: " + c2.NewEnergy.hashCode());
System.out.println("c3: " + c3 + " hashcode: " + c3.hashCode() + " NewEnergy: " + c3.NewEnergy + " hashcode: " + c3.NewEnergy.hashCode());
}
}
測試結果
可見,如果是基本資料類型,那就會傳遞值,将值賦給新的對象;對于對象,則隻是進行引用傳遞。這種就是淺拷貝。這樣會導緻隻要修改其中一個變量,就會導緻所有都修改,例如 c2.NewEnergy.setColor("red"); 之後在列印一次資料。
深拷貝
深拷貝複制變量值,對于引用資料,則遞歸至基本類型後,再複制。深拷貝後的對象與原來的對象是完全隔離的,互不影響,對一個對象的修改并不會影響另一個對象。深拷貝相比于淺拷貝速度較慢并且花銷較大。 重新定義兩個實體類,其中一個隻有基本資料類型,另一個包含引用資料類型
方式一:通過重寫clone()方法
Car: 在克隆的時候直接調用super.clone()來完成對基本資料類型的克隆,通過引用資料類的clone方法在進行類型強制裝換即可。
package com.lyd.demo.deep;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: lyd
* @Description: 汽車類
* @Date: 2022-08-25
*/
@Data
public class Car implements Cloneable, Serializable {
private String type;
private String color;
public NewEnergy newEnergy; // 引用資料類型
public Car(String type, String color) {
this.type = type;
this.color = color;
}
// 克隆執行個體
@Override
protected Object clone() throws CloneNotSupportedException {
Object object = null;
// 完成基本資料的裝換
object = super.clone();
// 對引用資料類型的屬性,單獨處理
Car car = (Car) object;
car.newEnergy = (NewEnergy) newEnergy.clone();
return car;
}
}
NewEnergy:
package com.lyd.demo.deep;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: lyd
* @Description: 新能源類
* @Date: 2022-08-25
*/
@Data
public class NewEnergy implements Serializable, Cloneable {
private String type;
private String color;
public NewEnergy(String type, String color) {
this.type = type;
this.color = color;
}
/**
* 該類都是基本資料類型,直接傳回即可
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
測試:
package com.lyd.demo.deep;
/**
* @Author: lyd
* @Description: 深拷貝
* @Date: 2022-08-25
*/
public class DeepCopyTest {
public static void main(String[] args) throws CloneNotSupportedException {
Car car = new Car("路虎", "green");
car.newEnergy = new NewEnergy("路虎新能源", "blue");
Car c1 = (Car) car.clone();
Car c2 = (Car) car.clone();
System.out.println("car: " + car + " hashcode: " + car.hashCode() + " NewEnergy: " + car.newEnergy + " hashcode: " + car.newEnergy.hashCode());
System.out.println("c1: " + c1 + " hashcode: " + c1.hashCode() + " NewEnergy: " + c1.newEnergy + " hashcode: " + c1.newEnergy.hashCode());
System.out.println("c2: " + c2 + " hashcode: " + c2.hashCode() + " NewEnergy: " + c2.newEnergy + " hashcode: " + c2.newEnergy.hashCode());
System.out.println("==============================================================================================================================");
c1.newEnergy.setColor("red"); // 修改
System.out.println("car: " + car + " hashcode: " + car.hashCode() + " NewEnergy: " + car.newEnergy + " hashcode: " + car.newEnergy.hashCode());
System.out.println("c1: " + c1 + " hashcode: " + c1.hashCode() + " NewEnergy: " + c1.newEnergy + " hashcode: " + c1.newEnergy.hashCode());
System.out.println("c2: " + c2 + " hashcode: " + c2.hashCode() + " NewEnergy: " + c2.newEnergy + " hashcode: " + c2.newEnergy.hashCode());
}
}
運作結果:
可見,不但完成了把引用對象進行拷貝,修改c1的屬性時,不會影響其他的對象,這就是深拷貝
方式二:通過對象序列化
通過對象流的方式将對象進行序列化之後在進行反序列化完成深拷貝。這樣的效果會更好。 隻要在Car類中添加一個deepClone方法
// 方式二:實作序列化
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);
Car car = (Car) ois.readObject();
return car;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
// 關閉流
try {
ois.close();
bis.close();
oos.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
測試:隻要調用deepClone方法就可以
Car c1 = (Car) car.deepClone();
Car c2 = (Car) car.deepClone();
最後實驗結果:
💓小建議:
了解設計模式不是一件簡單的事情,需要不斷的學習和動手去練習,才能了解。隻有掌握好設計模式,才能夠真正的了解SpringAOP和Mybatis的底層原理。各位讀者可以和我一樣,動手敲一敲代碼,甚至用不同的例子來做,通過debug一步一步調試,還有就是多看看别人的例子。能夠有助于了解!