Beyond Basics: Streamline Your Typescript Code With Fluent Interface Design Pattern
Tags: typescript, programming, designpatterns, architecture
Published: 2023-03-10 13:27:52

The Fluent Interface is a design pattern used to create object-oriented APIs that are easy to read and use. It allows developers to write code that reads like natural language. It achieves this by chaining multiple method calls together, resulting in a more readable, expressive, and intuitive syntax.

The principle behind the Fluent Interface is to allow developers to create a series of small, easy-to-read method calls that can be chained together. Each method call returns an instance of the object it was called on, allowing the developer to call the next method in the chain on the returned object.

## Implementing Fluent Interface

Let's look at a simple example to illustrate how the Fluent Interface pattern works in Typescript:

```typescript
class Person {
  private firstName: string;
  private lastName: string;
  private age: number;
  
  setFirstName(firstName: string): Person {
    this.firstName = firstName;
    return this;
  }

  setLastName(lastName: string): Person {
    this.lastName = lastName;
    return this;
  }
  
  setAge(age: number): Person {
    this.age = age;
    return this;
  }
  
  getInfo(): string {
    return `Name: ${this.firstName} ${this.lastName}, Age: ${this.age}`;
  }
}

const person = new Person()
  .setFirstName('John')
  .setLastName('Doe')
  .setAge(30);

console.log(person.getInfo()); // Name: John Doe, Age: 30
```

Example in [CodeSandbox](https://codesandbox.io/p/sandbox/beyond-basics-typescript-fluent-interface-01-zjbyb8).

As you can see, each fluent method in the `Person` class returns `this`, which is a reference to itself. As it is also an instance of `Person` class, we can access all methods available on `Person` class again.

## Beyond Basics

Now let's look at how we can implement the Fluent Interface in our own variation of one of the most popular testing libraries for JavaScript and TypeScript applications - [Jest](https://jestjs.io/). Jest mimics the Fluent Interface pattern in its `expect` method to provide a more natural and intuitive syntax for writing assertions in tests.

The `expect` method in Jest takes an actual value and returns an object that allows developers to make various assertions on that value. Here's an example of how it can be used:

```typescript
test('should return the correct sum of two numbers', () => {
  const result = sum(2, 3);
  expect(result).not.toBe(6);
});
```

Here we use the `expect` method to assert the result of a `sum` function. We chain the `not` and `toBe` method onto the object returned by `expect` to assert that the result of the `sum` method is not equal to the number 6.

Now let's look at how we could implement the `expect` method and underlying `Expect` class using the Fluent Interface pattern:

```typescript
class Expect<T> {
  constructor(private actual: T) {}

  toBe(expected: T): this {
    if (this.actual !== expected) {
      throw new Error(`Expected ${expected}, but got ${this.actual}`);
    }
    return this;
  }

  not: NotExpect<T> = new NotExpect(this.actual);

  // other assertion methods...
}

class NotExpect<T> {
  constructor(private actual: T) {}

  toBe(expected: T): this {
    if (this.actual === expected) {
      throw new Error(`Expected ${expected} not to be ${this.actual}`);
    }
    return this;
  }

  // other assertion methods...
}

function expect<T>(actual: T): Expect<T> {
  return new Expect<T>(actual);
}
```

See the example in this [CodeSandbox](https://codesandbox.io/p/sandbox/beyond-basics-typescript-fluent-interface-02-1tmp11?file=%2Fsrc%2Findex.ts).

In this implementation, the `not` method is a member of the `Expect` class that returns a new instance of the `NotExpect` class. The `NotExpect` class has similar assertion methods to the `Expect` class, but all of its assertion methods negate the assertion.

The `toBe` method in the `NotExpect` class checks that the actual value is not equal to the expected value. If the values are equal, the method throws an error with a negated error message.

### Benefits

Benefits of Fluent Interface Pattern:

1. **Readability**: The Fluent Interface pattern improves the readability of code by providing a more natural and intuitive syntax.
    
2. **Chaining**: The pattern allows developers to chain multiple method calls, resulting in more concise and easy-to-read code.
    
3. **Flexibility**: The pattern provides flexibility in creating and configuring objects.
    

### Drawbacks

The main two drawbacks of the Fluent Interface Pattern are overuse and with it connected issues with maintainability:

1. **Overuse**: The Fluent Interface pattern can be overused, resulting in code that is difficult to read and maintain. Do not use this pattern in every place where it can be used, but reserve it for cases where it makes sense. Such cases can be, as in our example, fluent assertions or builders for complex objects that can have many ways of the optional configuration of their internal state.
    
2. **Maintainability**: Adding new functionality to a fluent interface means extending the class that implements it. This might result in large classes with many methods if the interface grows. In that case, it is important to carefully split the class into multiple sub-groups and create an abstraction on top of them that connects them, so the interface can still be used fluently.
    

When used appropriately, the Fluent Interface pattern can result in code that is easier to read, write, and maintain. It is especially useful in testing libraries, where readability and ease of use are critical. Overall, the Fluent Interface pattern is a valuable tool in every developer's toolkit and is worth considering when designing applications.

Photo by <a href="https://unsplash.com/fr/@cgower?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Christopher Gower</a> on <a href="https://unsplash.com/photos/m_HRfLhgABo?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>