Day 09: Creational patterns - Prototype

目的

实践物件的 Deep Copy,避免 Shallow Copy 的问题以及省去重新制作物件。

说明

Prototype 源自於物件的复制问题,如果资料的型别是字串、数字、布林等,可以直接复制(call by value),物件与阵列这两类只会复制记忆体的储存位置(call by reference),这将导致复制不完全(Shallow CopyShallow Clone),新旧物件共用相同的物件与阵列,肯定会出事。

因此,为了完全复制(Deep CopyDeep Clone),可以采用 Prototype,作法是:

  1. 建立一个可以执行 Deep Copy 方法的物件,可以是 AbstractInterfaceClass
  2. 该方法实作上,针对物件与阵列处额外处理,确保复制成功。

UML 图

Prototype Pattern UML Diagram

使用 Java 实作

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();
    }
}

使用 JavaScript 实作

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 模式。


<<:  Day 24 实作 user_bp (2)

>>:  [Day09] 选择困难再度发作之主题挑选

Day 03 Python 入门

因为考虑到才第三篇就开始飙车直接上 Flask 会不会太快,加上这系列有一小部分原因(大约50%?)...

[机派X] Day 12 - 那些年还没介绍的无人机部件

引言 今天是机派X系列文章的第十二天。 今天会接续昨天的部件介绍,将剩下几个重要的部件介绍给大家。 ...

DAY17: 实作提交表单的Post请求

学完Get请求後就不免要学一下Post请求了,在DAY15: HTTP GET请求的开头有提到,Ge...

DAY16-Style Components

前言: 今天我们要来介绍React里很强大的一个工具!没错就是Style Components!废...

Day 30 云端守门员

来到了最後一天,我们也剩下最後一片云要一起来探索。今天就来谈谈云端的资安,以此来总结我们这一趟经历两...