Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class with many parameters, beyond the Builder pattern

Context

Suppose you have a component with a great many options to modify its behavior. Think a table of data with some sorting, filtering, paging, etc. The options could then be isFilterable, isSortable, defaultSortingKey, etc etc. Of course there will be a parameter object to encapsulate all of these, let's call it TableConfiguration. Of course we don't want to have a huge constructor, or a set of telescopic constructors, so we use a builder, TableConfigurationBuilder. The example usage could be:

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

So far so good, a ton of SO questions deals with this already.

Moving forward

There is now a ton of Tables and each of them uses its own TableConfiguration. However, not all of the "configuration space" is used uniformly: let's say most of the tables is filterable, and most of those are paginated. Let's say, there are only 20 different combinations of configuration options that make sense and are actually used. In line with the DRY principle, these 20 combinations live in methods like these:

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

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

Question

How to manage these 20 methods, so that developers adding new tables can easily find the configuration combination they need, or add a new one if it does not exist yet?

All of the above I use already, and it works reasonably well if I have an existing table to copy-paste ("it's exactly like Customers"). However, every time something out of the ordinary is required, it's hard to figure out:

  • Is there a method doing exactly what I want? (problem A)
  • If not, which one is the closest one to start from? (problem B)

I tried to give the methods some very descriptive names to express what configuration options are being built in inside, but it does not scale really well...

Edit

While thinking about the great answers below, one more thing occurred to me: Bonus points for grouping tables with the same configuration in a type-safe way. In other words, while looking at a table, it should be possible to find all its "twins" by something like go to definition and find all references.

like image 659
vektor Avatar asked Feb 01 '16 14:02

vektor


People also ask

Can a constructor have one or more parameters?

You can have constructors with multiple parameters, but also have a constructor with only the minimum parameters.

How many parameters can be passed in a constructor in Java?

This method has four parameters: the loan amount, the interest rate, the future value and the number of periods.

How many arguments can be passed to a constructor?

No parameters can be passed in the default constructor. The answer to the above question is zero or no parameters. We cannot pass parameters to a default constructor. The only purpose of a default constructor is to instantiate the object.

What is the real life use case of the builder design pattern?

In the Real World The simplest real world example of the builder pattern, which most of us are familiar with, is when we make (or order) a pizza. The pizza toppings cannot be added in any random order or the whole thing is likely to come out a mess. Instead, there is a step-by-step process that is followed.


1 Answers

I think that if you are already using the builder pattern, then sticking to the builder pattern would be the best approach. There's no gaining in having methods or an enum to build the most frequently used TableConfiguration.

You have a valid point regarding DRY, though. Why setting the most common flags to almost every builder, in many different places?

So, you would be needing to encapsulate the setting of the most common flags (to not repeat yourself), while still allowing to set extra flags over this common base. Besides, you also need to support special cases. In your example, you mention that most tables are filterable and paginated.

So, while the builder pattern gives you flexibility, it makes you repeat the most common settings. Why not making specialized default builders that set the most common flags for you? These would still allow you to set extra flags. And for special cases, you could use the builder pattern the old-fashioned way.

Code for an abstract builder that defines all settings and builds the actual object could look something like this:

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
    }
}

And this would be the base builder, which does nothing:

public class BaseTableConfigurationBuilder 
    extends AbstractTableConfigurationBuilder<BaseTableConfigurationBuilder> {
}

Inclusion of a BaseTableConfigurationBuilder is meant to avoid using generics in the code that uses the builder.

Then, you could have specialized builders:

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

The idea is that you have builders that set the most common combinations of flags. You could create a hierarchy or have no inheritance relation between them, your call.

Then, you could use your builders to create all combinations, without repeting yourself. For example, this would create a filterable and paginated table configuration:

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

And if you want your TableConfiguration to be filterable, paginated and also sortable:

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

And a special table configuration with a very strange setting that is also sortable:

TableConfiguration config = 
    new BaseTableConfigurationBuilder()
       .withVeryStrangeSetting()
       .sortable()
       .build();
like image 180
fps Avatar answered Nov 15 '22 17:11

fps