具有许多参数的类,超出Builder模式

时间:2016-02-01 14:50:26

标签: java constructor builder

上下文

假设您有一个包含许多选项的组件来修改其行为。考虑一些数据表,包括一些排序,过滤,分页等。选项可以是isFilterableisSortabledefaultSortingKey等等。当然会有一个参数对象来封装所有这些,我们称之为TableConfiguration。当然,我们不希望拥有庞大的构造函数或一组伸缩构造函数,因此我们使用 builder TableConfigurationBuilder。示例用法可以是:

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

到目前为止,很多SO问题已经解决了这个问题。

前进

现在有大量的Tables,每个人都使用自己的TableConfiguration。但是,并非所有的“配置空间”都是统一使用的:假设大多数表都是可过滤的,而且大多数都是分页的。比方说,只有20种不同的配置选项组合才有意义且实际使用。根据DRY原则,这20种组合存在于以下方法中:

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

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

问题

如何管理这20个方法,以便开发人员添加新表可以轻松找到他们需要的配置组合,或者如果它还不存在则添加新表?

我已经使用了以上所有内容,如果我有一个现有的表来进行复制粘贴(“它与客户完全一样”),它的工作原理相当不错。但是,每当需要一些与众不同的东西时,很难弄明白:

  • 有没有一种方法正是我想做的? (问题A)
  • 如果没有,哪一个是最接近的? (问题B)

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

修改

在考虑下面的好答案时,我又想到了一件事: 以类型安全的方式对具有相同配置的表进行分组的加分点。换句话说,在查看表时,应该可以通过转到定义查找所有引用之类的内容找到所有“双胞胎”。

5 个答案:

答案 0 :(得分:3)

我认为如果您已经在使用构建器模式,那么坚持构建器模式将是最好的方法。使用最常用的TableConfiguration 构建的方法或枚举没有任何好处。

但是你有关于DRY的有效观点。为什么在几个不同的地方为几乎所有的构建者设置最常见的标志?

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

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

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

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();
    }
}

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

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

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

如果您希望TableConfiguration可以过滤,分页并且可以排序:

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

还有一个特殊的表配置,其中包含一个非常奇怪的设置,也是可排序的:

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

答案 1 :(得分:2)

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

  

有没有一种方法正是我想做的? (问题A)

是的,执行所需操作的方法是new TableConfigurationBuilder()。顺便说一句,我认为将构建器构造包设为私有并使其可以通过TableConfiguration中的静态方法访问更清晰,然后您只需调用TableConfiguration.builder()

  

如果没有,哪一个是最接近的? (问题B)

如果您已经有TableConfigurationTableConfigurationBuilder的实例,那么将它传递给构建器可能会很好,这样它就可以根据现有实例进行预配置。这允许您执行以下操作:

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

答案 2 :(得分:1)

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

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();
}

现在表创建看起来并不那么丑:

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

答案 3 :(得分:1)

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

例如,将表设置为 s orort和 r ead-only:

defaultTable().set("sr");

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

这可能适用于支持表重用的其他方案。拥有一个创建Customers表的方法,我们可以以一致的方式改变它:

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

可能通过提供分隔符来改进此DSL,这将分隔 set unset 操作。之前的样本将如下所示:

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

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

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

答案 4 :(得分:1)

如果我不知道那么我会做什么

假设:表中可能存在少于2 ^(配置标志的数量)合理配置。

  1. 找出当前使用的所有配置组合。
  2. 绘制图表或其他任何内容,找到群集。
  3. 找出异常值并非常认真地考虑为什么它们不适合这些集群:这是一个特殊情况,还是一个遗漏,或者只是懒惰(没有人实现对该表的全文搜索尚未)?
  4. 选择群集,仔细思考它们,将它们打包为具有描述性名称的方法,并从现在开始使用它们。
  5. 这解决了问题A:使用哪一个? 嗯,现在只有少数选项。问题B也是:如果我想要一些特别的东西? 不,你很可能不会