Advanced Builder Pattern in TypeScript: Customizable Product Creation
The Builder pattern is a creational design pattern that allows you to construct complex objects step-by-step. This challenge focuses on implementing an advanced version of the Builder pattern in TypeScript, enabling flexible and customizable product creation with optional features and chaining capabilities. This is useful when object creation is complex and you want to decouple the construction process from the object's representation.
Problem Description
You are tasked with designing and implementing a Builder pattern for creating Car objects. A Car has several properties: make, model, year, color, engineType (e.g., "V6", "Inline-4"), hasSunroof (boolean), hasNavigation (boolean), and numberOfDoors (integer). The builder should allow clients to specify these properties in a fluent, chainable manner.
Key Requirements:
- Fluent Interface: The builder methods should return the builder instance itself, allowing for method chaining.
- Optional Features:
hasSunroofandhasNavigationshould be optional features that can be enabled or disabled. - Default Values: Provide sensible default values for all properties if they are not explicitly set by the client.
- Validation: Implement basic validation to ensure
yearis a valid year (e.g., between 1900 and the current year) andnumberOfDoorsis within a reasonable range (2-5). Throw an error if validation fails. - Build Method: A
build()method should return a fully constructedCarobject.
Expected Behavior:
The client should be able to create Car objects with varying levels of customization. The builder should handle default values and validation gracefully.
Edge Cases to Consider:
- Invalid year values.
- Invalid
numberOfDoorsvalues. - Creating a car with only essential properties (make, model, year).
- Creating a car with all properties specified.
- Chaining multiple optional feature settings.
Examples
Example 1:
Input: new CarBuilder().make("Toyota").model("Camry").year(2023).color("Silver").build()
Output: { make: "Toyota", model: "Camry", year: 2023, color: "Silver", engineType: "Inline-4", hasSunroof: false, hasNavigation: false, numberOfDoors: 4 }
Explanation: Creates a Camry with default values for engineType, sunroof, navigation, and number of doors.
Example 2:
Input: new CarBuilder().make("Tesla").model("Model S").year(2024).color("Red").engineType("Electric").hasSunroof(true).hasNavigation(true).numberOfDoors(5).build()
Output: { make: "Tesla", model: "Model S", year: 2024, color: "Red", engineType: "Electric", hasSunroof: true, hasNavigation: true, numberOfDoors: 5 }
Explanation: Creates a Tesla Model S with all properties explicitly set.
Example 3: (Edge Case - Invalid Year)
Input: new CarBuilder().make("Ford").model("Mustang").year(1880).build()
Output: Error: Invalid year. Year must be between 1900 and the current year.
Explanation: Demonstrates validation failing due to an invalid year.
Constraints
- The
yearproperty must be between 1900 and the current year (inclusive). - The
numberOfDoorsproperty must be between 2 and 5 (inclusive). - The
Carclass should be immutable after construction. - The builder should be extensible to support additional car properties in the future without modifying existing code.
- The code should be well-documented and follow TypeScript best practices.
Notes
- Consider using TypeScript's optional chaining (
?.) to handle optional properties gracefully. - Think about how to structure the builder class to promote code reusability and maintainability.
- The
build()method should return a new, immutableCarobject. You can achieve immutability by returning a new object with the specified properties rather than modifying an existing one. - Focus on creating a clean and expressive API for the builder.
- Error handling should be clear and informative.
- The current year can be obtained using
new Date().getFullYear().