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 ☕️