Class 有很多参数,超出了 Builder 模式

Class with many parameters, beyond the Builder pattern

上下文

假设您有一个组件,有很多选项可以修改它的行为。想想 table 的数据,进行一些排序、过滤、分页等。然后选项可以是 isFilterableisSortabledefaultSortingKey 等。当然会有一个参数对象来封装所有这些,我们称之为TableConfiguration。当然我们不想有一个巨大的构造函数,或者一组伸缩构造函数,所以我们使用了一个builderTableConfigurationBuilder。示例用法可以是:

TableConfiguration config = new TableConfigurationBuilder().sortable().filterable().build();   

到目前为止一切顺利,大量 SO 问题已经解决了这个问题。

前进

现在有很多 Tables,每个都使用自己的 TableConfiguration。然而,并非所有 "configuration space" 都被统一使用:假设大多数 table 是可过滤的,并且其中大部分是分页的。比方说,只有 20 种不同的配置选项组合有意义并实际使用。根据 DRY 原则,这 20 种组合存在于如下方法中:

public TableConfiguration createFilterable() {
  return new TableConfigurationBuilder().filterable().build();
}

public TableConfiguration createFilterableSortable() {
  return new TableConfigurationBuilder().filterable().sortable().build();
}

问题

如何管理这20个方法,让开发者添加新的table可以方便的找到自己需要的配置组合,或者不存在的就自己添加?

以上所有我已经使用过,如果我有一个现有的 table 来复制粘贴 ("it's exactly like Customers"),它工作得相当好。但是,每次要求的时候都有些不寻常的地方,让人摸不着头脑:

我试图给这些方法一些非常具有描述性的名称来表达内部构建的配置选项,但它并不能很好地扩展...

编辑

在思考下面的好答案时,我又想到一件事: 以类型安全的方式将具有相同配置的 table 分组的奖励积分。换句话说,在查看 table 时,应该可以通过 go to definitionfind 这样的方式找到它的所有 "twins"所有引用.

如果几乎所有配置选项都是布尔值,那么您可以将它们组合在一起:

public static int SORTABLE = 0x1;
public static int FILTERABLE = 0x2;
public static int PAGEABLE = 0x4;

public TableConfiguration createTable(int options, String sortingKey) {
  TableConfigurationBuilder builder = new TableConfigurationBuilder();
  if (options & SORTABLE != 0) {
    builder.sortable();
  }
  if (options & FILTERABLE != 0) {
    builder.filterable();
  }
  if (options & PAGEABLE != 0) {
    builder.pageable();
  }
  if (sortingKey != null) {
    builder.sortable();
    builder.setSortingKey(sortingKey);
  }
  return builder.build();
}

现在table创作看起来不那么丑了:

TableConfiguration conf1 = createTable(SORTEABLE|FILTERABLE, "PhoneNumber");

有一个 配置字符串 怎么样?这样,您可以以简洁但仍可读的方式对 table 设置进行编码。

例如,将 table 设置为 sortable 和 read-仅:

defaultTable().set("sr");

在某种程度上,这些字符串类似于命令行界面。

这可能适用于支持 table 重用的其他场景。有了创建客户 table 的方法,我们可以以一致的方式更改它:

customersTable().unset("asd").set("qwe");

可能,这个 DSL 甚至可以通过提供分隔符来改进,分隔 setunset 操作。之前的示例将如下所示:

customersTable().alter("asd|qwe");

此外,这些配置字符串可以从文件中加载,从而无需重新编译即可配置应用程序。

至于帮助新开发人员,我可以从一个可以轻松记录的很好分离的子问题中看到好处。

我会删除调用构建器的多个方法的便捷方法。像这样的流畅构建器的全部意义在于,您不需要为所有可接受的组合创建 20 种方法。

Is there a method doing exactly what I want? (problem A)

是的,可以满足您要求的方法是 new TableConfigurationBuilder()。顺便说一句,我认为将构建器构造函数包设为私有并通过 TableConfiguration 中的静态方法访问它会更干净,然后您只需调用 TableConfiguration.builder().

If not, which one is the closest one to start from? (problem B)

如果您已经有一个 TableConfigurationTableConfigurationBuilder 的实例,最好将它传递给构建器,这样它就可以根据现有实例进行预配置。这使您可以执行以下操作:

TableConfiguration.builder(existingTableConfig).sortable(false).build()

如果我不知道的话我会怎么做

假设:table.

的合理配置可能少于 2^(# of config flags)
  1. 找出当前使用的所有配置组合。
  2. 绘制图表或其他任何东西,找到聚类。
  3. 找到离群值并认真思考为什么它们不适合这些集群:这真的是一个特例,还是一个遗漏,或者只是懒惰(没有人为此实施全文搜索 table )?
  4. 选择集群,仔细考虑,将它们打包为具有描述性名称的方法,并从现在开始使用它们。

这解决了问题A:使用哪一个? 嗯,现在只有少数几个选项。还有问题 B:如果我想要一些特别的东西? 不,你很可能不会

我认为如果您已经在使用构建器模式,那么坚持使用构建器模式将是最好的方法。 build 最常用 TableConfiguration.

的方法或枚举没有任何好处

不过,关于 DRY,你的观点很有道理。为什么要在许多不同的地方为几乎每个构建器设置最常见的标志?

因此,您将需要封装最常用标志的设置(不再重复),同时仍允许在此通用基础上设置额外标志。此外,您还需要支持特殊情况。在您的示例中,您提到大多数 table 都是可过滤和分页的。

因此,虽然构建器模式为您提供了灵活性,但它会让您重复最常见的设置。为什么不制作专门的默认构建器来为您设置最常见的标志?这些仍然允许您设置额外的标志。对于特殊情况,您可以使用老式的构建器模式。

定义所有设置并构建实际对象的抽象构建器的代码可能如下所示:

public abstract class AbstractTableConfigurationBuilder
                      <T extends AbstractTableConfigurationBuilder<T>> {

    public T filterable() {
        // set filterable flag
        return (T) this;
    }

    public T paginated() {
        // set paginated flag
        return (T) this;
    }

    public T sortable() {
        // set sortable flag
        return (T) this;
    }

    public T withVeryStrangeSetting() {
        // set very strange setting flag
        return (T) this;
    }

    // TODO add all possible settings here

    public TableConfiguration build() {
        // build object with all settings and return it
    }
}

这将是基本构建器,它什么都不做:

public class BaseTableConfigurationBuilder 
    extends AbstractTableConfigurationBuilder<BaseTableConfigurationBuilder> {
}

包含 BaseTableConfigurationBuilder 是为了避免在使用构建器的代码中使用泛型。

那么,你可以有专门的建设者:

public class FilterableTableConfigurationBuilder 
    extends AbstractTableConfigurationBuilder<FilterableTableConfigurationBuilder> {

    public FilterableTableConfigurationBuilder() {
        super();
        this.filterable();
    }
}

public class FilterablePaginatedTableConfigurationBuilder 
    extends FilterableTableConfigurationBuilder {

    public FilterablePaginatedTableConfigurationBuilder() {
        super();
        this.paginated();
    }
}

public class SortablePaginatedTableConfigurationBuilder 
    extends AbstractTableConfigurationBuilder
            <SortablePaginatedTableConfigurationBuilder> {

    public SortablePaginatedTableConfigurationBuilder() {
        super();
        this.sortable().paginated();
    }
}

我们的想法是让构建器设置最常见的标志组合。你可以创建一个层次结构或者它们之间没有继承关系,你的电话。

然后,您可以使用您的构建器创建所有组合,而无需自己重复。例如,这将创建一个可过滤和分页的 table 配置:

TableConfiguration config = 
    new FilterablePaginatedTableConfigurationBuilder()
       .build();

如果您希望 TableConfiguration 可过滤、分页和排序table:

TableConfiguration config = 
    new FilterablePaginatedTableConfigurationBuilder()
       .sortable()
       .build();

还有一个特殊的 table 配置,其中有一个非常奇怪的设置,也是 sortable:

TableConfiguration config = 
    new BaseTableConfigurationBuilder()
       .withVeryStrangeSetting()
       .sortable()
       .build();