假设您有一个包含许多选项的组件来修改其行为。考虑一些数据表,包括一些排序,过滤,分页等。选项可以是isFilterable
,isSortable
,defaultSortingKey
等等。当然会有一个参数对象来封装所有这些,我们称之为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个方法,以便开发人员添加新表可以轻松找到他们需要的配置组合,或者如果它还不存在则添加新表?
我已经使用了以上所有内容,如果我有一个现有的表来进行复制粘贴(“它与客户完全一样”),它的工作原理相当不错。但是,每当需要一些与众不同的东西时,很难弄明白:
我尝试给这些方法一些非常具有描述性的名称,以表达内部构建的配置选项,但它不能很好地扩展...
在考虑下面的好答案时,我又想到了一件事: 以类型安全的方式对具有相同配置的表进行分组的加分点。换句话说,在查看表时,应该可以通过转到定义和查找所有引用之类的内容找到所有“双胞胎”。
答案 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)
如果您已经有TableConfiguration
或TableConfigurationBuilder
的实例,那么将它传递给构建器可能会很好,这样它就可以根据现有实例进行预配置。这允许您执行以下操作:
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 ^(配置标志的数量)合理配置。
这解决了问题A:使用哪一个? 嗯,现在只有少数选项。问题B也是:如果我想要一些特别的东西? 不,你很可能不会。