When designing software, we often aim for clean, maintainable, and flexible code. One way to achieve this is by following the SOLID principles, a set of five guidelines for object-oriented design. Today, we’ll dive into the Interface Segregation Principle (ISP), the “I” in SOLID, and explore it with a practical PHP example involving meal preferences.
What is the Interface Segregation Principle?
The Interface Segregation Principle, introduced by Robert C. Martin, states that clients should not be forced to depend on interfaces they do not use. In other words, instead of creating large, all-purpose interfaces, we should design smaller, specific ones tailored to the needs of the classes that implement them. This reduces unnecessary dependencies, keeps code modular, and makes it easier to adapt to change.
Think of it like a restaurant menu: if you’re a vegan, you don’t want a giant menu cluttered with meat and dairy options you’ll never order. A concise vegan menu is more useful and less overwhelming. ISP applies the same logic to software interfaces.
Why ISP Matters
Without ISP, you might end up with “fat interfaces”. Big, bulky contracts that force implementing classes to deal with methods they don’t need. This leads to:
- Tight coupling: Classes depend on irrelevant functionality.
- Maintenance headaches: Changes to one part of the interface affect unrelated clients.
- Awkward implementations: Classes might have to provide empty or “not implemented” methods.
By segregating interfaces, we ensure each class only cares about what’s relevant to its purpose.
A Meal Example in PHP
Let’s illustrate ISP with a real-world scenario: preparing meals for different dietary preferences—Vegetarian, Vegan, Pescatarian, and Normal (omnivore). We’ll use PHP to model this.
The Wrong Way: A Fat Interface
Imagine a single Meal
interface like this:
phpinterface Meal {
public function prepareMeat();
public function prepareFish();
public function prepareVegetables();
public function prepareDairy();
}
If we force all meal types to implement this:
-
A
VeganMeal
doesn’t needprepareMeat()
,prepareFish()
, orprepareDairy()
. -
A
VegetarianMeal
doesn’t needprepareMeat()
orprepareFish()
. -
A
PescatarianMeal
doesn’t needprepareMeat()
.
This violates ISP because classes are stuck implementing methods they don’t use. Let’s fix it.
The ISP Way: Segregated Interfaces
Instead, we’ll break it into smaller, focused interfaces:
php<?php
interface MeatPreparable {
public function prepareMeat();
}
interface FishPreparable {
public function prepareFish();
}
interface VegetablePreparable {
public function prepareVegetables();
}
interface DairyPreparable {
public function prepareDairy();
}
// Vegetarian Meal: Vegetables and dairy
class VegetarianMeal implements VegetablePreparable, DairyPreparable {
public function prepareVegetables() {
return "Preparing vegetables for vegetarian meal.";
}
public function prepareDairy() {
return "Adding cheese to vegetarian meal.";
}
}
// Vegan Meal: Only vegetables
class VeganMeal implements VegetablePreparable {
public function prepareVegetables() {
return "Preparing vegetables for vegan meal (no animal products).";
}
}
// Pescatarian Meal: Vegetables and fish
class PescatarianMeal implements VegetablePreparable, FishPreparable {
public function prepareVegetables() {
return "Preparing vegetables for pescatarian meal.";
}
public function prepareFish() {
return "Grilling fish for pescatarian meal.";
}
}
// Normal Meal: Everything
class NormalMeal implements MeatPreparable, FishPreparable, VegetablePreparable, DairyPreparable {
public function prepareMeat() {
return "Cooking meat for normal meal.";
}
public function prepareFish() {
return "Grilling fish for normal meal.";
}
public function prepareVegetables() {
return "Preparing vegetables for normal meal.";
}
public function prepareDairy() {
return "Adding dairy to normal meal.";
}
}
// Function to serve the meal
function serveMeal($meal) {
$output = [];
if ($meal instanceof MeatPreparable) {
$output[] = $meal->prepareMeat();
}
if ($meal instanceof FishPreparable) {
$output[] = $meal->prepareFish();
}
if ($meal instanceof VegetablePreparable) {
$output[] = $meal->prepareVegetables();
}
if ($meal instanceof DairyPreparable) {
$output[] = $meal->prepareDairy();
}
return implode("\n", $output);
}
// Test it out
$vegetarian = new VegetarianMeal();
$vegan = new VeganMeal();
$pescatarian = new PescatarianMeal();
$normal = new NormalMeal();
echo "Vegetarian Meal:\n" . serveMeal($vegetarian) . "\n\n";
echo "Vegan Meal:\n" . serveMeal($vegan) . "\n\n";
echo "Pescatarian Meal:\n" . serveMeal($pescatarian) . "\n\n";
echo "Normal Meal:\n" . serveMeal($normal) . "\n\n";
?>
Output
Vegetarian Meal:
Preparing vegetables for vegetarian meal.
Adding cheese to vegetarian meal.
Vegan Meal:
Preparing vegetables for vegan meal (no animal products).
Pescatarian Meal:
Preparing vegetables for pescatarian meal.
Grilling fish for pescatarian meal.
Normal Meal:
Cooking meat for normal meal.
Grilling fish for normal meal.
Preparing vegetables for normal meal.
Adding dairy to normal meal.
How This Follows ISP
-
Segregation: Each interface (
MeatPreparable
,FishPreparable
, etc.) is small and specific. Classes only implement what they need. -
Flexibility: The
VeganMeal
isn’t forced to deal with dairy or meat, and thePescatarianMeal
skips meat entirely. -
Client Control: The
serveMeal
function checks capabilities withinstanceof
, ensuring it only calls relevant methods.
Benefits in Action
- Modularity: Adding a new diet (e.g., Keto) just means combining the right interfaces—no need to touch existing classes.
- Clean Code: No empty method implementations or “not supported” exceptions.
-
Maintainability: Changes to
FishPreparable
won’t affectVeganMeal
, which doesn’t care about fish.
Conclusion
The Interface Segregation Principle is all about keeping your interfaces lean and purposeful. By breaking down a bloated Meal
interface into smaller ones, we created a system where each meal type only depends on what it actually uses. This PHP example shows how ISP can make your code more intuitive, adaptable, and aligned with real-world needs—just like serving the perfect meal to the right diner.
Next time you’re designing an interface, ask yourself: “Does this client need all these methods?” If not, it’s time to segregate!
Album of the day: