When creating objects with many optional or interdependent parts, maintaining clarity and flexibility in the construction process can become a challenge. The Builder Pattern addresses this by allowing step-by-step object creation, ensuring flexibility and scalability in your code.
In this article, we’ll explore the Builder Pattern with a practical gaming scenario, explaining the problem it solves, and illustrating how it works through a real-world implementation using TypeScript.
Table of Contents
- Builder Pattern Definition
- Problems Solved with Builder Pattern
- Real-Life Projects That Use the Builder Pattern
Builder Pattern Definition
The Builder Pattern is a creational design pattern that decouples the construction of a complex object from its representation.
Rather than using a class constructor overloaded with numerous parameters, which can make the code difficult to read and maintain, the Builder Pattern provides a way to construct objects step by step. This approach simplifies managing optional or interdependent components, resulting in more readable and maintainable code.
Problems Solved with Builder Pattern
Imagine you are designing a game where players can create customizable characters. Each character can have the following attributes:
Helmet
Torso armor
Arm covers
Pants
Primary weapon
Secondary weapon
Melee weapon
Now, consider the challenges:
Complex Object Construction
Some characters require full equipment (e.g., sniper characters), while others may start with only basic armor. Managing these configurations with traditional constructors becomes overwhelming.Flexibility in Customization
Players need to create characters step by step, with the freedom to skip or modify parts of the configuration.Readability and Maintainability
Directly initializing these objects using constructors or long parameter lists is not only error-prone but also hard to maintain as new features are added.
Implementation
Key Components
Step 1: Character Class
Represents the complex object being built, containing attributes like armor and weapons.
interface ICharacter {
helmet: string | null;
torsoArmor: string | null;
armsCover: string | null;
pants: string | null;
primaryWeapon: string | null;
secondaryWeapon: string | null;
meleeWeapon: string | null;
}
class Character implements ICharacter {
helmet: string | null = null;
torsoArmor: string | null = null;
armsCover: string | null = null;
pants: string | null = null;
primaryWeapon: string | null = null;
secondaryWeapon: string | null = null;
meleeWeapon: string | null = null;
constructor() {}
}
Step 2:ICharacterBuilder Interface
Defines the methods required to build each part of the character.
interface ICharacterBuilder {
addHelmet(helmet: string): this;
addTorsoArmor(armor: string): this;
addArmsCover(armsCover: string): this;
addPants(pants: string): this;
addPrimaryWeapon(weapon: string): this;
addSecondaryWeapon(weapon: string): this;
addMeleeWeapon(weapon: string): this;
getCharacter(): Character;
}
Step 3: CharacterBuilder Class
Implements the builder interface and provides methods for constructing each part of the character.
class CharacterBuilder implements ICharacterBuilder {
private character: Character;
constructor() {
this.reset();
}
reset() {
this.character = new Character();
}
addHelmet(helmet: string): this {
this.character.helmet = helmet;
return this;
}
addTorsoArmor(armor: string): this {
this.character.torsoArmor = armor;
return this;
}
addArmsCover(armsCover: string): this {
this.character.armsCover = armsCover;
return this;
}
addPants(pants: string): this {
this.character.pants = pants;
return this;
}
addPrimaryWeapon(weapon: string): this {
this.character.primaryWeapon = weapon;
return this;
}
addSecondaryWeapon(weapon: string): this {
this.character.secondaryWeapon = weapon;
return this;
}
addMeleeWeapon(weapon: string): this {
this.character.meleeWeapon = weapon;
return this;
}
getCharacter(): Character {
const result = this.character;
this.reset();
return result;
}
}
Step 4: Director Class
Orchestrates the building process by defining configurations for different character types.
class Director {
private characterBuilder: CharacterBuilder;
setCharacterBuilder(characterBuilder: CharacterBuilder) {
this.characterBuilder = characterBuilder;
}
buildStarterCharacter() {
this.characterBuilder
.addHelmet("starter")
.addTorsoArmor("starter")
.addArmsCover("starter")
.addPants("starter");
}
buildSniperCharacter() {
this.characterBuilder
.addHelmet("ghillie head cover")
.addTorsoArmor("ghillie torso cover")
.addArmsCover("ghillie arms cover")
.addPants("ghillie pants")
.addPrimaryWeapon("Bolt sniper rifle")
.addSecondaryWeapon("pistol")
.addMeleeWeapon("knife");
}
}
Step 5: Client Code
Demonstrates how the Builder Pattern is used to create different character configurations.
const director = new Director();
const builder = new CharacterBuilder();
director.setCharacterBuilder(builder);
director.buildStarterCharacter();
console.log(builder.getCharacter());
director.buildSniperCharacter();
console.log(builder.getCharacter());
Problems Solved with Builder Pattern
Simplifies Complex Object Creation
By delegating the construction logic to a builder class, the client code becomes cleaner and more manageable.Improves Flexibility
Clients can build objects step by step, adding only the parts they need.Promotes Scalability
Adding new attributes or configurations (e.g., a wizard character) doesn’t disrupt existing code.Supports Readability
The clear, incremental construction process is easier to understand and maintain.
Real-Life Projects That Use the Builder Pattern
Game Development:
Building customizable characters, levels, or items incrementally.UI Builders:
Constructing dynamic user interfaces where optional components are added based on user input.Document Generators:
Creating structured documents like PDFs or reports with optional sections.Query Builders:
Incrementally building SQL queries or API requests.
The Builder Pattern is a versatile tool for managing complex object creation, offering flexibility and scalability while keeping your code maintainable and readable. With the provided implementation, you can now start applying this pattern to your projects!
In the next article of our Learn Design Patterns series, we’ll explore the Prototype Pattern, which is used to create new objects by cloning an existing object, known as a prototype. Stay tuned, and keep learning!
Top comments (0)