← Back to the index page

Builder

Builder is a creational design pattern to build objects with complex state step-by-step. This article only covers a builder pattern without a director and abstract builder. To learn how to use builders without the director, check out the article on refactoring.guru.

Tip

Use builder pattern when the state of the object is complex and it is hard to put everything in the constructor.

Let’s imagine we have a Player class with lots of properties. We want to be able to create a player with different properties, and those properties depend on external factors and might change due to different circumstances. In this case, class constructor is not a good option, because it would be too complex and hard to maintain.

Builder Scheme

Consider:

---@class Player
local Player = {}
Player.__index = Player

---@return Player
function Player:new(name, hp, damage, armor, mana, inventory, xp)
    local t = {
        name = name,
        hp = hp,
        damage = damage,
        armor = armor,
        mana = mana,
        inventory = inventory,
        xp = xp,
        -- more props
    }
    return setmetatable(t, self)
end

return Player

In the case above, the constructor already has too many arguments. We can use a builder pattern to simplify the constructor:

---@class Player
local Player = {}
Player.__index = Player

---@return Player
function Player:new()
    local t = {}
    return setmetatable(t, self)
end


---@class PlayerBuilder
---@field player Player
local PlayerBuilder = {}
PlayerBuilder.__index = PlayerBuilder

---@return PlayerBuilder
function PlayerBuilder:new()
    local t = {
        player = Player:new()
    }
    return setmetatable(t, self)
end

---@param name string
---@return PlayerBuilder
function PlayerBuilder:setName(name)
    self.player.name = name
    return self
end

---@param hp string
---@return PlayerBuilder
function PlayerBuilder:setHitPoints(hp)
    self.player.hp = hp
    return self
end

---@param damage string
---@return PlayerBuilder
function PlayerBuilder:setDamage(damage)
    self.player.damage = damage
    return self
end

---@param item string
---@return PlayerBuilder
function PlayerBuilder:addItem(item)
    table.insert(self.player.inventory, item)
    return self
end

--- etc. for the rest of the properties

---@return Player
function PlayerBuilder:build()
    return self.player
end

PlayerBuilder usage

Also notice that the builder’s method returns the builder itself, so we can chain calls.

local PlayerBuilder = require("PlayerBuilder")

local builder = PlayerBuilder:new()
local player = builder:setDamage(10):setHitPoints(120):setName("Max"):build()

print(player.name, player.damage, player.hp)
--[[
Max     10      120
--]]

StringBuilder

Another example of a builder pattern is the StringBuilder class, which uses sting buffers to build strings very fast. A similar mechanism is used in the Java language for StringBuilder class, or Go’s strings.Builder, and other languages.

---@class StringBuilder
---@field private parts table
local StringBuilder = {}
StringBuilder.__index = StringBuilder

---@return StringBuilder
function StringBuilder:new()
    local t = {}
    t.parts = {}
    return setmetatable(t, self)
end

---@param s string
---@return StringBuilder
function StringBuilder:add(s)
    table.insert(self.parts, s)
    return self
end

---@return string
function StringBuilder:build()
    return table.concat(self.parts)
end

return StringBuilder

StringBuilder usage

local StringBuilder = require("StringBuilder")
local builder = StringBuilder:new()
builder:add("Hello"):add(" "):add("world")
builder:add("!!!")
local str = builder:build()
print(str) --> Hello world!!!

Feedback

For feedback, please check the contacts section. Before writing, please specify where you came from and who you are. Sometimes spammers go insane. Thank you in advance for your understanding.

← Back to the index page