android設計模式二十三式(四)——原型模式(Prototype)

原型模式

因爲類初始化需要消耗非常多的資源,包括數據、硬件資源等,通過原型拷貝則可以避免這些消耗,而且new一個對象會經過非常繁瑣的數據準備或訪問權限,假若一個對象需要供給其他對象訪問,而且各個對象都需要修改其值時,可以拷貝多個對象供調用者訪問,即保護性拷貝,以上就是用到原型模型的場景。總而言之,及時你需要用到一個對象,還需要修改該對象部分值,保留部分值,你就可以用它。下面就結合代碼來說明一下

我們有一個對象,包含了一些文本和一些對象

原型模型,主要是實現Cloneable接口和覆寫Object的clone方法

1.淺拷貝

我們還是寫一輛車,有品牌,重量,供應商,和擁有者的信息

/**
 * @author: hx
 * @Time: 2019/5/6
 * @Description: Car
 */
public class Car implements Cloneable {
    private String brand;
    private int weight;
    private ArrayList<String> supplier = new ArrayList<>();
    private Owner mOwner = new Owner();

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public List<String> getSupplier() {
        return supplier;
    }

    public void setSupplier(String... factoryName) {
        for (int i = 0; i < factoryName.length; i++) {
            supplier.add(factoryName[i]);
        }
    }

    public Owner getOwner() {
        return mOwner;
    }

    public void setOwner(String name, int age) {
        mOwner.setName(name);
        mOwner.setAge(age);
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", weight=" + weight +
                ", supplier=" + supplier +
                ", mOwner=" + mOwner +
                '}';
    }

    @Override
    public Car clone() throws CloneNotSupportedException {
        System.out.println("clone時不執行構造函數");
        Car car = (Car) super.clone();
        return car;
    }

    static class Owner {
        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "Owner{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}
public static void main(String[] args) throws CloneNotSupportedException {
    Car car1 = new Car();
    car1.setBrand("保時捷");
    car1.setWeight(1895);
    car1.setOwner("張三",33);
    car1.setSupplier("車輪","方向盤");
    System.out.println(car1.toString());
    System.out.println("----------------------");
    Car car2 = car1.clone();
    System.out.println(car2.toString());
    System.out.println("----------------------");
}

輸出結果:

Car{brand='保時捷', weight=1895, supplier=[車輪, 方向盤], mOwner=Owner{name='張三', age=33}}
----------------------
clone時不執行構造函數
Car{brand='保時捷', weight=1895, supplier=[車輪, 方向盤], mOwner=Owner{name='張三', age=33}}
----------------------

看起來並沒有什麼問題,對吧,我們來定製一下第二輛車的信息

public static void main(String[] args) throws CloneNotSupportedException {
    Car car1 = new Car();
    car1.setBrand("保時捷");
    car1.setWeight(1895);
    car1.setOwner("張三",33);
    car1.setSupplier("車輪","方向盤");
    System.out.println("car1="+car1.toString());
    System.out.println("----------------------");
    Car car2 = car1.clone();
    car2.setWeight(2000);
    car2.setOwner("李四",50);
    car2.setSupplier("底盤");
    System.out.println("car1="+car1.toString());
    System.out.println("----------------------");
    System.out.println("car2="+car2.toString());
    System.out.println("----------------------");
}

輸出結果:

car1=Car{brand='保時捷', weight=1895, supplier=[車輪, 方向盤], mOwner=Owner{name='張三', age=33}}
----------------------
clone時不執行構造函數
car1=Car{brand='保時捷', weight=1895, supplier=[車輪, 方向盤, 底盤], mOwner=Owner{name='李四', age=50}}
----------------------
car2=Car{brand='保時捷', weight=2000, supplier=[車輪, 方向盤, 底盤], mOwner=Owner{name='李四', age=50}}
----------------------

可以看到,我們只修改了第二輛車的信息,但是第一輛車的也一起改了,只是第一輛車的原本基本類型的值沒有修改,所以可以得出結論,淺拷貝,在拷貝引用的時候,並沒有拷貝對象,只是拷貝了對象的引用,所以我們需要對非基本類型也進行拷貝,即深拷貝。

2.深拷貝

在運用原型模式時建議還是用深拷貝,下面我們把上面的淺拷貝改成深拷貝

要注意的是,拷貝的時候是不走構造函數的

/**
 * @author: hx
 * @Time: 2019/5/6
 * @Description: Car
 */
public class Car implements Cloneable {
    private String brand;
    private int weight;
    private ArrayList<String> supplier = new ArrayList<>();
    private Owner mOwner = new Owner();

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public List<String> getSupplier() {
        return supplier;
    }

    public void setSupplier(String... factoryName) {
        for (int i = 0; i < factoryName.length; i++) {
            supplier.add(factoryName[i]);
        }
    }

    public Owner getOwner() {
        return mOwner;
    }

    public void setOwner(String name, int age) {
        mOwner.setName(name);
        mOwner.setAge(age);
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", weight=" + weight +
                ", supplier=" + supplier +
                ", mOwner=" + mOwner +
                '}';
    }

    @Override
    public Car clone() throws CloneNotSupportedException {
        System.out.println("clone時不執行構造函數");
        Car car = (Car) super.clone();
        car.supplier = (ArrayList<String>) this.supplier.clone();
        car.mOwner = (Owner) this.mOwner.clone();
        return car;
    }

    static class Owner implements Cloneable{
        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "Owner{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            Owner owner = (Owner) super.clone();
            return owner;
        }
    }

}

看一下運行結果

public static void main(String[] args) throws CloneNotSupportedException {
    Car car1 = new Car();
    car1.setBrand("保時捷");
    car1.setWeight(1895);
    car1.setOwner("張三",33);
    car1.setSupplier("車輪","方向盤");
    System.out.println("car1="+car1.toString());
    System.out.println("----------------------");
    Car car2 = car1.clone();
    car2.setWeight(2000);
    car2.setOwner("李四",50);
    car2.setSupplier("底盤");
    System.out.println("car1="+car1.toString());
    System.out.println("----------------------");
    System.out.println("car2="+car2.toString());
    System.out.println("----------------------");
}

輸出結果:

car1=Car{brand='保時捷', weight=1895, supplier=[車輪, 方向盤], mOwner=Owner{name='張三', age=33}}
----------------------
clone時不執行構造函數
car1=Car{brand='保時捷', weight=1895, supplier=[車輪, 方向盤], mOwner=Owner{name='張三', age=33}}
----------------------
car2=Car{brand='保時捷', weight=2000, supplier=[車輪, 方向盤, 底盤], mOwner=Owner{name='李四', age=50}}
----------------------

 可以看到,深拷貝的之後,再修改拷貝對象就不會對原始對象的值有所影響。

爲了防止遺漏,一般都是將所有的值都會進行一次賦值,即

    @Override
    public Car clone() throws CloneNotSupportedException {
        System.out.println("clone時不執行構造函數");
        Car car = (Car) super.clone();
        car.brand = this.brand;
        car.weight = this.weight;
        car.supplier = (ArrayList<String>) this.supplier.clone();
        car.mOwner = (Owner) this.mOwner.clone();
        return car;
    }



    @Override
    protected Object clone() throws CloneNotSupportedException {
        Owner owner = (Owner) super.clone();
        owner.name = this.name;
        owner.age = this.age;
        return owner;
    }

 在android中,其實還有的會將賦值行爲放到構造函數中,然後在clone方法中直接new一個自己的對象。如Intent對象

/**
     * Copy constructor.
     */
    public Intent(Intent o) {
        this(o, COPY_MODE_ALL);
    }

    private Intent(Intent o, @CopyMode int copyMode) {
        this.mAction = o.mAction;
        this.mData = o.mData;
        this.mType = o.mType;
        this.mPackage = o.mPackage;
        this.mComponent = o.mComponent;

        if (o.mCategories != null) {
            this.mCategories = new ArraySet<>(o.mCategories);
        }

        if (copyMode != COPY_MODE_FILTER) {
            this.mFlags = o.mFlags;
            this.mContentUserHint = o.mContentUserHint;
            this.mLaunchToken = o.mLaunchToken;
            if (o.mSourceBounds != null) {
                this.mSourceBounds = new Rect(o.mSourceBounds);
            }
            if (o.mSelector != null) {
                this.mSelector = new Intent(o.mSelector);
            }

            if (copyMode != COPY_MODE_HISTORY) {
                if (o.mExtras != null) {
                    this.mExtras = new Bundle(o.mExtras);
                }
                if (o.mClipData != null) {
                    this.mClipData = new ClipData(o.mClipData);
                }
            } else {
                if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
                    this.mExtras = Bundle.STRIPPED;
                }

                // Also set "stripped" clip data when we ever log mClipData in the (broadcast)
                // history.
            }
        }
    }

    @Override
    public Object clone() {
        return new Intent(this);
    }

 執行Intent的clone方法時,實際上是重新new了一個對象,具體是選擇clone對象還是new一個對象,就看實際創建對象的成本和場景,如果構造很麻煩或者成本太高,還是選擇clone效率會高一點。

優點

  • 可以解決複雜對象創建時消耗過多的問題,在某些場景下提升創建對象的效率。
  • 保護性拷貝,可以防止外部調用者對對象的修改,保證這個對象是隻讀的。

缺點

  • 拷貝對象時不會執行構造函數。
  • 有時需要考慮深拷貝和淺拷貝的問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章