signed

QiShunwang

“诚信为本、客户至上”

设计模式之建造者模式:静态内部类实现方式

2021/1/28 16:57:28   来源:

文章目录

  • 1.为什么需要建造者模式?
    • 1.1 遇到的问题
    • 1.2 解决办法一set方法
  • 2.建造者模式是什么?
  • 3.上面的例子怎么改?
  • 4.总结

1.为什么需要建造者模式?

1.1 遇到的问题

当我们在创建一个对象的时候,我们可以声明带参数的构造函数,例如有如下的这样一个pojo:

package com.example.demo.entity;

public class Robot {
    private String manufacturer;
    private String name;
    private String brand;
    private long id;
    private double weight;
    private double width;
    private double height;

    public Robot(String manufacturer, String name, String brand, long id, double weight, double width, double height) {
        this.manufacturer = manufacturer;
        this.name = name;
        this.brand = brand;
        this.id = id;
        this.weight = weight;
        this.width = width;
        this.height = height;
    }
}

当需要使用Robot实例的时候,直接new一个就完了!

Robot robot = new Robot("TF", "Optimus Prime","Peterbilt",234L,1234.34, 3.00, 10.00);

说实话,我已经糊涂了,完全不知道这个robot的制造商和brand,更分不清它到重量,高度。。。这还有个更过分的,我分不清,但是编译器只检查其类型,编译能通过,赋值错误要等到运行时才能发现。这样是不符合要求的,对于程序员而言,我们应当首选编译时错误而不是运行时错误。

1.2 解决办法一set方法

看到这个问题,很容易让人联想到JavaBeans模式:添加setter方法就好了嘛!不要提供这个这么多参数的构造器了,直接使用默认的无参构造函数,然后使用setter方法去设置各个字段的值就好了!

package com.example.demo.entity;

public class Robot {
    private String manufacturer;
    private String name;
    private String brand;
    private long id;
    private double weight;
    private double width;
    private double height;

    public void setManufacturer(String manufacturer) {
        this.manufacturer = manufacturer;
    }

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

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

    public void setId(long id) {
        this.id = id;
    }

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

    public void setWidth(double width) {
        this.width = width;
    }

    public void setHeight(double height) {
        this.height = height;
    }
}

使用的时候就可以

        Robot robot = new Robot();
        robot.setManufacturer("TF");
        robot.setName("Optimus Prime");
        robot.setWeight(1234.34);
        robot.setHeight(10.00);
        robot.setWidth(2);

机智如我在这里插入图片描述
但是,在Effective Java一书中,有这样的内容

Unfortunately, the JavaBeans pattern has serious disadvantages of its own. Because construction is split across multiple calls, a JavaBean may be in an inconsistent state partway through its construction. The class does not have the option of enforcing consistency merely by checking the validity of the constructor parameters. Attempting to use an object when it’s in an inconsistent state may cause failures that are far removed from the code containing the bug and hence difficult to debug. A related disadvantage is that the JavaBeans pattern precludes the possibility of making a class immutable(Item 17) and requires added effort on the part of the programmer to ensure thread safety.

不幸的是,JavaBeans模式也有一些严重的缺陷。因为对象的构造过程本分在了几个方法调用里,因此一个JavaBean在创建过程中,可能处于不一致状态。 类无法仅仅通过检测构造器参数的有效性来保证一致性。当我们企图去使用一个处于不一致状态的对象的时候会出错,而且这种错误比代码中包含bug更难察觉和修改。和这个相关的另一个缺点是JavaBeans模式不能创建一个不可变类(Item17),也就需要程序员付出很大的努力去保证线程安全。
这个时候就轮到Builder闪亮登场了!

2.建造者模式是什么?

  • 意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

  • 主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

  • 何时使用:一些基本部件不会变,而其组合经常变化的时候。

  • 如何解决:将变与不变分离开。

  • 关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。

  • 应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。 2、JAVA 中的 StringBuilder。

  • 优点: 1、建造者独立,易扩展。 2、便于控制细节风险。

  • 缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。

  • 使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。

  • 注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
    (上面这一块我是抄来的,哦,不是,是复制来的,我赖得抄了builder-pattern)

3.上面的例子怎么改?

一般有两种实现方式:

  • 通过Client, Director, Builder等形成建造者模式。
  • 静态内部类。
    第一种方式,我就不抄了,菜鸟教程讲得挺好,咱们来试试第二种:
package com.example.demo.entity;

public class Robot {
    private String manufacturer;
    private final String name;
    private String brand;
    private final long id;
    private double weight;
    private double width;
    private double height;

    private Robot(Builder builder) {
        this.brand = builder.brand;
        this.height = builder.height;
        this.manufacturer = builder.manufacturer;
        this.weight = builder.weight;
        this.width = builder.width;
        this.name = builder.name;
        this.id = builder.id;
    }
    
    public static class Builder {
        private String manufacturer;
        private final String name;
        private String brand;
        private final long id;
        private double weight;
        private double width;
        private double height;
        
        public Builder(long id, String name) {
            this.id = id;
            this.name = name;
        }
        
        public Builder setManufacturer(String manufacturer) {
            this.manufacturer = manufacturer;
            return this;
        }
        
        public Builder setBrand(String brand) {
            this.brand = brand;
            return this;
        }
        
        public Builder setHeight(double height) {
            this.height = height;
            return this;
        }
        
        public Builder setWidth(double width) {
            this.width = width;
            return this;
        }

        public Builder setWeight(double weight) {
            this.weight = weight;
            return this;
        }
        
        public Robot build() {
            return new Robot(this);
        }
    }

}

在Robot类中加入了静态类Builder,由Builder来构造Robot所需要的字段属性,默认id与name是必须传入的不可更改字段。然后再Robot中加入私有的构造器,传入的参数就是Builder对象。
当需要使用的时候,只需要

 Robot robot = new Robot.Builder(1234l, "Optimus Prime")
                .setBrand("Optimus Prime")
                .setHeight(12.23)
                .setManufacturer("TF")
                .setWeight(2345.6)
                .build();

这样我们就可以得到一个Robot对象了。

4.总结

建造者模式最明显的就是解决了参数太长太多容易出错的问题。当然还有说的视图与创建解耦,便于控制细节风险等。
但是也有缺点,例如要生成的对象的类具有了复杂的内部结构,还会需要额外的建造器,需要消耗性能等。
借用一句总结:
总的来说,在设计一个类的时候,当该类的构造器或者静态工厂方法有一定数量的参数时,Builder模式是一个不错的选择。 尤其是当一些参数是可选的,或者一些参数具有相同的类型的时候。Builder模式相对于伸缩构造器模式而言,客户端代码更容易编写和阅读;相对于JavaBeans模式而言,更安全。