Managing Constructors with Many Parameters in Java

Managing constructors with many parameters in Java

The Builder Design Pattern might help. Consider the following example

public class StudentBuilder
{
private String _name;
private int _age = 14; // this has a default
private String _motto = ""; // most students don't have one

public StudentBuilder() { }

public Student buildStudent()
{
return new Student(_name, _age, _motto);
}

public StudentBuilder name(String _name)
{
this._name = _name;
return this;
}

public StudentBuilder age(int _age)
{
this._age = _age;
return this;
}

public StudentBuilder motto(String _motto)
{
this._motto = _motto;
return this;
}
}

This lets us write code like

Student s1 = new StudentBuilder().name("Eli").buildStudent();
Student s2 = new StudentBuilder()
.name("Spicoli")
.age(16)
.motto("Aloha, Mr Hand")
.buildStudent();

If we leave off a required field (presumably name is required) then we can have the Student constructor throw an exception.
And it lets us have default/optional arguments without needing to keep track of any kind of argument order, since any order of those calls will work equally well.

Many parameters in java constructor

As others already said in comments, using the Builder Pattern would be an option. But if not done properly, that introduces the risk of creating incomplete objects.

But there are more ways to improve your design. E.g. you pass names and surnames (and a full name in case of the director - why only there?) as separate Strings. I'd create a PersonName class that encapsulates these different naming elements, so your constructor becomes:

public Book(String title, 
PersonName directorName,
String type,
int issueYear,
List<PersonName> actors) {
...
}

Looks better and makes naming issues more consistent.

And of course, rename that class to be Movie instead of Book.

Constructors with too many parameters

If your subclasses vary only in the parameters passed to the superclass, you might be looking for the Builder Pattern. A builder for the superclass lets you pass in whatever parameters you need without cluttering your constructor, and if you want subclasses for readability, you can just wrap a call to the builder and return its result from the subclass constructors.

Constructor requiring many input arguments

If there are really that many parameters, it could that your class is trying to be too many things at the same time. Take this as a hint that it could be broken down into smaller classes.

Alternatively, you could make the constructor package private, and use a builder in the same package to instantiate it.

How many constructor arguments is too many?

Two design approaches to consider

The essence pattern

The fluent interface pattern

These are both similar in intent, in that we slowly build up an intermediate object, and then create our target object in a single step.

An example of the fluent interface in action would be:

public class CustomerBuilder {
String surname;
String firstName;
String ssn;
public static CustomerBuilder customer() {
return new CustomerBuilder();
}
public CustomerBuilder withSurname(String surname) {
this.surname = surname;
return this;
}
public CustomerBuilder withFirstName(String firstName) {
this.firstName = firstName;
return this;
}
public CustomerBuilder withSsn(String ssn) {
this.ssn = ssn;
return this;
}
// client doesn't get to instantiate Customer directly
public Customer build() {
return new Customer(this);
}
}

public class Customer {
private final String firstName;
private final String surname;
private final String ssn;

Customer(CustomerBuilder builder) {
if (builder.firstName == null) throw new NullPointerException("firstName");
if (builder.surname == null) throw new NullPointerException("surname");
if (builder.ssn == null) throw new NullPointerException("ssn");
this.firstName = builder.firstName;
this.surname = builder.surname;
this.ssn = builder.ssn;
}

public String getFirstName() { return firstName; }
public String getSurname() { return surname; }
public String getSsn() { return ssn; }
}
import static com.acme.CustomerBuilder.customer;

public class Client {
public void doSomething() {
Customer customer = customer()
.withSurname("Smith")
.withFirstName("Fred")
.withSsn("123XS1")
.build();
}
}

Handling more than 7 Parameters

There shouldn't be anything stopping you from passing 7 parameters to a constructor, if that's what you want. I don't know if there's a maximum number of parameters that can be passed to a method in Java, but it's certainly higher than 7 if there is a max.

When you create a class and its public methods, you're creating an interface on how to use and access that class. So technically what you've done so far is correct. Is it the "right way" to ask the client of a class to pass in arguments? That's up to you, the designer of the interface.

My first instinct when I saw 7 parameters being passed was to silently ask "Is there some relationship between some or all of these parameters that might mean they'd go together well in a class of their own?" That might be something you address as you look at your code. But that's a question of design, not one of correctness.

Too many constructor arguments in Spring Beans

You can make your steps Spring beans as prototype beans (since they are stateful, and you want a different instance every time), and inject Provider<Step> in your Task beans (which, if I understand correctly, can be singleton beans).

For example:

public class Step1 {
private Bean1 bean1;
private Bean2 bean2;

private final String someValue;
private final String someOtherValue;

public Step(String someValue, String someOtherValue) {
this.someValue = someValue;
this.someOtherValue = someOtherValue;
}

@Autowired
public void setBean1(Bean1 bean1) {
this.bean1 = bean1;
}

@Autowired
public void setBean2(Bean2 bean2) {
this.bean2 = bean2;
}

do() {
// ...
}
}

In your configuration class, you then define The various Steps as beans, with methods expecting the needed arguments:

@Bean
@Scope("prototype")
public Step1 step1(String someValue, String someOtherValue) {
return new Step(someValue, someOtherValue);
}

And in the Task bean, you inject an ObjectProvider<Step1>:

private ObjectProvider<Step1> stepProvider;

public Service(ObjectProvider<Step1> step1Provider) {
this.stepProvider = stepProvider;
}

public void start() {
Step1 step1 = step1Provider.getObject("a", "b");
step1.do();
}

Class with many parameters, beyond the Builder pattern

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

Solutions for too many parameters warning

There are some techniques reduce the # of parameters;

  1. Use minimized methods (break the method into multiple methods, each which require only a subset of the parameters)
  2. Use utility classes (helper classes) to hold group of parameters (typically static member classes)
  3. adapt the Builder pattern from object construction to method invocation.
  4. Try to reduce the data flow between separate packages with better architectural design.

Refer some standard java book;

  • Java: How To Program
  • Head First Java
  • Effective Java

Also try to learn design patterns it'll extremely useful as best coding practices.

  • Head First Design Patterns


Related Topics



Leave a reply



Submit