LibraryAbout

🏗️ Exploring the Builder Design Pattern in Ruby


author

Author: David Martinez

Created: November 11, 2024 - Updated: November 12, 2024

Read Time: 10 min

Ruby

Here, we’ll explore the Builder design pattern, a versatile tool in software engineering for managing the creation of complex objects. We'll examine its structure, benefits, and a practical use case in Ruby.

Introducing the Builder Pattern 🧩

The Builder pattern is a creational design pattern that simplifies the construction of complex objects by separating the creation process into distinct steps. By breaking down object creation, it gives flexibility in how these objects are assembled, making it useful when objects require multiple configurations or components.

This pattern is ideal for scenarios where building an object requires multiple, conditional steps, like configuring a vehicle or constructing a GUI.

When to Use the Builder Pattern?

🔸 Use Builder when creating an object involves many optional or configurable parts.

🔸 It’s particularly useful when constructing complex objects with many combinations or when different versions of an object share similar building steps.

Problem Scenario

Imagine a manufacturing application for assembling motorcycles. Each type of motorcycle may require different parts and configurations, such as a sport model with high-performance features or a basic model with standard components.

Without a structured approach, the code becomes hard to manage, and making changes or adding new models risks introducing errors.

Solution

The Builder pattern enables us to split the construction process into distinct steps that can be reused or customized for each motorcycle type. We can apply this pattern to construct different configurations of motorcycles with the desired components and settings.

UML Diagram

In this example, the Builder pattern helps us configure and build motorcycles with specific parts and features.

🔹 We create different builders (e.g., MotoNakedBuilder, MotoSportBuilder) to handle specific configurations.

🔹 A Director class orchestrates the building process based on the motorcycle model requirements.

How does it Work?

1️⃣ Define a Builder interface with methods for each component (e.g., set_engine, set_gps).

2️⃣ Implement concrete Builder classes (e.g., MotoNakedBuilder, MotoSportBuilder) to create configurations with different parts.

3️⃣ Use a Director to control and execute the steps for building each motorcycle type.

💡 Note: This pattern supports flexibility in object creation, allowing builders to be customized or extended as needed.

Why Embrace the Builder Pattern?

🔮 Provides flexibility in creating complex objects with optional components.

🔮 Simplifies adding new configurations or versions without altering existing code.

🔮 Separates object construction from the representation, promoting clean, modular code.

🔮 Makes it easier to implement various versions of an object that share common construction steps.

Show me the code

# Product classes
class MotoNaked
def initialize
@parts = []
end
def add(part)
@parts << part
end
def list_parts
puts "Moto Naked parts: #{@parts.join(', ')}"
end
end
class MotoSport
def initialize
@parts = []
end
def add(part)
@parts << part
end
def list_parts
puts "Moto Sport parts: #{@parts.join(', ')}"
end
end
# Builder interface
module Builder
def reset
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def set_seats
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def set_engine
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def set_trip_computer
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def set_gps
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def set_break_assist
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
# Concrete Builder for Naked model
class MotoNakedBuilder
include Builder
def initialize
reset
end
def reset
@moto = MotoNaked.new
end
def set_seats
@moto.add('seats')
end
def set_engine
@moto.add('basic engine')
end
def set_trip_computer
@moto.add('trip computer')
end
def set_gps
@moto.add('ai gps')
end
def set_break_assist
@moto.add('basic break assist')
end
def moto
moto = @moto
reset
moto
end
end
# Concrete Builder for Sport model
class MotoSportBuilder
include Builder
def initialize
reset
end
def reset
@moto = MotoSport.new
end
def set_seats
@moto.add('premium seats')
end
def set_engine
@moto.add('sport engine')
end
def set_trip_computer
@moto.add('trip computer')
end
def set_gps
@moto.add('premium gps')
end
def set_break_assist
@moto.add('ai break assist')
end
def moto
moto = @moto
reset
moto
end
end
# Director class
class Director
def build_naked_moto(builder)
builder.set_seats
builder.set_engine
builder.set_trip_computer
end
def build_sport_moto(builder)
builder.set_seats
builder.set_engine
builder.set_trip_computer
builder.set_gps
builder.set_break_assist
end
end
# Client code
def main
director = Director.new
builder = MotoNakedBuilder.new
director.build_naked_moto(builder)
builder.moto.list_parts
builder = MotoSportBuilder.new
director.build_sport_moto(builder)
builder.moto.list_parts
end
main if __FILE__ == $PROGRAM_NAME
# Output:
# Moto Naked parts: seats, basic engine, trip computer
# Moto Sport parts: premium seats, sport engine, trip computer, premium gps, ai break assist

Conclusion 🔖

The Builder pattern is a powerful solution for constructing complex objects with various configurations.

🔺 It’s ideal for creating objects with multiple optional parts or when different versions share common construction steps.

🔺 The Builder pattern helps maintain clean, modular code by separating the construction process from the object structure.

🔺 By implementing Builder, you create a flexible and extendable way to manage the creation of complex objects.

Join the Quest!

  • Design Patterns: Elements of Reusable Object-Oriented Software
  • Head First Design Patterns: A Brain-Friendly Guide
  • Code example

💻 You can find this and other design patterns here 📚