Factory还是Builder

每次看到框架中的工厂模式时,便会想起一个和它功能相近的模式——创建者模式。二者均属于创建型设计模式,都在帮助使用者更好地完成创建对象的工作。那么穿过浅层的印象,略微深入。它们为什么作为两种模式出现?二者有什么区别?围绕着两个问题,笔者将简要分享自己的拙见。

工厂模式示例

单一职责思想告诉我们,一个代码块应该专注于一件事情,如果某一类对象的创建流程较为复杂,并不只是简单地new()一下就可以,那么可以考虑使用工厂模式来解决。

以SpringFramework中BeanFactory创建Bean对象的工厂方法为例:

protected Object createBean(String beanName, BeanDefinition beanDefinition,Object[] args) throws BeansException {
        Object bean = null;
        try {
            bean = resolveBeforeInstantiation(beanName, beanDefinition);
            if (null != bean) {
                return bean;
            }

            bean = createBeanInstance(beanDefinition, beanName, args);
            // 给 Bean 填充属性
            applyPropertyValues(beanName, bean, beanDefinition);
            // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }
        registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
        addSingleton(beanName, bean);
        return bean;
    }

如上述代码所示,在SpringFramework中bean对象的创建就是一个相较于new()来讲复杂得多的场景。需要考虑对bean定义的修改,属性填充、初始化等等操作。

建造者模式示例

建造者模式针对的问题:

  1. 在对象创建时,必要的参数较多,由构造函数传入会较为臃肿。
  2. 成员变量之间有一定约束关系,比如三角形的两边之和大于第三边。或者说是依赖关系。
  3. 如果希望成员变量是不可变的,那么其set方法自然不能暴漏在外。
  4. 避免对象出现中间状态或者说无效状态,保证对象创建的原子性。在下文的示例中,如果使用set方式初始化,那么在全部参数被填充之前,对象是不可用的。这一现象在多线程中会导致未初始化的对象被用户获取的问题。

以构建一个三角形作为示例。

package com.caldarius.study.dp.builder;

public class Triangle {
    private int sideALength;
    private String sideAName;

    private int sideBLength;
    private String sideBName;

    private int sideCLength;
    private String sideCName;

    private Triangle(Builder builder) {
        this.sideALength = builder.sideALength;
        this.sideAName = builder.sideAName;
        this.sideBLength = builder.sideBLength;
        this.sideBName = builder.sideBName;
        this.sideCLength = builder.sideCLength;
        this.sideCName = builder.sideCName;
    }

    @Override
    public String toString() {
        return "Triangle{" +
                "sideALength=" + sideALength +
                ", sideAName='" + sideAName + '\'' +
                ", sideBLength=" + sideBLength +
                ", sideBName='" + sideBName + '\'' +
                ", sideCLength=" + sideCLength +
                ", sideCName='" + sideCName + '\'' +
                '}';
    }

    public static class Builder{
        private int sideALength;
        private String sideAName;

        private int sideBLength;
        private String sideBName;

        private int sideCLength;
        private String sideCName;
        public Triangle build(){
            //名字不能为null,不能为""
            if (sideAName == null || sideAName.isEmpty() || sideBName == null || sideBName.isEmpty() || sideCName == null || sideCName.isEmpty()) {
                throw new IllegalArgumentException(getSideNameLog());
            }
            //边长大于0
            if (sideALength <= 0 || sideBLength <=0 || sideCLength <=0) {
                throw new IllegalArgumentException(getSideLengthLog());
            }
            //任意两边之和大于第三边
            if (sideALength + sideBLength <= sideCLength) {
                throw new IllegalArgumentException(getSideLengthLog());
            }
            if (sideALength + sideCLength <= sideBLength) {
                throw new IllegalArgumentException(getSideLengthLog());
            }
            if (sideBLength + sideCLength <= sideALength) {
                throw new IllegalArgumentException(getSideLengthLog());
            }
            return new Triangle(this);
            
        }
        //
        public Builder  setSideALength(int sideALength) {
            if (sideALength <= 0) {
                throw new IllegalArgumentException("边长不能小于等于0"+sideALength);
            }
            this.sideALength = sideALength;
            //链式编程 -> 返回自身
            return this;
        }

        public Builder setSideAName(String sideAName) {
            if (sideAName == null || sideAName.isEmpty()) {
                throw new IllegalArgumentException("名字不能为null,不能为\"\""+sideAName);
            }
            this.sideAName = sideAName;
            return this;
        }

        public Builder setSideBLength(int sideBLength) {
            if (sideBLength <= 0) {
                throw new IllegalArgumentException("边长不能小于等于0" + sideBLength);
            }
            this.sideBLength = sideBLength;
            return this;
        }

        public Builder setSideBName(String sideBName) {
            if (sideBName == null || sideAName.isEmpty()) {
                throw new IllegalArgumentException("名字不能为null,不能为\"\""+sideBName);
            }
            this.sideBName = sideBName;
            return this;
        }

        public Builder setSideCLength(int sideCLength) {
            if (sideCLength <= 0) {
                throw new IllegalArgumentException("边长不能小于等于0"+sideCLength);
            }
            this.sideCLength = sideCLength;
            return this;
        }

        public Builder setSideCName(String sideCName) {
            if (sideCName == null || sideCName.isEmpty()) {
                throw new IllegalArgumentException("名字不能为null,不能为\"\""+sideCName);
            }
            this.sideCName = sideCName;
            return this;
        }

        private String getSideLengthLog() {
            return "sideALength=" + sideALength +
                    ", sideCLength=" + sideCLength +
                    ", sideBLength=" + sideBLength;
        }
        private String getSideNameLog() {
            return "sideAName=" + sideAName +
                    ", sideCName=" + sideCName +
                    ", sideBName=" + sideBName;
        }
    }

    public static void main(String[] args) {
        Triangle triangle = new Builder()
                .setSideALength(3)
                .setSideAName("a")
                .setSideBLength(4)
                .setSideBName("b")
                .setSideCLength(5)
                .setSideCName("C").build();
        System.out.println(triangle);

        Triangle triangle2 = new Builder()
                .setSideALength(1)
                .setSideAName("a")
                .setSideBLength(2)
                .setSideBName("b")
                .setSideCLength(3)
                .setSideCName("C").build();
    }
}
/*
output:
Triangle{sideALength=3, sideAName='a', sideBLength=4, sideBName='b', sideCLength=5, sideCName='C'}
Exception in thread "main" java.lang.IllegalArgumentException: sideALength=1, sideCLength=3, sideBLength=2
 */

区别与联系

从上文的代码示例可以直观感觉到,相对于工厂模式创建不同但是相关类型的对象,这里的复杂度我认为主要是创建什么类型的对象,以及需要进行什么样的处理。

建造者模式更多聚焦于某一种类型创建的复杂度,主要体现在初始化的参数上。

二者之间的区别是工厂更多关注创建的流程,建造者更多关注具体的细节。

网上有一个经典的例子很好地解释了两者的区别。
顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。

建议从要解决的问题出发,灵活选择模式或者创造自己的模式。