實踐物件的 Deep Copy,避免 Shallow Copy 的問題以及省去重新製作物件。
Prototype 源自於物件的複製問題,如果資料的型別是字串、數字、布林等,可以直接複製(call by value
),物件與陣列這兩類只會複製記憶體的儲存位置(call by reference
),這將導致複製不完全(Shallow Copy 或 Shallow Clone),新舊物件共用相同的物件與陣列,肯定會出事。
因此,為了完全複製(Deep Copy 或 Deep Clone),可以採用 Prototype,作法是:
Abstract
、Interface
、Class
。Prototype:NewTasteBeverage
public class NewTasteBeverage implements Cloneable {
private String baseBeverage;
private String taste;
private String size;
private IdInfo idInfo;
public NewTasteBeverage(String baseBeverage) {
this.baseBeverage = baseBeverage;
idInfo = new IdInfo();
}
/**
* Deep Clone 需要注意的點
*/
private NewTasteBeverage(IdInfo idInfo) throws CloneNotSupportedException {
this.idInfo = (IdInfo) idInfo.clone();
}
public void setProduceInfo(String location, String factoryAddress) {
idInfo.setLocation(location);
idInfo.setFactoryAddress(factoryAddress);
}
public void setSeriesNumber(String seriesNumber) {
idInfo.setSeriesNumber(seriesNumber);
}
public void setTaste(String taste) {
this.taste = taste;
}
public void setSize(String size) {
this.size = size;
}
@Override
public Object clone() throws CloneNotSupportedException {
NewTasteBeverage clonedObject = new NewTasteBeverage(this.idInfo);
clonedObject.baseBeverage = this.baseBeverage;
clonedObject.taste = this.taste;
clonedObject.size = this.size;
return clonedObject;
}
public void showInfo() {
System.out.println("產品是:" + taste + " " + baseBeverage + " " + size);
System.out.println("產地資訊:" + idInfo.getFactoryAddress() + " " + idInfo.getLocation());
System.out.println("序號:" + idInfo.getSeriesNumber() + "\n");
}
}
class IdInfo implements Cloneable {
private String seriesNumber;
private String location;
private String factoryAddress;
public String getSeriesNumber() {
return seriesNumber;
}
public void setSeriesNumber(String seriesNumber) {
this.seriesNumber = seriesNumber;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getFactoryAddress() {
return factoryAddress;
}
public void setFactoryAddress(String factoryAddress) {
this.factoryAddress = factoryAddress;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
進行複製
public class PrototypeSample {
public static void main(String[] args) throws CloneNotSupportedException {
System.out.println("進行第一項產品的嘗試");
NewTasteBeverage beverage1 = new NewTasteBeverage("可樂");
beverage1.setSize("350毫升");
beverage1.setTaste("香草");
beverage1.setProduceInfo("桃園", "臺灣");
beverage1.setSeriesNumber("3617263");
System.out.println("更換,進行第二項");
NewTasteBeverage beverage2 = (NewTasteBeverage) beverage1.clone();
beverage2.setTaste("檸檬");
beverage2.setSeriesNumber("8911733");
System.out.println("更換,進行最後一項");
NewTasteBeverage beverage3 = (NewTasteBeverage) beverage1.clone();
beverage3.setProduceInfo("臺中", "臺灣");
beverage3.setSeriesNumber("1657209");
beverage1.showInfo();
beverage2.showInfo();
beverage3.showInfo();
}
}
Prototype:NewTasteBeverage
class NewTasteBeverage {
constructor(baseBeverage) {
this.baseBeverage = baseBeverage;
this.taste = '';
this.size = '';
this.idInfo = new IdInfo();
}
setProduceInfo(location, factoryAddress) {
this.idInfo.setLocation(location);
this.idInfo.setFactoryAddress(factoryAddress);
}
setSeriesNumber(seriesNumber) {
this.idInfo.setSeriesNumber(seriesNumber);
}
setTaste(taste) {
this.taste = taste;
}
setSize(size) {
this.size = size;
}
clone() {
const clonedObject = Object.create(this);
clonedObject.idInfo = this.idInfo.clone();
clonedObject.baseBeverage = this.baseBeverage;
clonedObject.taste = this.taste;
clonedObject.size = this.size;
return clonedObject;
}
showInfo() {
console.log("產品是:" + this.taste + " " + this.baseBeverage + " " + this.size);
console.log("產地資訊:" + this.idInfo.getFactoryAddress() + " " + this.idInfo.getLocation());
console.log("序號:" + this.idInfo.getSeriesNumber() + "\n");
}
}
class IdInfo {
constructor() {
this.seriesNumber = '';
this.location = '';
this.factoryAddress = '';
}
getSeriesNumber() {
return this.seriesNumber;
}
setSeriesNumber(seriesNumber) {
this.seriesNumber = seriesNumber;
}
getLocation() {
return this.location;
}
setLocation(location) {
this.location = location;
}
getFactoryAddress() {
return this.factoryAddress;
}
setFactoryAddress(factoryAddress) {
this.factoryAddress = factoryAddress;
}
clone() {
const clonedObject = Object.create(this);
clonedObject.seriesNumber = this.seriesNumber;
clonedObject.location = this.location;
clonedObject.factoryAddress = this.factoryAddress;
return clonedObject;
}
}
進行複製
const prototypeSample = () => {
console.log("進行第一項產品的嘗試");
const beverage1 = new NewTasteBeverage("可樂");
beverage1.setSize("350毫升");
beverage1.setTaste("香草");
beverage1.setProduceInfo("桃園", "臺灣");
beverage1.setSeriesNumber("3617263");
console.log("更換,進行第二項");
const beverage2 = beverage1.clone();
beverage2.setTaste("檸檬");
beverage2.setSeriesNumber("8911733");
console.log("更換,進行最後一項");
const beverage3 = beverage1.clone();
beverage3.setProduceInfo("臺中", "臺灣");
beverage3.setSeriesNumber("1657209");
beverage1.showInfo();
beverage2.showInfo();
beverage3.showInfo();
}
prototypeSample();
過往的開發 JavaScript
的經驗,處理物件的 Deep Clone 時,最常使用的是 JSON.parse(JSON.stringify(obj))
,這個做法的前提物件本身的組成只有 JSON 允許的字串、數字、布林值、物件、陣列,其他像是 undefined、Symbol、Set、Map 會因為 JSON 不支援導致複製後得到的是 {}
。除此之外,物件之間的繼承也會變重置,因為 JSON.parse()
會建立新物件,不可能會知道 JSON 前的繼承關係。
藉由這個模式,可以暸解實作 Deep Clone 是所有物件導向語言的都會遇到的問題,趁著這次的學習與了解,讓自己往後遇到擁有複雜繼承關係的物件時,多了一種處理方式。
明天將介紹最後一個 Creational patterns: Prototype 模式。