Elegant Swift: Default Parameters vs The Builder Pattern

I was re-reading Effective Java (it is a rainy Sunday afternoon in Vancouver, and if you are stuck at home, and you happen to be European, and therefore you don’t really care about what Americans call “football”, re-reading Effective Java is not as bad as it sounds) when I soon stumbled upon an example that I think highlights the elegance of Swift (and, to be completely fair, also Kotlin’s).

Effective Java’s Item #2 says: Consider a builder when faced with many constructor parameters. Which makes total sense, if you ask me.

I am not going to discuss or justify the Builder Pattern now, but let’s just assume that it is more than justified.

The canonical implementation of the pattern, and I am going to take the liberty of using the code sample used in Effective Java without any modification, would read like the following (servings and servingSize are the only mandatory parameters, the rest are all optional)

public class NutritionFacts {
    private final int servingSize; //required
    private final int servings; //required  
    private final int calories; //optional
    private final int fat; //optional
    private final int sodium; //optional
    private final int carbs; //optional
    
    public static class Builder {
        //Required
        private final int servingSize;
        private final int servings;
        
        //Optional
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbs = 0;
        
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }
        
        public Builder calories(int val) {
            calories = val;
            return this;
        }
        
        public Builder fat(int val) {
            fat = val;
            return this;
        }
        
        public Builder sodium(int val) {
            sodium = val;
            return this;
        }
        
        public Builder carbs(int val) {
            carbs = val;
            return this;
        }
        
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
        
    }
    
    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbs = builder.carbs;
    }
}

class Untitled {
    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts.Builder(240,8)
.calories(100)
.sodium(35)
.carbs(27)
.build();
    }
}

If we follow the pattern to the letter, it translates into Swift like this:

final class NutritionFacts {
    private let servingSize: Int
    private let servings: Int
    private let calories: Int
    private let fat: Int
    private let sodium: Int
    private let carbs: Int

    init(builder: Builder) {
        servingSize = builder.servingSize
        servings = builder.servings
        calories = builder.calories
        fat = builder.fat
        sodium = builder.sodium
        carbs = builder.carbs
    }

    class Builder {
        let servingSize: Int
        let servings: Int

        private(set) var calories = 0
        private(set) var fat = 0
        private(set) var carbs = 0
        private(set) var sodium = 0

        init(servingSize: Int, servings: Int) {
            self.servingSize = servingSize
            self.servings = servings
        }

        func calories(value: Int) -> Builder {
            calories = value
            return self
        }

        func fat(value: Int) -> Builder {
            fat = value
            return self
        }

        func carbs(value: Int) -> Builder {
            carbs = value
            return self
        }

        func sodium(value: Int) -> Builder {
            sodium = value
            return self
        }

        func build() -> NutritionFacts {
            return NutritionFacts(builder: self)
        }
    }
}

let facts = NutritionFacts.Builder(servingSize: 10, servings: 1)
    .calories(value: 20)
    .carbs(value: 2)
    .fat(value: 5)
    .build()

Which is a lot of boilerplate, no matter how you look at it.

We have discussed Swift’s Default Parameters before in this blog, but in the context of Dependency Injection, in particular when focusing on testability.

But the thing is that Default Parameters, like many other Swift artifacts (or constructs, or whatever is the right word to refer to “stuff that Swift provides and makes your life easier”) can make code much more concise, readable and easier to understand and reason about while removing some of the bloating that certain languages (I’m looking at you, Java) are known for.

Because when providing default values to all the optional parameters, this is how our NutritionFacts look.

final class NutritionFacts {
    private let servingSize: Int
    private let servings: Int
    private let calories: Int
    private let fat: Int
    private let sodium: Int
    private let carbs: Int
    
    init(servingSize: Int, servings: Int, calories: Int = 0, fat: Int = 0, sodium: Int = 0, carbs: Int = 0) {
        self.servingSize = servingSize
        self.servings = servings
        self.calories = calories
        self.fat = fat
        self.sodium = sodium
        self.carbs = carbs
    }
}

let facts = NutritionFacts(servingSize: 10, servings: 1, calories: 500)
let otherFacts = NutritionFacts(servingSize: 10, servings: 1, calories: 1500, sodium: 2)

Much better, eh?

One Reply to “Elegant Swift: Default Parameters vs The Builder Pattern”

  1. Better for this use case. But Builder main use case is to separate the “parameter recollection” from the building point. You can pass around a Builder, adding data on each phase and finally call build() to get a nice new object. This is not posible with the initializer with optional parameters approach. So, yes it is nicer, but for me, you are solving other problem 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *