JavaWorld in OO: Getters / Setters vs Builder

Story:

I found this article on JavaWorld where Allen Golub explains an alternative to Getters / Setters that supports the principle that the implementation of an object should (its sample code can also be found below).

It is explained that the Name / EmployeeId / Money classes must have a constructor that takes one line - the reasoning is that if you enter it as an int , and then you need to change it to a long , you will have to change all uses of this class, and with this template you do not need.

Question 1:

I was wondering: doesn't this just translate the problem into parsing String parameters that are thrown? For example, if all the code using EmployeeId (obtained from Exporter ) parses String into int , and suddenly you start exporting long values, you need to change so much usage exactly ... and if you start parsing it as long , you might have to change to double (even if that doesn't make sense for id) ... and if you cannot be sure what to parse String into, you cannot implement anything.

Question 2:

In addition to this question, I have one more thing: I understand that the article is more than seven years old, so can someone point me to some recent reviews regarding OO-design, and in particular ideas about getter / setter and implementation, hiding the debate?

Listing 1. Employee: Context Builder


  public class Employee { private Name name; private EmployeeId id; private Money salary; public interface Exporter { void addName ( String name ); void addID ( String id ); void addSalary ( String salary ); } public interface Importer { String provideName(); String provideID(); String provideSalary(); void open(); void close(); } public Employee( Importer builder ) { builder.open(); this.name = new Name ( builder.provideName() ); this.id = new EmployeeId( builder.provideID() ); this.salary = new Money ( builder.provideSalary(), new Locale("en", "US") ); builder.close(); } public void export( Exporter builder ) { builder.addName ( name.toString() ); builder.addID ( id.toString() ); builder.addSalary( salary.toString() ); } //... } 
+8
java oop design-patterns builder getter-setter
source share
4 answers

Question 1: String parsing seems weird. IMHO, you can do so much to anticipate future improvements. Either you use the long parameter from the very beginning, or you can add additional constructors later. Alternatively, you can introduce an extensible parameter class. See below.

Question 2: There are several scenarios in which the builder pattern may be useful.

  • Creating a complex object

    When you are dealing with a very complex object that has many properties that you would prefer to set only once when creating the object, doing this with regular constructors can become difficult to read, since the constructor will have a long list of parameters. Publishing it as an API is not very good because everyone will have to carefully read the documentation and make sure they do not confuse the parameters.

    Instead, when you offer a builder, you should deal with a (private) constructor accepting all the arguments, but consumers of your class can use much more readable individual methods.

    Setters are not the same thing because they allow you to modify a property object after it is created.

  • Extensible API

    When you publish only a multi-parameter constructor for your class, and then decide that you need to add a new (optional) property (say, in a later version of your software) you need to create a second constructor that is identical to the first, but accepts another parameter. Otherwise - if you just added it to an existing constructor - you will break compatibility with existing code.

    With the builder, you simply add a new method for the new property with all existing code still compatible.

  • Invariance

    Software development is very different from running multiple threads in parallel. In such scenarios, it is best to use objects that cannot be modified after they are created (immutable objects), since these cannot cause problems with concurrent updates from multiple threads. This is why setters are not an option.

    Now, if you want to avoid the problems of multi-parameter public designers, which leaves builders as a very convenient alternative.

  • Reading ("Free API")

    The Builder-based API can be very easy to read, if the builder’s methods are smart, you can exit with code that reads almost like English sentences.

In general, collectors are a useful template, and depending on the language you use, they are either very easy to use (e.g. Groovy) or a little more tedious (e.g., Java) for the API provider, However, for consumers this may be just as easy.

+6
source share

You can implement Builders in a more concise way.;) I often found that Writers built by hand tediously and error-prone.

It can work well if you have a data model that generates Data Value objects and their Builders (and marshalers). In this case, I think using Builders is worth it.

+2
source share

There are many problems with constructors that take arguments (for example, you cannot build an object in a few steps). Also, if you need a lot of arguments, you will ultimately get confused with the order of the parameters.

The final idea is to use a β€œ free interface ”. It works with setters that return this . Often set not specified in the method name. Now you can write:

 User user = new User() .firstName( "John" ) .familyName( "Doe" ) .address( address1 ) .address( address2 ) ; 

This has several advantages:

  • It is very readable.
  • You can change the order of the parameters without breaking anything
  • It can handle single-valued and multi-valued arguments ( address ).

The main disadvantage is that you no longer know when the instance is "ready" for use.

The solution is to have many unit tests or specifically add the init () or "done ()" method, which performs all the checks and sets the flag "this instance is correctly initialized".

Another solution is a factory, which creates the actual instance in the build() method, which should be the last in the chain:

 User user = new UserFactory() .firstName( "John" ) .familyName( "Doe" ) .address( address1 ) .address( address2 ) .build() ; 

Modern languages ​​like Groovy turn this into a language:

 User user = new User( firstName: 'John', familyName: 'Doe', address: [ address1, address2 ] ) 
+2
source share

When you need a constructor (similarly consider similar objects) for an object, you force the code, using your object, to pass the basic requirements to the constructor. The clearer the better. You can leave optional fields that will be installed later (entered) using the installer.

0
source share

All Articles