
Software Design•8 min read•May 14, 2025
Understanding the Open-Closed Principle in Software Engineering
AS
Asif Shaikh
Mobile Engineer
# Understanding the Open-Closed Principle in Software Engineering
Welcome back to our series on the SOLID principles of software engineering! In Part 1, we explored the Single Responsibility Principle, learning how each class should have just one job to keep our code clean and manageable.
Today, in Part 2, we're diving into the Open-Closed Principle (OCP), a super useful idea that makes our code flexible and easy to grow, like a LEGO set that you can keep adding to without breaking what's already built.
We'll explain OCP in simple, everyday terms, use Kotlin (a popular language for Android development) for examples, and even sprinkle in some Android-inspired scenarios to make it relatable.
So let's get started 🚀
## What is the Open-Closed Principle?
The Open-Closed Principle sounds fancy, but it's actually pretty straightforward.
It says:
> "Software entities (like classes, modules, or functions) should be _open for extension_ but _closed for modification_."
Let's break that down:
Open for extension: You can add new features or behaviors to your code easily, like adding a new character to a game.
Closed for modification: You shouldn't have to change the existing code to add those new features, so the original code stays safe and untouched.
Think of it like a smartphone. When you want a new feature, like a cool camera filter, you download a new app or update (extension). You don't need to crack open the phone and rewire its circuits (modification). OCP helps us design code that works the same way—new stuff can be added without messing with what's already there.
## Why Does OCP Matter?
Imagine you're building an Android app, like a to-do list app. Users love it, but they start asking for new features, like reminders or color-coded tasks. If you have to rewrite big chunks of your original code every time you add something new, you might accidentally break the app, frustrate users, or spend hours fixing bugs. OCP helps you avoid this by making your code flexible and ready for growth.
Following OCP means:
Your app can handle new features without breaking.
It's easier to maintain and test.
* You save time and avoid headaches when requirements change (and they _always_ change!).
## A Real-World Example: The To-Do List App
Let's say you're building a to-do list app for Android. You have a feature that displays tasks in a simple list. But now, users want different ways to display tasks—like a grid view or a calendar view. Without OCP, you might end up with code that looks like this:
### What's Wrong Here?
kotlin
01class TaskDisplay {02fun displayTasks(tasks: List<Task>, viewType: String) {03when (viewType) {04"list" -> displayAsList(tasks)05"grid" -> displayAsGrid(tasks)06"calendar" -> displayAsCalendar(tasks)07else -> displayAsList(tasks)08}09}1011private fun displayAsList(tasks: List<Task>) { /* ... */ }12private fun displayAsGrid(tasks: List<Task>) { /* ... */ }13private fun displayAsCalendar(tasks: List<Task>) { /* ... */ }14}
Every time you want to add a new display type (say, a "kanban" view), you have to _modify_ the
TaskDisplay class and add another if statement. This:Makes the code messy and hard to read.
Risks breaking existing display types if you make a mistake.
* Violates OCP because the class isn't _closed for modification_.
### Fixing It with OCP
To follow OCP, we can redesign the code so it's _open for extension_ (you can add new display types) but _closed for modification_ (you don't change the original code). Here's how we can do it using Kotlin and an interface:
kotlin
01interface TaskDisplay {02fun display(tasks: List<Task>)03}0405class ListDisplay : TaskDisplay {06override fun display(tasks: List<Task>) {07// Display tasks as a list08}09}1011class GridDisplay : TaskDisplay {12override fun display(tasks: List<Task>) {13// Display tasks as a grid14}15}1617class CalendarDisplay : TaskDisplay {18override fun display(tasks: List<Task>) {19// Display tasks as a calendar20}21}2223class TaskManager(private val display: TaskDisplay) {24fun showTasks(tasks: List<Task>) {25display.display(tasks)26}27}
### How Does This Follow OCP?
Open for extension: Want a new display type, like a kanban view? Just create a new class (e.g.,
KanbanDisplay) that implements TaskDisplay. No need to touch the existing code.Closed for modification: The
TaskManager and existing display classes stay unchanged when you add new display types.Here's how you'd use it:
kotlin
01val listDisplay = ListDisplay()02val taskManager = TaskManager(listDisplay)03taskManager.showTasks(tasks)
Now, adding a new
KanbanDisplay is as simple as:kotlin
01class KanbanDisplay : TaskDisplay {02override fun display(tasks: List<Task>) {03// Display tasks as a kanban board04}05}0607// Usage08val kanbanDisplay = KanbanDisplay()09val taskManager = TaskManager(kanbanDisplay)
No changes to
TaskManager or other classes needed!## Another Android Example: Notifications
Let's look at another Android example to drive this home. Suppose your app sends notifications to users, like reminders for tasks. Initially, you only support push notifications, but later you want to add email notifications and SMS notifications.
### Without OCP
You might write something like:
kotlin
01class NotificationSender {02fun sendNotification(message: String, type: String) {03when (type) {04"push" -> sendPushNotification(message)05"email" -> sendEmailNotification(message)06"sms" -> sendSMSNotification(message)07}08}0910private fun sendPushNotification(message: String) { /* ... */ }11private fun sendEmailNotification(message: String) { /* ... */ }12private fun sendSMSNotification(message: String) { /* ... */ }13}
Again, adding a new notification type (like in-app notifications) means modifying the
NotificationSender class, which violates OCP.### With OCP
Here's a better way:
kotlin
01interface Notification {02fun send(message: String)03}0405class PushNotification : Notification {06override fun send(message: String) {07// Send push notification08}09}1011class EmailNotification : Notification {12override fun send(message: String) {13// Send email notification14}15}1617class SMSNotification : Notification {18override fun send(message: String) {19// Send SMS notification20}21}2223class NotificationManager(private val notification: Notification) {24fun notifyUser(message: String) {25notification.send(message)26}27}
Now, if you want to add an in-app notification, just create a new class:
kotlin
01class InAppNotification : Notification {02override fun send(message: String) {03// Show in-app notification04}05}0607// Usage08val inAppNotification = InAppNotification()09val notificationManager = NotificationManager(inAppNotification)
The
NotificationManager doesn't need any changes, and your app can keep growing with new notification types.## Tips for Applying OCP in Your Code
1. Use Interfaces or Abstract Classes: They let you define a contract (like
TaskDisplay or Notification) that new classes can follow without changing existing code.2. Think About Future Features: When designing a feature, ask, "What might users want next?" Design your code to make those additions easy.
3. Keep It Simple: Don't overcomplicate things with too many interfaces. Use OCP where you expect change, like features that might expand (e.g., display types, notifications).
4. Test Your Extensions: Since you're adding new classes, make sure they work as expected without breaking the rest of the app.
## Wrapping Up
The Open-Closed Principle is like building a house with extra rooms ready for guests—you can welcome new features without tearing down walls. By designing your code to be _open for extension_ and _closed for modification_, you make your Android apps (or any software) easier to maintain, grow, and test.
In our to-do list and notification examples, we saw how using interfaces in Kotlin lets us add new display types or notification methods without touching the original code. This saves time, reduces bugs, and keeps your app flexible for whatever users dream up next.
Stay tuned for Part 3 of our SOLID series, where we'll explore the Liskov Substitution Principle! In the meantime, try applying OCP to your next Android project. Have you used OCP before, or do you have a feature in mind where it could help? Let me know in the comments!
Happy coding, and see you in the next post!
---
_This blog post is part of a series on SOLID principles. Check out Part 1: Single Responsibility Principle if you missed it!_
Published on May 14, 2025 by Asif Shaikh