Published on

Exploring The Builder Pattern

Authors
what is the builder pattern feature bob software design pattern

Hey there coder! Welcome to this episode on design patterns. Today, our focus will be on the Builder pattern, a powerful technique that enables the construction of complex objects with elegance and precision within the JavaScript ecosystem. As software developers and creators, we need to recognize the significance of design patterns in crafting functional, robust, and maintainable software systems.

What exactly is the Builder pattern? In the heart of its brilliance, the Builder pattern serves as an architectural artisan, facilitating the construction of complex objects step by step. Instead of overwhelming you with the entirety of its complexity, the Builder pattern hands you one brick at a time, guiding you through a structured process.

By delving into the realm of design patterns, we unlock a world where code evolves into an art form. We tackle the challenges of object creation with simplicity and sophistication. The Builder pattern presents a dynamic and intelligent solution to the puzzles we encounter when constructing objects with multiple attributes.

If you find yourself entangled in complex and convoluted code, drowning in a sea of optional and required configurations, the Builder pattern is here to simplify your code. Let us unravel the complexities and guide you towards a flexible and readable path to success.

What is the Builder Pattern

Diagram showcasing the Builder Pattern's application in software architecture

The Builder pattern serves as a powerful solution to the challenges encountered when constructing objects with multiple attributes, including optional or interdependent fields. In traditional approaches to object creation, managing objects with numerous optional and required fields can quickly lead to code that is convoluted, error-prone, and difficult to understand.

One of the main problems that the Builder pattern addresses is the management of objects with many optional fields. In traditional approaches, constructors often require numerous parameters, many of which may be optional. This leads to unwieldy code and makes it challenging to remember the order and meaning of each parameter. The Builder pattern solves this issue by providing a clear and intuitive interface for setting each attribute separately, ensuring that developers can specify only the necessary options without worrying about the rest.

Understanding the Need for the Builder Pattern

The Builder pattern is a creational design pattern that separates the construction of an object from its representation. It allows the step-by-step construction of complex objects by using a builder class that encapsulates the object creation logic. The builder class provides methods for setting different attributes of the object, enabling the creation of objects with varied configurations while maintaining code readability.

Complex Objects with Interlinking Parts

In many software applications, objects often consist of numerous interlinked parts. These parts may have dependencies on each other or require specific configurations to work correctly. Creating such complex objects directly in the client code can quickly become convoluted and error-prone. The Builder pattern addresses this need by encapsulating the construction logic within a builder class, ensuring that the object's interdependencies are handled appropriately.

Many Optional and Required Fields

Objects with multiple optional and required fields pose a challenge when creating them using traditional approaches. Directly passing numerous parameters to a constructor can lead to cumbersome code and confusion, especially when some fields are optional or have default values. The Builder pattern provides a more elegant solution by allowing the object's attributes to be set using a series of method calls. This approach simplifies the object creation process, enhances code readability, and improves maintainability.

Examples of the Builder Pattern

These real-world examples illustrate how the Builder pattern can be applied to simplify the construction of complex objects in various domains. By encapsulating the object creation logic and providing a fluent and readable interface, the Builder pattern enhances code maintainability, flexibility, and readability.

Example 1: Object Configuration in Frameworks

Example of the Builder Pattern used for object configuration in software frameworks

Software frameworks often need to manage complex configurations. These configurations could involve setting up various components or modules, each of which may have numerous optional settings. In such cases, the Builder pattern can prove highly beneficial as it allows for the creation of complex configuration objects in a manageable way.

For instance, in a web development framework like Express.js, a builder class can be used to set up a configuration object for a web server:

class WebServerConfigBuilder {
  constructor() {
    this.config = {
      port: 3000, // Default port
      timeout: 5000, // Default timeout
      security: {
        enabled: true, // Default security enabled
        apiKey: null, // Default API key
      },
    }
  }

  withPort(port) {
    this.config.port = port
    return this
  }

  withSecurityEnabled(enabled) {
    this.config.security.enabled = enabled
    return this
  }

  withApiKey(apiKey) {
    this.config.security.apiKey = apiKey
    return this
  }

  build() {
    // Perform any additional validation or customization if needed
    return this.config
  }
}

// Example usage
const webServerConfig = new WebServerConfigBuilder()
  .withPort(8080)
  .withSecurityEnabled(true)
  .withApiKey('my-secret-key')
  .build()

console.log(webServerConfig)

This would allow developers to specify various options such as the port number, security configurations, etc., in an orderly manner. By chaining methods corresponding to each of these settings, the builder class would provide a clean, readable interface for server configuration.

Example 2: Database Query Builders

Representation of a database query builder, a real-world application of the Builder Pattern

Constructing complex database queries, especially when there are optional filters, sorting parameters, or joins, can be quite challenging. However, the Builder pattern can help here too, enabling step-by-step construction of SQL or NoSQL queries.

class SQLQueryBuilder {
  constructor() {
    this.query = ''
  }

  select(table, fields) {
    this.query += `SELECT ${fields.join(', ')} FROM ${table} `
    return this
  }

  where(field, value) {
    this.query += `WHERE ${field} = ${value} `
    return this
  }

  orderBy(field, order) {
    this.query += `ORDER BY ${field} ${order} `
    return this
  }

  getQuery() {
    return this.query
  }
}

// Usage
let builder = new SQLQueryBuilder()
let query = builder
  .select('users', ['name', 'email'])
  .where('id', 1)
  .orderBy('name', 'ASC')
  .getQuery()

console.log(query)

This would output:

SELECT name, email FROM users WHERE id = 1 ORDER BY name ASC

Query builders usually involve chaining method calls corresponding to specific query components. For instance, methods might include .select(), .where(), .orderBy(), etc. This leads to improved query readability and flexibility, allowing for dynamic query generation based on user input or application state.

Example 3: UI Component Builders

Graphic illustrating the use of the Builder Pattern in UI component construction

The Builder pattern is a valuable tool for constructing complex UI components with multiple configuration options. Let's illustrate how this pattern can be applied to create a table component in a user interface using JavaScript:

class TableComponentBuilder {
  constructor() {
    this.columnHeaders = []
    this.rowData = []
    this.styleClass = ''
  }

  setColumnHeaders(columnHeaders) {
    this.columnHeaders = columnHeaders
    return this
  }

  setRowData(rowData) {
    this.rowData = rowData
    return this
  }

  setStyleClass(styleClass) {
    this.styleClass = styleClass
    return this
  }

  build() {
    // Additional validation or customization can be performed here if needed
    return new TableComponent(this.columnHeaders, this.rowData, this.isSortable, this.styleClass)
  }
}

// Example usage:
const builder = new TableComponentBuilder()
const table = builder
  .setColumnHeaders(['Header1', 'Header2', 'Header3'])
  .setRowData([
    ['Row1Data1', 'Row1Data2', 'Row1Data3'],
    ['Row2Data1', 'Row2Data2', 'Row2Data3'],
    ['Row3Data1', 'Row3Data2', 'Row3Data3'],
  ])
  .setStyleClass('table-style')
  .build()

In the example above, we have a TableComponentBuilder class that provides methods for configuring various options of the table component. These methods return the builder instance itself, allowing for method chaining.

The resulting TableComponent class might look like this:

class TableComponent {
  constructor(columnHeaders, rowData, isSortable, styleClass) {
    this.columnHeaders = columnHeaders
    this.rowData = rowData
    this.styleClass = styleClass
  }
}

// Example usage:
const columnHeaders = ['Header1', 'Header2', 'Header3']
const rowData = [
  ['Row1Data1', 'Row1Data2', 'Row1Data3'],
  ['Row2Data1', 'Row2Data2', 'Row2Data3'],
  ['Row3Data1', 'Row3Data2', 'Row3Data3'],
]
const isSortable = true
const styleClass = 'table-style'

const table = new TableComponent(columnHeaders, rowData, isSortable, styleClass)

Using the Builder pattern, developers can create a table component like this:

const table = new TableComponentBuilder()
  .setColumnHeaders(['Name', 'Age', 'Country'])
  .setRowData([
    ['John Doe', 30, 'USA'],
    ['Jane Smith', 25, 'Canada'],
  ])
  .setStyleClass('my-table')
  .build()

This approach provides a clear and structured way to initialize complex UI components, making it easier to manage their configurations, especially when dealing with numerous options. The Builder pattern enhances code readability and flexibility in UI development.

Example 4: Test Data Generation

Visual depiction of the Builder Pattern in generating test data for software testing

In testing scenarios, you often need to generate test data with various configurations. This task can be tedious and error-prone, but the Builder pattern can help by facilitating the efficient generation of test data objects.

class UserDataBuilder {
  constructor() {
    this.userData = {}
  }

  withUsername(username) {
    this.userData.username = username
    return this
  }

  withEmail(email) {
    this.userData.email = email
    return this
  }

  withAge(age) {
    this.userData.age = age
    return this
  }

  build() {
    // Perform any additional validation or customization if needed
    return this.userData
  }
}

// Example usage
const userTestData = new UserDataBuilder()
  .withUsername('JohnDoe')
  .withEmail('[email protected]')
  .withAge(30)
  .build()

console.log(userTestData)

In this case, a test data builder might have methods corresponding to different attributes of the test data. For example, if you're testing a user registration system, the builder could have methods like .withUsername(), .withEmail(), .withAge(), etc. This would allow you to easily generate test cases for various scenarios, like registering a user with missing data or invalid inputs. By encapsulating the logic for data generation within the builder, this approach promotes DRY (Don't Repeat Yourself) principles and enhances the maintainability of your test suite.

Benefits of Using the Builder Pattern

Comparative image contrasting different approaches in the Builder Pattern

The Builder pattern offers several benefits when applied to software development:

  • Simplified Object Construction: By separating the construction logic into a builder class, the object creation process becomes more intuitive and readable. Developers can focus on setting individual attributes of the object without worrying about complex object initialization.
  • Flexible Object Configuration: The Builder pattern allows the creation of objects with different configurations using a single builder class. By providing different methods to set various attributes, the builder class provides flexibility in constructing objects with optional or interdependent fields.
  • Encapsulated Object Creation Logic: The builder class encapsulates the object creation logic, isolating it from the client code. This abstraction enables changes in the object creation process without affecting the client code, promoting code maintainability and modularity.
  • Consistent Object Initialization: The Builder pattern ensures that the object is initialized consistently, regardless of the configuration chosen. It eliminates the possibility of partially constructed or invalid objects, providing a robust and reliable object creation process.

Traditional Builder Pattern Implementation

In software development, creating objects with a multitude of optional fields can be a complex and error-prone task. The Builder pattern provides a structured and efficient solution to this challenge. In this section, we'll explore the traditional implementation of the Builder pattern, using a UserBuilder class as our example.

Step 1: Create a UserBuilder Class

To begin, create a dedicated class named UserBuilder. This class will serve as the foundation for constructing User objects and will allow us to specify a range of optional fields during the object creation process.

class UserBuilder {
  // Fields for the User object
  constructor() {
    this.name = ''
    this.age = 0
    this.phone = ''
    this.address = ''
    // More fields as needed...
    // Private access modifiers encapsulate fields within the UserBuilder class
  }
}

Step 2: Implement a Constructor for UserBuilder

Within the UserBuilder class, introduce a constructor that accepts the required parameters for creating a User object. In this example, let's consider "name" as an essential parameter. The constructor initializes the UserBuilder object and anchors it with the provided name.

class UserBuilder {
  // Fields for the User object
  constructor(name) {
    this.name = name
    this.age = 0
    this.phone = ''
    this.address = ''
    // More fields as needed...
  }
}

Step 3: Implement Methods to Set Optional Fields

Next, implement additional methods within the UserBuilder class to set optional fields of the User object. These methods, such as setAge(), setPhone(), and setAddress(), enable us to define values for fields like age, phone number, and address, respectively.

class UserBuilder {
  // Fields for the User object
  constructor(name) {
    this.name = name
    this.age = 0
    this.phone = ''
    this.address = ''
    // More fields as needed...
  }

  setAge(age) {
    this.age = age
    return this
  }

  setPhone(phone) {
    this.phone = phone
    return this
  }

  setAddress(address) {
    this.address = address
    return this
  }
}

Step 4: How Method Chaining Simplifies Object Creation

One of the significant advantages of the Builder pattern is the ability to chain multiple method calls together. This approach allows us to set multiple optional fields in a concise and highly readable manner. Each method call returns the UserBuilder object itself, enabling us to chain the setter methods for a seamless flow. Consider the following code snippet:

const user = new UserBuilder('John')
  .setAge(30)
  .setPhone('123456789')
  .setAddress('123 Main St')
  .build()

In this example, we create a User object named "John" and set optional fields like age, phone number, and address using method chaining. The final invocation of .build() constructs and returns the fully initialized User object.

Step 5: Introduce the build() Method

To finalize the object construction process, introduce a build() method within the UserBuilder class. This method collects all the provided information, combines it into a cohesive User object, and returns the finalized result.

class UserBuilder {
  // Fields for the User object
  constructor(name) {
    this.name = name
    this.age = 0
    this.phone = ''
    this.address = ''
    // More fields as needed...
  }

  setAge(age) {
    this.age = age
    return this
  }

  setPhone(phone) {
    this.phone = phone
    return this
  }

  setAddress(address) {
    this.address = address
    return this
  }

  build() {
    return new User(this)
  }
}

By leveraging the traditional Builder pattern implementation in JavaScript, we can create User objects with numerous optional fields with ease. The UserBuilder class acts as an invaluable facilitator, abstracting the complexity of object construction and providing an intuitive and readable approach to setting optional values.

The method chaining technique employed by the builder simplifies object creation code, fostering a fluent and natural syntax. With a single chain of method calls, we can set multiple optional fields, enhancing code simplicity and maintainability.

A Simplified Approach in JavaScript

A Simplified Approach in JavaScript tpo using the Builder Pattern

In JavaScript, we can leverage the language's flexibility and object-oriented features to implement a simplified approach to the Builder pattern. This approach eliminates the need for a separate Builder class and allows us to modify the User class itself to accommodate optional fields.

Before we delve into the steps, it's crucial to understand our approach. We are going to modify the User class to handle both required and optional fields directly. By using a JavaScript object as the last parameter in the User constructor, we can allow for the specification of optional fields using key-value pairs, thus eliminating the need for a separate Builder class.

Step 1: Modify the User Class

We first modify the User class. In the constructor of this class, the first parameter is the required field, in this case, the name. The second parameter is an object (optionalFields) that will contain all the optional parameters.

In the constructor, we assign the name to the instance, and then iterate through optionalFields to dynamically assign all the optional fields to the instance.

class User {
  constructor(name, optionalFields = {}) {
    this.name = name
    // Assign optional fields to `this`
    for (let field in optionalFields) {
      this[field] = optionalFields[field]
    }
  }
}

Step 2: Utilize a JavaScript Object for Optional Fields

With the class definition from Step 1, you can create an object and pass any optional fields as properties of an object. This is the flexibility and advantage of this approach: you can pass as many optional fields as you want in any order.

const user1 = new User('John', { age: 30 })
console.log(user1)
// Output: User { name: 'John', age: 30 }

Step 3: Default Values for Optional Fields

Now, let's say you want to have default values for some or all optional fields. This can be achieved by modifying the User class to use object destructuring for the second parameter. You can specify the default values for the optional fields in this object. If the field is not present in the object passed when creating the User instance, it will take the default value.

class User {
  constructor(name, { age = undefined, phone = undefined, address = undefined } = {}) {
    this.name = name
    this.age = age
    this.phone = phone
    this.address = address
  }
}

const user2 = new User('Jane')
console.log(user2)
// Output: User { name: 'Jane', age: undefined, phone: undefined, address: undefined }

Step 4: Construct an Object Using the Simplified Approach

Finally, we'll create an object using the modified User class. Now, if you pass any optional fields during object creation, they will overwrite the default values specified in the User constructor. This is how you can provide specific values for optional fields when needed, while having default values as a fallback.

const user3 = new User('John', { age: 30, phone: '123456789', address: '123 Main St' })
console.log(user3)
// Output: User { name: 'John', age: 30, phone: '123456789', address: '123 Main St' }

This simplified approach provides an intuitive and flexible way to handle optional parameters, effectively replicating the functionality of the Builder pattern in JavaScript without the need for a separate Builder class.

Comparing the Traditional Builder Pattern and Simplified JavaScript Approach

sofware minion utilizing the builder pattern

In software development, object creation can vary significantly in complexity. The traditional Builder pattern and a simplified JavaScript approach each offer unique advantages to cater to these variances. This comparison aims to illuminate when to utilize each approach, based on the complexity of the objects being constructed.

Making the Choice

The complexity and specific needs of your project should guide your decision between these two options. Whether you opt for the Traditional Builder Pattern or the Simplified Approach in JavaScript, aim for the one that best matches your project's needs and enhances code readability and maintainability.

Here's a table comparing the two approaches:

AttributeTraditional Builder PatternSimplified Approach in JavaScript
SuitabilityComplex objects with multiple partsSimpler objects with fewer parts
Separation of ConcernsClear separation with a Builder classNot explicit; optional fields handled within the class
Flexibility/ExtensibilityCan be extended to handle more fieldsLimited; only handles existing fields in the class
ReadabilityMethod chaining provides clear syntaxDirect object creation is simple and readable
Code ComplexityHigher due to separate Builder classLower; no separate Builder class
Handling Optional FieldsAccommodated in Builder classHandled in class constructor
Default Values for Optional FieldsDepends on Builder class implementationEasily provided in the constructor

How to choose between Traditional Builder Pattern VS Simplfiied Version

Side-by-side comparison of the traditional Builder Pattern and its simplified JavaScript approach

Traditional Builder Pattern

Let's consider two different building scenarios. In the first scenario, you are constructing a complex machine with numerous moving parts and dependencies. For such a task, a well-drawn blueprint and an organized approach to handle components would be necessary. This is where the Traditional Builder Pattern excels.

The Traditional Builder Pattern utilizes a dedicated Builder class, which ensures a clear separation of concerns. Think of the Builder class as your project manager, responsible for keeping the construction logic separate from the main code. This separation makes it easier to manage the creation of complex objects, especially those that require intricate initialization logic or involve multiple dependencies.

The design of the Builder class can be easily extended to accommodate additional fields, allowing for greater customization in object creation. In terms of readability, the Builder class utilizes method chaining, providing a clean and transparent syntax similar to following an instruction manual for assembling your intricate machine.

Simplified Javascript Approach to Builder Pattern

On the other hand, imagine a simpler do-it-yourself project with fewer parts and less complexity. For such cases, the Simplified Approach in JavaScript is more suitable. It simplifies the class by directly handling optional fields, making it a great choice for objects with fewer dependencies and less complexity.

One of the main advantages of the Simplified Approach is reduced code complexity. The syntax is also simpler, utilizing a JavaScript object for optional fields, which provides a straightforward way to specify them during object creation. Default values for optional fields can be defined within the constructor, simplifying the object creation process and eliminating the need for explicit checks.

Consider Complexity When Deciding

When deciding between these two approaches, consider the complexity and requirements of the object you are building. Choose the approach that best fits the needs of your project, much like selecting the right tool for a building project. The Traditional Builder Pattern serves as a detailed blueprint for more complex creations, while the Simplified Approach in JavaScript acts as a convenient guide for simpler projects.


Potential Pitfalls of the Builder Pattern

Warning signs indicating potential pitfalls in implementing the Builder Pattern

Consider the Overhead

General Complexity: The Builder pattern involves creating a separate builder class to construct a product class. For objects that are simple and have only a few fields, introducing a builder can seem like adding unnecessary complexity. This could make the code harder to follow for those who are unfamiliar with the pattern.

Performance Impact: While the Builder pattern enhances readability and maintainability, it comes at the cost of extra method calls and object creations. In most applications, this overhead is negligible. However, in situations where performance is paramount, like real-time systems, the cumulative effect of these additional operations could be significant.

Avoid Misuse and Overuse

Unwarranted Usage: Just because the Builder pattern exists doesn't mean it's always the right tool for the job. Implementing it where it's not genuinely beneficial can lead to over-engineering, making the solution more complex than the problem.

Inconsistencies: The primary objective of the Builder pattern is to ensure the creation of valid objects. However, if not designed or implemented correctly, it could allow the creation of inconsistent or even invalid objects. This defeats its primary purpose.

Maintenance Challenges

Evolutionary Updates: When the main product class undergoes changes, the corresponding Builder class must be updated to reflect those changes. This means two points of maintenance. If this synchronization is overlooked, it can lead to issues or incomplete object constructions.

Multiple Builder Management: Some classes might have multiple builder variations. When the primary class changes, ensuring all associated builders are updated can be a daunting task, increasing the risk of oversight.

Learning Curve

Newcomer Orientation: For developers who haven't encountered the Builder pattern before, it might initially seem confusing. They could struggle to understand the separation of object construction from its representation, leading to a steeper learning curve.

Complex Inheritance: If the main class for which the Builder is used is part of a broad inheritance hierarchy, things can get tricky. Each subclass might necessitate its distinct Builder, leading to a proliferation of Builder classes and potential maintenance challenges.

Scenarios Where the Builder Pattern Might Not Be Ideal

Graphic highlighting scenarios where the Builder Pattern might not be ideal

When adopting the Builder pattern, it’s essential to weigh the benefits against the overhead it introduces. This pattern shines in situations where objects have numerous parameters, some of which have defaults. But is it always worth it? Let’s dissect the intricacies and the subtle costs that come with this design pattern.

Simplicity Over Complexity

Straightforward Objects: For objects that have a clear and uncomplicated construction, employing a Builder might be overkill. In such cases, a direct constructor or a simple factory method would be more efficient and less convoluted.

Transient Objects: Some objects are meant for brief use and are quickly discarded. In such scenarios, the slight overhead introduced by the Builder might not justify its benefits, especially when performance is a top priority.

Immutable Object Considerations

Few Variations: Immutable objects, by definition, cannot change after they're created. If such an object has only a handful of possible configurations or variations, using dedicated constructors or factory methods for each variation is often clearer and more straightforward than a generic Builder.

Stateful Builders

Mutable State Issues: If a Builder retains state across multiple invocations or builds, it can introduce hard-to-trace bugs. This risk is amplified in multi-threaded environments where concurrent operations can lead to unpredictable results.


Concepts and Keywords in the Blog Post

Keyword/ConceptExplanation
Builder patternA creational design pattern that separates the construction of an object from its representation. It allows step-by-step construction of complex objects by using a builder class that encapsulates the object creation logic.
Creational design patternDesign patterns that deal with object creation mechanisms, trying to create objects in a manner suitable for the situation.
UserBuilder classA class used in the traditional implementation of the Builder pattern that serves as the foundation for constructing User objects. It provides methods for setting optional fields and facilitates the creation of complex objects.
Simplified approachAn alternative approach to implementing the Builder pattern in JavaScript where the User class itself is modified to handle optional fields directly. This eliminates the need for a separate Builder class and simplifies object creation.
Optional fieldsFields of an object that are not required for its creation and can be set or omitted based on the specific needs. The Builder pattern helps manage optional fields in a more structured and readable way, ensuring that developers can specify only the necessary options.
Method chainingA technique that enables sequential method calls on an object. In the context of the Builder pattern, method chaining allows for the concise and readable setting of multiple optional fields by returning the builder object itself after each method call.
Object construction processThe process of creating an object with the desired configuration and state. The Builder pattern provides a clear and intuitive interface for specifying the attributes and optional fields of an object, making the construction process more manageable and readable.
Flexibility and extensibilityBenefits of the Builder pattern that allow for easy modification and extension of the construction process. The separate builder class or modified User class enables the addition of new optional fields or customization without impacting the client code.

This table provides a summary of the keywords and concepts related to the Builder pattern discussed in the blog post. It aims to give you a quick reference for understanding the key aspects of the pattern and its application in object construction.

Conclusion and next steps

The Builder pattern offers an effective solution for creating objects with multiple interlinking parts or optional/required fields. In this blog, we explored two approaches to implementing the Builder pattern: the traditional approach and a simplified approach in JavaScript.

The traditional approach involves creating a separate UserBuilder class with methods to set optional fields. This method chaining simplifies object creation and allows for a clear separation of concerns. The build method finalizes the User object and returns it.

In the simplified JavaScript approach, we modify the User class itself to accommodate optional fields. Using a JavaScript object as the last parameter allows for easy specification of optional fields and setting default values within the constructor. This approach is suitable for simpler objects with fewer dependencies.

Choosing the appropriate approach depends on the complexity of the objects being created. The traditional approach is ideal for complex objects with intricate initialization requirements, while the simplified JavaScript approach is suitable for simpler objects that require a concise and straightforward approach.

By leveraging the Builder pattern, you can improve code readability, maintainability, and flexibility in object creation. Thank you for joining us on this journey toward writing cleaner and more efficient code. Have a fantastic day!


Sources for Further Reading

The following sources were referenced in the creation of this blog post and provide additional information on the Builder Pattern:

  1. Wikipedia: Builder Pattern

  2. Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides

    • Link: Amazon
    • Description: This book is the original and classic source of design patterns, including the Null Object pattern. It explains the pattern in detail, with examples in C++ and Smalltalk.