生成器模式和大量必需参数

2022-08-31 12:32:40

到目前为止,我使用以下生成器模式的实现(而不是此处描述的实现):

public class Widget {
    public static class Builder {
        public Builder(String name, double price) { ... }
        public Widget build() { ... }
        public Builder manufacturer(String value) { ... }
        public Builder serialNumber(String value) { ... }
        public Builder model(String value) { ... }
    }

    private Widget(Builder builder) { ... }
}

这适用于我遇到的大多数情况,在这些情况下,我需要使用各种必需/强制和可选参数构建复杂对象。但是,我最近一直在努力理解当你的所有参数都是强制性的(或者至少绝大多数都是强制性的)时,这种模式是如何受益的。

解决此问题的一种方法是对传递给它们自己的类的参数进行逻辑分组,以减少传递给生成器构造函数的参数数。

例如,而不是:

Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
                           .addOptional(opt9)
                           .build();

分组如下:

Object1 group1 = new Object1(req1, req2, req3, req4);
Object2 group2 = new Object2(req5, req6);

Widget example2 = new Widget.Builder(group1, group2, req7, req8)
                            .addOptional(opt9)
                            .build();

虽然拥有单独的对象可以简化很多事情,但如果一个人不熟悉代码,它也会使事情变得有点难以理解。我考虑的一件事是将所有参数移动到它们自己的方法中,然后对方法中的必需参数执行验证。addParam(param)build()

什么是最佳实践,有没有一种我没有考虑过的更好的方法?


答案 1

如果您有许多必需参数,则可以使用步骤生成器。简而言之:为每个必需参数定义一个接口,并且生成器方法返回下一个必需生成器接口或可选方法的生成器本身。生成器仍然是实现所有接口的单个类。

interface StepB {
    StepBuilder b(String b);
}

interface StepA {
    StepB a(String a);
}

final class StepBuilder implements StepA, StepB {
    private String a;
    private String b;
    private String c = "";

    private StepBuilder() {
    }

    static StepA with() {
      return new StepBuilder();
    }

    // mandatory, from StepA
    @Override
    StepB a(String a) {
        this.a = a;
        return this;
    }

    // mandatory, from StepB
    @Override
    StepBuilder b(String b) {
        this.b = b;
        return this;
    }

    // optional
    StepBuilder c(String c) {
        this.c = c;
        return this;
    }

    Product build() {
        return new Product(a, b, c);
    }
}

用法:

StepBuilder.with().a("hello").b("world").build();

// or with the optional parameter c
StepBuilder.with().a("hello").b("world").c("!").build();

像Kotlin和Scala这样的语言在这里更方便,因为它们提供了具有默认值的命名参数。


答案 2

但是,我最近一直在努力理解当你的所有参数都是强制性的(或者至少绝大多数都是强制性的)时,这种模式是如何受益的。

流畅的生成器模式仍然是有益的:

  1. 它更具可读性 - 它有效地允许命名参数,以便调用不仅仅是一长串未命名参数

  2. 它是无序的 - 这使您可以将参数组合到逻辑组中,可以作为单个生成器 setter 调用的一部分,也可以简单地允许您使用自然顺序来调用生成器 setter 方法,这些方法最能说明此特定实例化。


Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
                               .addOptional(opt9)
                               .build();

分组如下:

Object1 group1  = new Object1(req1, req2, req3, req4);
Object2 group2  = new Object2(req5, req6);
Widget example2 = new Widget.Builder(group1, group2, req7, req8)
                            .addOptional(opt9)
                            .build();

虽然拥有单独的对象可以简化很多事情,但如果一个人不熟悉代码,它也会使事情变得有点难以理解。我考虑的一件事是将所有参数移动到它们自己的方法中,然后对方法中的必需参数执行验证。addParam(param)build()

我会在适当或自然的时候选择杂交种。它不必全部在构造函数中,或者每个参数都有自己的addParam方法。Builder 使您可以灵活地执行一个、另一个、中间或组合:

Widget.Builder builder = new Widget.Builder(Widget.BUTTON);

builder.withWidgetBackingService(url, resource, id);
builder.withWidgetStyle(bgColor, lineWidth, fontStyle);
builder.withMouseover("Not required");

Widget example = builder.build();