In the ever-evolving landscape of software development, writing clean and readable code is paramount. Design patterns such as the Fluent Interface, Builder, and Facade pattern stand out as champions in promoting code expressiveness and flexibility. In this blog post, we’ll explore these patterns and demonstrate their implementation in TypeScript.
I also made a vlog about working on a project using these design patterns on YouTube.
What is Design Pattern?
A design pattern is a reusable solution to a common problem encountered within a specific context in software design. It represents best practices evolved over time by experienced software developers. Design patterns capture the essential elements of a successful design and provide a template or blueprint for solving particular issues.
These patterns are not necessarily code or libraries that can be directly be converted into code. Instead, they are high-level concepts that can be adapted to various situations. Design patterns can speed up development process by providing tested, proven development paradigms.
Some well-known design patterns include the Singleton, Factory, Observer, and Decorator pattern. Each pattern addresses a specific type of problem, and understanding these patterns can significantly improve a developer’s ability to design robust and maintainable software systems.
Fluent Interface Design Pattern
The Fluent Interface design pattern is a powerful technique that enables method chaining, making your code read like a fluent language. It enhances the readability of your API, providing a natural and intuitive way to interact with objects.
An example of a fluent interface would be a request builder for our API.
const response = await AuthRequest.builder()
.withPath('api/token')
.build()
.fetch()
Under the hood of all that is the code to create a Request
. It includes an object called Builder
that sets all the pieces of necessary data to build a request and and finally fetch the data necessary from the given path. But all of that is hidden.
Method Chaining
I’d like to point out that Fluent Interface is not, but very similar to, Method Chaining. Method chaining is a programming style where multiple method calls are chained together in a single expression. Each method in the chain typically returns the host object, and the subsequent method is called on that returned object. This style is often used to make code more concise and readable.
Fluent Interface is a more specific form of method chaining where the methods are designed to be expressive, and the chain read more like a sentence. The goal is to create a code that is easy to understand and resembles a domain-specific language. One really good example would be a SQL query builder.
Query.builder()
.select('*')
.from('baz')
.where('foo > ?', 9000)
.build()
.execute()
The code above creates an SQL statement. The following SQL statement returns all records with foo
that is greater than 9000.
Builder Design Pattern
The Builder design pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations.
const response = await WebRequest.builder()
.withPath('me/albums')
.withAccessToken(`${AuthorizationType.Bearer} ${accessToken}`)
.withContentType(ContentType.ApplicationJSON)
.withMethod(Method.GET)
It’s particularly useful when dealing with objects that can have multiple configurations. With our code above, the builder
is customisable and allows us to construct a WebRequest
object step by step. APIs can be complex that an endpoint will not always have a method of GET
, or path of me/albums
, etc. This also helps make the code much more readable.
Facade Design Pattern
Complex systems often comprise numerous components in software design. As system evolves, managing these intricacies becomes a challenge. This is where the Facade design pattern comes in to play.
Facade pattern simplifies the interactions for the client by providing a higher-level interface and shielding them from the complexities within the application. It promotes simplicity and reduces dependencies by providing a single, well-defined entry point to a subsystem.
export const getAccessToken = async function(code) {
const auth = generateAuthorisationToken(...)
const response = await AuthRequest.builder()
// builder and request
...
return response.json();
}
In the example above, the getAccessToken encapsulates interactions with the subsystem – generating authorisation token, creating a request using a request builder, and processing the response and returning the required data. Clients can now interact with the functionality through a simplified interface provided by the API.
As you may have already guessed, the Facade pattern is prevalent in various software systems, primarily the Application Programming Interface. So yes, all Facade is an API and so as any other code which you use as an interface into a more complicated system.
System Design
Implementing these patterns not only enhances code readability but also promotes a more modular and maintainable codebase. It empowers developers to create objects with different configurations without sacrificing clarity.
Whether you are constructing complex objects in a fluent manner or separating the construction process from representation, these patterns provide a robust foundation for expressive and readable code. Consider incorporating design patterns when dealing design your application to enhance the overall architecture and usability of your software.
Conclusion
Adopting the Fluent Interface, Builder, and Facade patterns can transform your code into a readable, and maintainable software. You can adapt these patterns to fit your specific needs, but remember that not all design patterns will be the same for all system. You will have to use a design pattern that suits your specific needs.
Leave a Reply