Design models: the constructor model

I have been planning to write a series of articles on design patterns for quite some time. Templates are incredibly valuable components in a developer’s toolbox – they deal with common problems that have accepted and effective solutions. In addition, they contribute to a vocabulary shared between developers.

This series assumes an understanding of object oriented programming (OOP). I will, however, try to keep the examples as simple and accessible as possible, favoring practical implementations over obscure examples. If you are looking for an authoritative academic text on models, this is what you need: Design Patterns: Reusable Object-Oriented Software Elements.

We’ll start with the Builder template (one of my favorites). The Builder template is an authoring template – in other words, it is used to create and configure objects. I particularly like the example Joshua Bloch uses in Efficient java.

The problem

For this example, we’ll pretend we’re part of a Java team working on software for a bank. Among other things, we will need a way to represent bank accounts. Our first pass looks like this (note that the use of double for actual currency values ​​is A bad idea).

public class BankAccount {

    private long accountNumber;
    private String owner;
    private double balance;

    public BankAccount(long accountNumber, String owner, double balance) {
        this.accountNumber = accountNumber;
        this.owner = owner;
        this.balance = balance;
    }

    //Getters and setters omitted for brevity.
}

It’s pretty straightforward – we can use it as follows.

BankAccount account = new BankAccount(123L, "Bart", 100.00);

Unfortunately, the solutions are seldom straightforward. A new requirement is coming in which says we have to keep track of the monthly interest rate applicable to each account, the date it was opened and possibly the branch it was opened at. It sounds simple enough, so we are offering version 2.0 of the Bank account to classify.

public class BankAccount {

    private long accountNumber;
    private String owner;
    private String branch;
    private double balance;
    private double interestRate;

    public BankAccount(long accountNumber, String owner, String branch, double balance, double interestRate) {
        this.accountNumber = accountNumber;
        this.owner = owner;
        this.branch = branch;
        this.balance = balance;
        this.interestRate = interestRate;
   }

    //Getters and setters omitted for brevity.
}

Thanks to our new and improved account management process, we are getting new customers.

BankAccount account = new BankAccount(456L, "Marge", "Springfield", 100.00, 2.5);
BankAccount anotherAccount = new BankAccount(789L, "Homer", null, 2.5, 100.00);  //Oops!

Our compiler, which should be our safety net, thinks this code is good. The practical implication, however, is that Homer’s money will double every month. (If anyone knows of an account with returns like this let me know!) Can you understand why? Tip: pay close attention to the order of the parameters passed to the constructor.

If we have multiple consecutive arguments of the same type, it’s easy to accidentally swap them out. Since the compiler doesn’t see it as an error, it can manifest itself as a problem somewhere downstream at run time – and it can turn into a tricky debugging exercise. Additionally, adding additional constructor parameters results in code that becomes more difficult to read. If we had 10 different settings, it would become very difficult to identify what is in the constructor at a glance. To make matters worse, some of these values ​​might be optional, which means we’ll need to create an overloaded constructor group to handle all possible combinations, or we’ll need to pass NULL values ​​to our (ugly!) Constructor.

You might think we can mitigate the problem by calling a constructor with no arguments and then configuring the account through setter methods instead. However, that leaves us open to another problem: what if a developer forgets to call a particular setter method? We could end up with an object that is only partially initialized, and again the compiler would see no problem with that.

So, there are two specific problems that we need to solve:

  • Too many constructor arguments.
  • Incorrect object state.

This is where the Model Builder comes in.

The reason

Model Builder allows us to write readable and understandable code to configure complex objects. It is often implemented with a fluid interface, which you may have seen in tools like Apache Camel Where Hamcrest. The generator will contain all the fields that exist on the Bank account class itself. We will configure all the fields we want on the generator, then we will use the generator to create accounts. At the same time, we will remove the public constructor from the Bank account class and replace it with a private constructor so that accounts can only be created through the constructor.

For our example, we will put the constructor in the Bank account to classify. It looks like this.

public class BankAccount {

    public static class Builder {

        private long accountNumber; //This is important, so we'll pass it to the constructor.
        private String owner;
        private String branch;
        private double balance;
        private double interestRate;

        public Builder(long accountNumber) {
            this.accountNumber = accountNumber;
        }

        public Builder withOwner(String owner){
            this.owner = owner;

            return this;  //By returning the builder each time, we can create a fluent interface.
        }

        public Builder atBranch(String branch){
            this.branch = branch;

            return this;
        }

        public Builder openingBalance(double balance){
            this.balance = balance;

            return this;
        }

        public Builder atRate(double interestRate){
            this.interestRate = interestRate;

            return this;
        }

        public BankAccount build(){
            //Here we create the actual bank account object, which is always in a fully initialised state when it's returned.
            BankAccount account = new BankAccount();  //Since the builder is in the BankAccount class, we can invoke its private constructor.
            account.accountNumber = this.accountNumber;
            account.owner = this.owner;
            account.branch = this.branch;
            account.balance = this.balance;
            account.interestRate = this.interestRate;

            return account;
        }
    }

    //Fields omitted for brevity.
    private BankAccount() {
        //Constructor is now private.
    }

    //Getters and setters omitted for brevity.

}

We can now create new accounts as follows.

BankAccount account = new BankAccount.Builder(1234L)
            .withOwner("Marge")
            .atBranch("Springfield")
            .openingBalance(100)
            .atRate(2.5)
            .build();

BankAccount anotherAccount = new BankAccount.Builder(4567L)
            .withOwner("Homer")
            .atBranch("Springfield")
            .openingBalance(100)
            .atRate(2.5)
            .build();

Is this code more verbose? Yes. Is that clearer ? Yes. Is it better? Since a lot of our time is spent reading code rather than writing it, I’m pretty sure it is.

Summary

We worked on an example where the code started out simple and then got more and more complex. We then used the Model Builder to troubleshoot the issues we discovered.

If you find yourself in a situation where you keep adding new parameters to a constructor, resulting in code that becomes error-prone and hard to read, maybe now is a good time to take a step back and consider refactor your code to use a constructor.

That’s all for the moment. Feel free to leave a comment if you have anything to ask or add.


Source link

Abdul J. Gaspar

Leave a Reply

Your email address will not be published.