JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?

Java极客  |  作者  /  铿然一叶
这是Java极客的第 66 篇原创文章

相关阅读:

JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA基础(三)ClassLoader实现热加载
HikariPool源码(二)设计思想借鉴
人在职场(一)IT大厂生存法则


1. 创建对象实例的方式

Builder模式,工厂模式,new都可以用于创建对象实例,它们三者的应用场景和区别如下:

创建方式 应用场景
new 创建逻辑简单,没有影响实例生成的参数或条件
工厂模式 根据参数或条件生成不同实例
Builder模式 创建实例时可以设置不同的参数组合,每种组合可以表现不同的行为

在选择以上三种方式创建对象时,不要考虑A方式能不能替代B方式,因为如果它们只是替代关系,那就没有体现它们各自的价值,只有当一个事物有不可替代性时,才有其价值,所以应该考虑的是:我是不是不得不选择它

如果A,B互相可替代,那么就使用最简单的方式,比如new能搞定,就不要硬整一个工厂模式,不要为了用模式而用模式

1.1. 根据参数或条件生成不同实例

如在JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则 中的例子,税策略工厂根据不同的税类型生成了不同的税策略实例去算税。


在这种场景下用new创建实例是不合适的, 不得不使用工厂模式来创建实例。

1.2. 不同的参数组合表现不同的行为

Builder模式创建实例时可以使用不同的参数,使得最后创建的实例有不同的行为,举个例子,IPhone有时针对不同的颜色区分不同的具体机型参数,例如(举例用,不是完全和实际一致):

颜色 摄像头个数 MAX版 内存
红色 2,3个摄像头可选 普通版和MAX版 256G和512G
黑色 仅有2个摄像头 普通版 128G,256G和512G

此时用工厂模式就不合适,因为组合参数有很多种,每种组合都写一个创建方法很冗余,更好的处理方式是在创建实例时可以设置参数,由调用者自行设置需要的参数。

下面举例说明。

1.3. 创建者模式举例

1.3.1. 定义合法参数

public enum Color {
    RED, BLACK
}

public enum Model {
    NORMAL, MAX
}

public enum CameraNum {
    TWO, THREE
}

public enum MemorySize {
    G128, G256, G512
}
复制代码

1.3.2. IPhone类

public class IPhone {

    private Color color;
    private Model model;
    private CameraNum cameraNum;
    private MemorySize memorySize;

    public IPhone(Color color, Model model, CameraNum cameraNum, MemorySize memorySize) {
        this.color = color;
        this.model = model;
        this.cameraNum = cameraNum;
        this.memorySize = memorySize;
    }

    // 1.独立的创建者,职责更清晰。2. 作为内部类,功能更内聚 3.不需要访问IPhone的成员变量-【静态】内部类 
    public static final class Builder {
        private Color color;
        private Model model;
        private CameraNum cameraNum;
        private MemorySize memorySize;

        public Builder setColor(Color color) {
            this.color = color;
            return this;
        }

        public Builder setModel(Model model) {
            this.model = model;
            return this;
        }

        public Builder setCameraNum(CameraNum cameraNum) {
            this.cameraNum = cameraNum;
            return this;
        }

        public Builder setMemorySize(MemorySize memorySize) {
            this.memorySize = memorySize;
            return this;
        }

        public IPhone build() {
            IPhone iPhone = new IPhone(this.color, this.model, this.cameraNum, this.memorySize);
            return iPhone;
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("color=").append(color.toString()).append(", ");
        builder.append("model=").append(model.toString()).append(", ");
        builder.append("cameraNum=").append(cameraNum.toString()).append(", ");
        builder.append("memorySize=").append(memorySize.toString());
        return builder.toString();
    }
}
复制代码

1.3.3. 测试类

public class BuilderDemo {
    public static void main(String[] args) {
        IPhone.Builder builder = new IPhone.Builder();
        // 红色
        IPhone redPhone = builder.setColor(Color.RED)
                .setCameraNum(CameraNum.THREE)
                .setMemorySize(MemorySize.G512)
                .setModel(Model.MAX).build();

        // 黑色
        IPhone blackPhone = builder.setColor(Color.BLACK)
                .setCameraNum(CameraNum.THREE)
                .setMemorySize(MemorySize.G128)
                .setModel(Model.NORMAL).build();

        System.out.println(redPhone);
        System.out.println(blackPhone);
    }
}
复制代码

输出结果:

color=RED, model=MAX, cameraNum=THREE, memorySize=G512
color=BLACK, model=NORMAL, cameraNum=THREE, memorySize=G128
复制代码

至此,创建者模式完成,一般的创建者设计模式举例,包括大厂的代码(例如Android中的JobInfo.Builder)也就到此为止了,但不知细心的读者是否发现了例子中存在的问题?

例子中的错误为:创建了一个错误的黑色IPhone,按照前面表格说明,并没有3个摄像头的黑色IPhone,但却创建了一个这样的黑色IPhone。

这种错误的解决方式有:

  1. 运行时抛出异常,这是最差的方式。
  2. 编译时异常,在这个场景无法做到。
  3. 增加API说明,让开发者参考文档避免错误。
  4. 通过不同的Builder创建对象实例,在编码时避免错误。

下面,我们就通过第4种方式在早期避免犯错

1.4. 创建者模式优化,编码避免创建错误

1.4.1. 新增适用红色IPhone的参数定义

// 用于红色IPhone,限定内存范围
public enum RedMemorySize {
    G256 {
        @Override public MemorySize getMemorySize() {
            return MemorySize.G256;
        }
    },
    G512 {
        @Override public MemorySize getMemorySize() {
            return MemorySize.G512;
        }
    };

    public abstract MemorySize getMemorySize();
}
复制代码

1.4.2. 定义不同的Builder创建不同的Iphone

public class IPhone {

    // 成员定义不变
    private Color color;
    private Model model;
    private CameraNum cameraNum;
    private MemorySize memorySize;

    // 构造器不变
    public IPhone(Color color, Model model, CameraNum cameraNum, MemorySize memorySize) {
        this.color = color;
        this.model = model;
        this.cameraNum = cameraNum;
        this.memorySize = memorySize;
    }

    // 黑色IPhone Builder
    public static final class BlackBuilder {
        private MemorySize memorySize;

        // 其他参数没得选,只需保留此方法
        public BlackBuilder setMemorySize(MemorySize memorySize) {
            this.memorySize = memorySize;
            return this;
        }

        public IPhone build() {
            // 限定的参数直接传入
            IPhone iPhone = new IPhone(Color.BLACK, Model.NORMAL, CameraNum.TWO, this.memorySize);
            return iPhone;
        }
    }

    // 红色IPhone Builder
    public static final class RedBuilder {
        private Model model;
        private CameraNum cameraNum;
        private MemorySize memorySize;

        public RedBuilder setModel(Model model) {
            this.model = model;
            return this;
        }

        public RedBuilder setCameraNum(CameraNum cameraNum) {
            this.cameraNum = cameraNum;
            return this;
        }

        // 注意这里入参是RedMemorySize,用于限定内存范围只能是256G和512G
        public RedBuilder setMemorySize(RedMemorySize memorySize) {
            this.memorySize = memorySize.getMemorySize();
            return this;
        }

        public IPhone build() {
            // 限定的参数直接传入
            IPhone iPhone = new IPhone(Color.RED, this.model, this.cameraNum, this.memorySize);
            return iPhone;
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("color=").append(color.toString()).append(", ");
        builder.append("model=").append(model.toString()).append(", ");
        builder.append("cameraNum=").append(cameraNum.toString()).append(", ");
        builder.append("memorySize=").append(memorySize.toString());
        return builder.toString();
    }
}
复制代码

1.4.3. 测试代码

public class BuilderDemo {
    public static void main(String[] args) {
        // 红色专有Builder,在开发时即可避免创建错误对象
        IPhone.RedBuilder redBuilder = new IPhone.RedBuilder();
        IPhone redPhone = redBuilder.setCameraNum(CameraNum.THREE)
                .setMemorySize(RedMemorySize.G512)
                .setModel(Model.MAX).build();

        // 黑色专有Builder,在开发时即可避免创建错误对象
        IPhone.BlackBuilder blackBuilder = new IPhone.BlackBuilder();
        IPhone blackPhone = blackBuilder.setMemorySize(MemorySize.G128).build();

        System.out.println(redPhone);
        System.out.println(blackPhone);
    }
}
复制代码

输出:

color=RED, model=MAX, cameraNum=THREE, memorySize=G512
color=BLACK, model=NORMAL, cameraNum=TWO, memorySize=G128
复制代码

可以看到,这时创建的黑色IPhone是正确的。

1.5. 创建者模式经典范式

至此,我们可以总结出创建者模式的经典范式:

  1. 创建者和要创建对象分离,这样可解耦,职责更清晰
  2. 创建者可作为创建对象的内部类(通常是静态内部类),这样更内聚,使得很容易找到创建者。
  3. 由于不同的条件/参数组合创建出的对象实例有不同行为,错误组合可能导致错误结果时,可以通过不同的Builder来创建对象,在编码时就避免犯错。
  4. 通过链式创建方式使得代码更简洁

2. 总结

  1. 每个设计模式有不能被替代的用途,如果发现能互相替代时,就用最简单的那种。
  2. 尽早避免编码犯错能使代码更健壮,纠错成本更低,所以应尽可能的在前期避免错误发生。

end.


<--阅过留痕,左边点赞!


猜你喜欢

转载自juejin.im/post/5eae20e35188256d5324c81b