欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

设计模式之原型模式(Prototype)

程序员文章站 2022-06-13 16:05:42
...

简介

原型模式是在平时很少会去关注的设计模式,用到的机会也不多,但是呢,我们经常能看到原型模式,那就是接口Cloneable(Java),其原理就是通过拷贝一个现有对象来创建一个和当前对象完全一样的对象

问题

老规矩,我们还是通过问题来引入场景。之前,我通过建造者模式组装了一台我喜欢的电脑,这时候,我的朋友听说了,而且觉得我组装的配置很适合他,希望让我给他用同样的配置组装一台电脑,当然一种方式是我继续用建造者模式给他组装一台电脑,当然了,还有另一种方式就是使用原型模式,直接clone一台一模一样的电脑给他,就不要再重新组装。(举例可能不是非常恰当,我们没法使用clone的方式clone电脑,理解思想就行:对现有复杂对象的复制

实现

原型模式我都是结合着建造者模式来介绍的,有部分源码大家可以去建造者模式的文章中找到。这也是因为原型模式的特点:很少单独出现。首先看下,我之前组装的一台电脑。

        //我想组装一台电脑,需要很多配件,就可以使用建造者模式。
        // 构造复杂对象就可以采用建造者模式,例如创建网络请求
        CommonComputerBuilder commonComputerBuilder = new CommonComputerBuilder();
        Computer computer = commonComputerBuilder.buyCache("huawei cache")
                .buyCpu("intel cpu")
                .buyEngine("lenovo engine")
                .buyDisk("sansung disk")
                .buyKeyboard("luoji keyboard")
                .buyScreen("xiaomi screen")
                .build();
        computer.toString();

现在我的朋友也要组装一台完全一样的电脑,我当然可以完全按照上面的代码再来一遍,不过就会比较复杂了。我们来改造Computer,来实现直接复制。

public class Computer implements Cloneable {

    private String screen;

    private String cpu;

    private String keyboard;

    private String disk;

    /**
     * 主机
     */
    private String engine;

    private String cache;

    public Computer() {
    }

    public Computer(String screen, String cpu, String keyboard, String disk, String engine,
                    String cache) {
        this.screen = screen;
        this.cpu = cpu;
        this.keyboard = keyboard;
        this.disk = disk;
        this.engine = engine;
        this.cache = cache;
    }

    public String getScreen() {
        return screen;
    }

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public String getCpu() {
        return cpu;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public String getKeyboard() {
        return keyboard;
    }

    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }

    public String getDisk() {
        return disk;
    }

    public void setDisk(String disk) {
        this.disk = disk;
    }

    public String getEngine() {
        return engine;
    }

    public void setEngine(String engine) {
        this.engine = engine;
    }

    public String getCache() {
        return cache;
    }

    public void setCache(String cache) {
        this.cache = cache;
    }

    @Override
    public String toString() {
        System.out.println("computer is combined by : ---" + getCpu() + "--" + getCache() + "---" + getDisk()
        + "----" + getEngine() + "----" + getKeyboard() + "----" + getScreen());
        return super.toString();
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

仔细观察代码,只调整了两处,1. 实现了cloneable接口,2. 继承了clone()方法。

        //我的朋友想要一台一模一样的
        Computer computer1 = (Computer) computer.clone();
        System.out.print("copy computer:");
        computer1.toString();

结果

computer is combined by : ---intel cpu--huawei cache---sansung disk----lenovo engine----luoji keyboard----xiaomi screen
copy computer:computer is combined by : ---intel cpu--huawei cache---sansung disk----lenovo engine----luoji keyboard----xiaomi screen

很清晰的看到,我将我之前的电脑和配置,完全复制了一份,就给到了我的朋友。也就是说在有现成对象的情况,通过原型模式,可以不断的复制新对象。

深拷贝&浅拷贝

这里还需要提出一个新的概念深拷贝和浅拷贝,我们先来看例子,我给朋友组装完电脑后,又给自己加了一些外接的u盘

public class Computer implements Cloneable {

    private String screen;

    private String cpu;

    private String keyboard;

    private String disk;

    /**
     * 主机
     */
    private String engine;

    private String cache;

    /**
     * U盘
     */
    private List<String> usbFlashDisk;

    public Computer() {
    }

    public Computer(String screen, String cpu, String keyboard, String disk, String engine,
                    String cache) {
        this.screen = screen;
        this.cpu = cpu;
        this.keyboard = keyboard;
        this.disk = disk;
        this.engine = engine;
        this.cache = cache;
    }

    public String getScreen() {
        return screen;
    }

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public String getCpu() {
        return cpu;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public String getKeyboard() {
        return keyboard;
    }

    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }

    public String getDisk() {
        return disk;
    }

    public void setDisk(String disk) {
        this.disk = disk;
    }

    public String getEngine() {
        return engine;
    }

    public void setEngine(String engine) {
        this.engine = engine;
    }

    public String getCache() {
        return cache;
    }

    public void setCache(String cache) {
        this.cache = cache;
    }

    public List<String> getUsbFlashDisk() {
        return usbFlashDisk;
    }

    public void setUsbFlashDisk(List<String> usbFlashDisk) {
        this.usbFlashDisk = usbFlashDisk;
    }

    @Override
    public String toString() {
        System.out.println("computer is combined by : ---" + getCpu() + "--" + getCache() + "---" + getDisk()
        + "----" + getEngine() + "----" + getKeyboard() + "----" + getScreen());
        return super.toString();
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

对应builder增加购买u盘

public class CommonComputerBuilder extends ComputerBuilder {


    @Override
    public ComputerBuilder buyScreen(String screen) {
        computer.setScreen(screen);
        return this;
    }

    @Override
    public ComputerBuilder buyCpu(String cpu) {
        computer.setCpu(cpu);
        return this;
    }

    @Override
    public ComputerBuilder buyKeyboard(String keyboard) {
        computer.setKeyboard(keyboard);
        return this;
    }

    @Override
    public ComputerBuilder buyDisk(String disk) {
        computer.setDisk(disk);
        return this;
    }

    @Override
    public ComputerBuilder buyEngine(String engine) {
        computer.setEngine(engine);
        return this;
    }

    @Override
    public ComputerBuilder buyCache(String cache) {
        computer.setCache(cache);
        return this;
    }

    @Override
    public ComputerBuilder buyUsbFlashDisk(List<String> usbFlashDisk) {
        computer.setUsbFlashDisk(usbFlashDisk);
        return this;
    }
}

来看下执行情况:

        CommonComputerBuilder commonComputerBuilder2 = new CommonComputerBuilder();
        List<String> usbFlashDisk = new ArrayList<>();
        usbFlashDisk.add("sandisk");
        usbFlashDisk.add("otherdisk");
        Computer computer2 = commonComputerBuilder2.buyCache("huawei cache")
                .buyCpu("intel cpu")
                .buyEngine("lenovo engine")
                .buyDisk("sansung disk")
                .buyKeyboard("luoji keyboard")
                .buyScreen("xiaomi screen")
                .buyUsbFlashDisk(usbFlashDisk)
                .build();

        Computer computer3 = (Computer) computer2.clone();
        System.out.println(computer2.getUsbFlashDisk().equals(computer3.getUsbFlashDisk()));
        //好像我和朋友共用了这买来的两个u盘,测试一下是不是这样
        usbFlashDisk.add("thirdDisk");
        computer2.getUsbFlashDisk().forEach(val -> System.out.print(val + ","));
        System.out.println();
        System.out.println("compute3:");
        computer3.getUsbFlashDisk().forEach(val -> System.out.print(val + ","));

结果:

true
sandisk,otherdisk,thirdDisk,
compute3:
sandisk,otherdisk,thirdDisk,

万万没想到,我新配置的u盘,朋友也在用。这是为什么呢?是因为在构造对象的时候,出现了list这个复杂对象,在普通copy中,list等复杂对象只会做浅拷贝,也就是直接把对象引用拷贝过去了,而如果要做深拷贝的话,就必须重写clone方法,将list对象做重新拷贝。我们来看下,改造clone的方法,为了方便起见,我把usbFlashDisk转成了ArrayList,大家如果要运行代码的话,记得改下。为什么用arrayList呢,因为arrayList实现了深拷贝,我们可以直接调用就行,而List只是个接口,无法做clone。

    @Override
    public Object clone() throws CloneNotSupportedException {
        Computer computer = (Computer) super.clone();
        ArrayList<String> usbFlashDisk = (ArrayList<String>) getUsbFlashDisk().clone();
        computer.setUsbFlashDisk(usbFlashDisk);
        return computer;
    }

我们来看下之前的执行结果:

true
sandisk,otherdisk,thirdDisk,
compute3:
sandisk,otherdisk,

不明觉厉,竟然两个List仍然是同一对象,但是呢,我添加了u盘后,我朋友就和我的不一样了。不清楚这是java做的什么优化操作,但是表明我们的深拷贝是生效了!

Cloneable & clone()

Cloneable是Java的一个接口,如果想要实现clone效果,必须要实现Cloneable接口,但是呢Cloneable是没有包含任何方法的。那是因为clone方式是包含在Object类中,而Object是Java所有对象的父类。我们看看clone方法的一些注释

     * @return     a clone of this instance.
     * @throws  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
     * @see java.lang.Cloneable

也是说明了如果不实现Cloneable,那么执行clone方法就会抛出CloneNotSupportedException异常。

总结

原型模式的应用场景:

  1. 创建的对象需要较多资源及复杂的流程
  2. 一个对象可能有多个修改者者

更切实的应用场景,可能是因为我写代码较少,几乎没怎么遇到过,如果以后遇到了,再回来补充介绍。不过有电话,也就是比较明显,减少了对象的重复创建,而且不会和构造对象内部的属性耦合。