avatarItchimonji

Summary

The web content discusses refactoring switch statements in Angular using object composition and the Factory Method pattern to adhere to the Open-Closed Principle, improve code flexibility, and reduce maintenance effort.

Abstract

The article "How to Refactor a Switch Statement with Object Composition in Angular" explores the drawbacks of using switch statements, which violate the Open-Closed Principle and lead to high maintenance efforts due to the need for frequent modifications. It provides an example of a typical switch statement in an Angular application for theme selection and demonstrates how object composition and the Factory Method pattern can be used to refactor such code. By abstracting the feature into classes and implementing a factory for each theme, the code becomes more scalable, reusable, and compliant with the Open-Closed Principle. The article concludes by showing the final refactored Angular component code, which no longer requires a switch statement, and advocates for the use of creational patterns to enhance software design and maintainability.

Opinions

  • Switch statements are considered problematic as they require modifications whenever new cases are added, potentially leading to errors and maintenance issues.
  • Object composition is presented as a superior method for refactoring switch statements, offering greater flexibility and ease of handling.
  • The Factory Method pattern is highlighted as an effective solution to avoid the pitfalls of switch statements, allowing for scalable and maintainable code.
  • The author emphasizes the importance of adhering to the Open-Closed Principle to ensure that software entities are open for extension but closed for modification.
  • The article suggests that with practice in design patterns and software architecture, developers can more easily refactor code and avoid common issues associated with switch statements.
  • The author encourages following SOLID principles, particularly the single responsibility principle, to create cleaner and more maintainable components.

How to Refactor a Switch Statement with Object Composition in Angular

Using Creational Patterns like Factory Method to refactor switch statements in Angular

Programmers try to avoid switch statements because switches break the rule of the Open-Closed-Principle. Also, continuously extending the switch statement with more clauses leads to a high maintenance effort, as we need to modify the written code every time we extend it. Maybe our working unit tests will fail after the change. We might need to fix our business logic and tests every time — what a nightmare…

“The Problem with such duplicate switches is that, whenever you add a clause, you have to find all the switches and update them.” (Martin Fowler, [Refactoring])

Why do we use switch statements?

We use them because switch statements work the way our brain works. If we have to implement a selection of different things — like a selection of themes, or types of tea/coffee we want to cook, or a selection of cars we want to buy with our saved money — the logical way is to implement different junctions to map all the possibilities: we code like a flow.

Example code

In Angular, using an HTML element like <select> seduces us to handle it with switch statements to check the chosen value. Let us take a list of themes as an example:

<div class="container">
  <h1>Select theme with switch statement</h1>
  <mat-form-field appearance="fill">
    <mat-label>Theme selection</mat-label>
    <mat-select [(ngModel)]="selectedTheme"  (selectionChange)="selectionChanged()">
      <mat-option value="dark">dark theme</mat-option>
      <mat-option value="light">light theme</mat-option>
      <mat-option value="red">red theme</mat-option>
      <mat-option value="green">green theme</mat-option>
    </mat-select>
  </mat-form-field>
</div>

Additionally to the HTML code, we need to implement a controller as follows:

public selectionChanged() {
    switch (this.selectedTheme): void {
      case 'dark':
        console.log('changed theme to dark');
        // do something more with the theme
        break;
      case 'light':
        console.log('changed theme to light');
        // do something more with the theme
        break;
      case 'red':
        console.log('changed theme to red');
        // do something more with the theme
        break;
      case 'green':
        console.log('changed theme to green');
        // do something more with the theme
        break;
      default:
        console.log('What ever..');
    }
}

As we can see, we cover all cases of the selection inside the switch statement and implement some logic to see the output in the development console.

If we want to add more themes, we have to change the existing switch statement — more precisely: we have to modify the existing code. If we have more switch statements in other modules using the current selectedTheme (maybe as a parameter), we need to add the new case there, too. We have to comb the entire codebase for switch statements regarding the theme.

Time wasting? Error-prone, isn’t it?

Is there a better way?

Of course — Object Composition is a nice method for refactoring switch statements and making our code more flexible and decoupled. Because we abstract a simple use case into a complex and easy-to-handle software design principle (in our case: a pattern), it reduces the maintenance effort and makes it more reusable. Sometimes it is tough to find the right way and the suitable software architecture — but with more practice in Design Patterns and Software Architecture, it could be easy.

If we want to know more about Object Composition and Code Reuse in Angular, scroll to the Learn More section below.

Object Composition & Factory Method

Object Composition can help. Regarding the theme example, we only need to abstract the use case of the feature —managing themes, and classes of themes. So, why using string attributes and no real classes? We can, for example, couple these concrete classes with an interface or an abstract class at design time:

Made by Itchimonji

Here are the concrete theme classes based on the interface above:

Made by Itchimonji

Now we can design a factory for every concrete theme class, coupled with an abtract creator class to hide the logic from the client. Something like this:

Made by Itchimonji

Now look, we implemented the Factory Method Pattern of the “Design Pattern” by GoF.

This construct is easily scalable when we want to add more themes. In this case, we only need to create one more theme class and a creator class. We do not need to touch any code we have written before. We do not need to change any tests we have implemented yet. We comply with the Open-Closed-Principle.

The final code

So, let us show how the code looks if we implement this principle in Angular (from UML to code).

First of all, we need a theme interface and some concrete models:

// theme.interface.ts

export interface Theme {
  name: string;
  getStylename(): string;
  getFontname(): string;
}
// theme.model.ts

export class DarkTheme implements Theme {
  public name = 'dark';

  public getStylename(): string {
    return this.name;
  }

  public getFontname(): string {
    return 'Arial';
  }
}

export class LightTheme implements Theme {
  public name = 'light';

  public getStylename(): string {
    return this.name;
  }

  public getFontname(): string {
    return 'Comic Sans MS';
  }
}

// any other themes

Additionally, we need to implement our abstract creator class and the inherited concrete creator classes:

// theme.creator.ts

import { DarkTheme, LightTheme, RedTheme, Theme } from './theme.model';

export abstract class ThemeCreator {
  public abstract factoryMethod(): Theme;
}

export class DarkThemeCreator extends ThemeCreator {
  public factoryMethod(): Theme {
    return new DarkTheme();
  }
}

export class LightThemeCreator extends ThemeCreator {
  public factoryMethod(): Theme {
    return new LightTheme();
  }
}

// any other creator classes

To help us to encapsulate the different architecture levels, we can implement a facade-like helper unit, where we create the other theme objects. The function addAvailableThemesToList is the only one we need to extend, if we want to add new themes — we will add them to an array.

// theme.facade.ts

import { DarkThemeCreator, LightThemeCreator, RedThemeCreator, ThemeCreator } from './theme.creator';
import { Theme } from './theme.model';

export function addAvailableThemesToList(): Theme[] {
  return [
    clientCode(new DarkThemeCreator()),
    clientCode(new LightThemeCreator()),
    clientCode(new RedThemeCreator())
  ];
}

function clientCode(creator: ThemeCreator): Theme {
  return creator.factoryMethod();
}

Finally, we integrate it into our Angular Component, which receives the themes from the facade-like unit. So, we do not need the switch statement anymore and got a very CLEAN component.

// theming-with-factory-method.component.ts

export class ThemingWithFactoryMethodComponent implements OnInit {

  public selectedTheme: Theme;
  public themes: Theme[];

  constructor() {
    this.themes = new Array<Theme>();
  }

  ngOnInit(): void {
    this.themes = addAvailableThemesToList();
  }

  public selectionChanged(): void {
    // read abstract class
    console.log('changed theme to ' + this.selectedTheme.name);
  }

  public trackByFn(index, item: Theme): string {
    return item.name;
  }
}

Regardingthe controller, the view looks like this:

<div class="container">
  <h1>Select attribute with factory method</h1>

  <mat-form-field appearance="fill">
    <mat-label>Theme selection</mat-label>
    <mat-select [(ngModel)]="selectedTheme"  (selectionChange)="selectionChanged()">
      <mat-option *ngFor="let theme of themes; trackBy: trackByFn" [value]="theme">
        {{ theme.name }}
      </mat-option>
    </mat-select>
  </mat-form-field>

</div>

We see there is no ngSwitch either. We comply with the Open-Closed-Principle with a simple Factory Method. We have got only one place where the themes are created. The component doesn’t know all operations of the business logic. Its only responsibility is to receive the themes and show them to the user. That is all.

“A class should only have a single responsibility […]”

Conclusion

With the family of creational patterns, especially the Factory Method Pattern of this example, we can refactor switch statements to comply with the Open-Closed-Principle.

Another way to refactor switches is polymorphism. Martin Fowler describes some excellent examples in his book “Refactoring” about that. Take a look.

Thanks for reading! Follow me on Medium, Twitter, or Instagram, or subscribe here on Medium to read more about DevOps, Agile & Development Principles, Angular, and other useful stuff. Happy Coding! :)

Code example on Github

Learn More

Resources

Angular
Object Composition
Switch Statement
Refactoring
Factory Method
Recommended from ReadMedium