Typed Builder Pattern in TypeScript
The Builder pattern is a creational design pattern that allows you to construct complex objects step-by-step. This is particularly useful when an object has many optional parameters or configurations. This challenge asks you to implement a Builder pattern in TypeScript, ensuring strong typing throughout the process to improve code safety and maintainability.
Problem Description
You are tasked with creating a Product class and a ProductBuilder class in TypeScript. The Product class will represent a complex product with several properties, some of which are optional. The ProductBuilder will provide a fluent interface for constructing Product instances, allowing clients to set properties in a controlled and readable manner.
What needs to be achieved:
- Define a
Productclass with properties:name(string, required),description(string, optional),price(number, required),category(string, optional), andtags(string[], optional). - Create a
ProductBuilderclass that allows setting each of these properties individually. - The
ProductBuildershould have methods for settingname,description,price,category, andtags. - The
ProductBuildershould have abuild()method that returns a fully constructedProductinstance. - Ensure that the
nameandpriceproperties are required and cannot be left unset during the building process.
Key Requirements:
- Strong Typing: All properties and methods should be strongly typed to prevent errors.
- Fluent Interface: The builder methods should return the builder instance itself (
this) to allow for method chaining. - Immutability (Optional but Recommended): Consider making the
Productinstance immutable after it's built. - Error Handling: The
build()method should throw an error if the required properties (name,price) are not set.
Expected Behavior:
A client should be able to use the ProductBuilder to create Product instances with varying configurations. The builder should enforce the required properties and provide a clear and readable way to set optional properties.
Edge Cases to Consider:
- What happens if the client tries to build a product without setting the required properties?
- How should the builder handle invalid input (e.g., a non-positive price)? (This is not strictly required, but demonstrates good design.)
- Consider how to handle the
tagsproperty, which is an array of strings.
Examples
Example 1:
Input: Using the builder to create a product with all properties set.
builder.setName("Awesome Widget").setDescription("A truly awesome widget").setPrice(19.99).setCategory("Gadgets").setTags(["widget", "awesome", "new"]);
Output: A Product object with name: "Awesome Widget", description: "A truly awesome widget", price: 19.99, category: "Gadgets", tags: ["widget", "awesome", "new"]
Explanation: The builder sets all properties as specified and returns a fully configured Product.
Example 2:
Input: Using the builder to create a product with only required properties set.
builder.setName("Basic Product").setPrice(9.99);
Output: A Product object with name: "Basic Product", price: 9.99, description: undefined, category: undefined, tags: undefined
Explanation: Only the required properties are set; optional properties remain undefined.
Example 3: (Edge Case)
Input: Attempting to build a product without setting the name.
builder.setPrice(25.00).build();
Output: Error: "Name is required."
Explanation: The build method throws an error because the required 'name' property was not set.
Constraints
- The
nameproperty must be a string. - The
descriptionproperty must be a string or undefined. - The
priceproperty must be a number greater than 0. - The
categoryproperty must be a string or undefined. - The
tagsproperty must be an array of strings or undefined. - The
build()method must throw an error ifnameorpriceare not set. - The solution must be written in TypeScript.
Notes
- Consider using TypeScript's optional properties and union types to define the properties of the
Productclass. - The fluent interface is a key aspect of the Builder pattern. Make sure your builder methods return
thisto enable method chaining. - Think about how to handle potential errors, such as invalid input values. While not strictly required, it demonstrates good design principles.
- Focus on creating a clean, readable, and well-typed solution.