[TWIL] Week of July 21, 2024

Sean KimSean Kim
4 min read

Hi builders! I’ve been working on making the profile page public and adding a new feature to it. While diving into the platform’s codebase, I wanted to explore the principles of good code design more deeply. Today, I’d like to share what I learned about the Open-Closed Principle (OCP) and some thoughts on its application.

Open-Closed Principle

The Open-Closed Principle (OCP) is one of the five object-oriented programming principles known as SOLID. The essence of OCP is that a class should be open for extension but closed for modification. Instead of just describing what this means, let’s look at some code to illustrate.

Consider this example:

enum DrinkType {
  COKE,
  SPRITE,
  WATER
}

class IngredientAnalyzer {
  sugarAmount(drinkType: DrinkType) {
    if (drinkType === DrinkType.COKE) {
      return 64;
    } else if (drinkType === DrinkType.SPRITE) {
      return 51;
    } else if (drinkType === DrinkType.WATER) {
      return 0;
    }

    throw new Error("Not a valid drink type");
  }
}

Seems straightforward, right? But what happens when we introduce another drink type? We’d have to update the enum and modify the sugarAmount method by adding another if case:

enum DrinkType {
  COKE,
  SPRITE,
  WATER,
  FANTA
}

class IngredientAnalyzer {
  sugarAmount(drinkType: DrinkType) {
    if (drinkType === DrinkType.COKE) {
      return 64;
    } else if (drinkType === DrinkType.SPRITE) {
      return 51;
    } else if (drinkType === DrinkType.WATER) {
      return 0;
    } else if (drinkType === DrinkType.FANTA) {
      return 66;
    }

    throw new Error("Not a valid drink type");
  }
}

This works, but what if there’s a way to add new drink types without modifying the sugarAmount method? This is where the OCP comes in.

By converting each enum value into its own class and using an abstract class with the sugarAmount method, we can apply OCP. Here’s how:

abstract class Drink {
  sugarAmount() {
    return 0;
  }
}

class Coke extends Drink {
  sugarAmount() {
    return 61;
  }
}

class SPRITE extends Drink {
  sugarAmount() {
    return 61;
  }
}

class FANTA extends Drink {
  sugarAmount() {
    return 66;
  }
}

class IngredientAnalyzer {
  sugarAmount(drink: Drink) {
    return drink.sugarAmount();
  }
}

Now, if you want to add a new drink type, simply create a new class that inherits from Drink, without touching the IngredientAnalyzer.

But What About Functional Programming?

I had the same question until I came across an article by Alex Nault, which explains how OCP can be expressed through composition. Here’s an example using React components:

function Text() {
  return <p>Hi Builders!</p>;
}

function Page() {
  return <Text />;
}

What if you want to render a Glimmer component while fetching data from an API? One way is to early return Glimmer within Text:

type Props = {
  isFetching: boolean;
}

function Glimmer() {
  return <div>This is a glimmer component</div>;
}

function Text({ 
  isFetching
}: Props) {
  if (isFetching) {
    return <Glimmer />  
  }

  return <p>Hi Builders!</p>;
}

function Page({
  isFetching
}: {
  isFetching: boolean
}) {
  return <Text isFetching={isFetching} />;
}

However, instead of the code above, we can apply OCP here by using the children prop:

type Props = {
  isFetching: boolean;
  children: React.ReactNode;
}

function Glimmer({
  isFetching,
  children 
}: Props) {
  if (isFetching) {
    return <div>This is a glimmer component</div>;
  }

  return children;
}

function Text() {
  return <p>Hi Builders!</p>;
}

function Page({
  isFetching
}: {
  isFetching: boolean
}) {
  return (
    <Glimmer isFetching={isFetching}>
      <Text />
    </Glimmer>
  );
}

This approach introduces the Glimmer component without modifying the Text component. As it turns out, OCP through composition is widely used in React components, such as the Suspense component.

Is It Actually Beneficial?

This is a valid question. At first glance, you might think that OCP requires more code, and in simple cases, the benefits might not be immediately apparent. However, as your application grows in complexity, the ability to extend functionality without modifying existing, working code becomes increasingly valuable. In such scenarios, OCP is a principle worth considering.

That’s it for this week! I hope this deep dive into OCP helps you in designing better code. Happy hacking ☕️

0
Subscribe to my newsletter

Read articles from Sean Kim directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sean Kim
Sean Kim

Engineer @ ___ | Ex-Ourkive | Ex-Meta