Design Principle: Open/Closed Principle(OCP)

Summary

Principle NameOpen/Closed Principle
AcronymOCP
Other NamesProtected Variations
Principle Type SOLID
TaglineSoftware entities should be open for extension,
but closed for modification
Implementing
Design Patterns
Abstract Factory Pattern
Command Pattern
Decorator Pattern
Factory Method Pattern
Observer Pattern
Strategy Pattern
Template Method Pattern
Visitor Pattern
Related Principles Single Responsibility Principle(SRP)
Liskov Substitution Principle(LSP)
Interface Segregation Principle(ISP)
Dependency Inversion Principle(DIP)

Definition

Open/Closed Principle(OCP) is the second principle of the SOLID Principles.

Modifying an entity(classes, modules, etc.) in code has a high chance of introducing the risk of breaking parts of code(which relies on the changed part).

Open/Close Principle(OCP) says-

Software entities(classes, modules, etc.), should be open for extension but closed for modification.

It means that once a software entity is defined and implemented, we should not directly change it to add new functionality. Instead, we should extend it using inheritance or interfaces to accommodate new behavior or functionality.

OCP is often achieved through the use of abstraction, polymorphism, and inheritance. This principle enables us to introduce new features or behavior without breaking or altering the existing code base. This approach eliminates unintended side effects and/or bugs when we make changes.

OCP is very closely related to ISP and LSP. Let’s see how those two principles help Open/Closed Principle(OCP)-

Let’s clearly define, what we can and can not do as part of this principle-

Open for Extension (What You Can Do)

Add New Class/Module: we can add new classes and/or modules to extend new functionality.
Create Subclasses: we can inherit from existing class(es) and in the subclass(es) add new implementations or override existing implementations, to accommodate new behavior.
Implement Interfaces: we can create a new implementation for an existing interface, that has a different behavior.
Use Composition: we can use a composition of existing classes(or objects) to achieve new behavior.
New Functionality via Plugins: we can create a system that supports plugins, and then add new plugins to change behavior or introduce new features.

Closed for Modification (What You Cannot Do)

Change Existing Code: we can not modify existing code inside classes or methods.
Alter Existing Functionality: we cannot change any existing behavior in the current implementation.
Remove Existing entity: we can not remove any class/module/function.
Rename Existing entity: we can not rename any class/module/function.
Modify Class Interfaces: we can not change the interface of an existing class, as that will break the classes that already implement the interface.

Implementation

Let’s take a look at a few examples, to understand the implementation process.

Example #1: Discount Calculation

Initial Implementation (without OCP)

Let’s say we have a customer class, and we provide different percentages of discount depending on the type of customer.

class Customer {
    private String name;
    private String customerType;

    public Customer(String name, String customerType) {
        this.name = name;
        this.customerType = customerType;
    }

    public double getDiscount(double amount) {
        if (customerType.equals("regular")) {
            return amount * 0.05;  // 5% discount for regular customers
        } else if (customerType.equals("premium")) {
            return amount * 0.10;  // 10% discount for premium customers
        }
        return 0;
    }

    public static void main(String[] args) {
        Customer customer = new Customer("Demo Customer", "regular");
        System.out.println(customer.getDiscount(100));
    }
}
Java
class Customer:
    def __init__(self, name: str, customer_type: str) -> None:
        self.name = name
        self.customer_type = customer_type

    def get_discount(self, amount: float) -> float:
        if self.customer_type == "regular":
            return amount * 0.05  # 5% discount for regular customers
        elif self.customer_type == "premium":
            return amount * 0.10  # 10% discount for premium customers

        return 0


# Usage demo
customer = Customer("Demo Customer", "regular")
print(customer.get_discount(100))
Python
package main

import (
    "fmt"
)

type Customer struct {
    Name        string
    CustomerType string
}

func (c *Customer) GetDiscount(amount float64) float64 {
    if c.CustomerType == "regular" {
        return amount * 0.05 // 5% discount for regular customers
    } else if c.CustomerType == "premium" {
        return amount * 0.10 // 10% discount for premium customers
    }
    return 0
}

func main() {
    customer := Customer{"Demo Customer", "regular"}
    fmt.Println(customer.GetDiscount(100))
}
Go
<?php

class Customer {
    private string $name;
    private string $customerType;

    public function __construct(string $name, string $customerType) {
        $this->name = $name;
        $this->customerType = $customerType;
    }

    public function getDiscount(float $amount): float {
        if ($this->customerType === "regular") {
            return $amount * 0.05;  // 5% discount for regular customers
        } elseif ($this->customerType === "premium") {
            return $amount * 0.10;  // 10% discount for premium customers
        }
        return 0;
    }
}

// Usage demo
$customer = new Customer("Demo Customer", "regular");
echo $customer->getDiscount(100);
PHP
class Customer {
    private name: string;
    private customerType: string;

    constructor(name: string, customerType: string) {
        this.name = name;
        this.customerType = customerType;
    }

    getDiscount(amount: number): number {
        if (this.customerType === "regular") {
            return amount * 0.05;  // 5% discount for regular customers
        } else if (this.customerType === "premium") {
            return amount * 0.10;  // 10% discount for premium customers
        }
        return 0;
    }
}

// Usage demo
const customer = new Customer("Demo Customer", "regular");
console.log(customer.getDiscount(100));
TypeScript

WARNING

The problem with this is, that if we want to introduce a new customer type “VIP” then we have to change the “get_discount” function.

Which we don’t want to do. We completely want to avoid making any direct changes to the existing code.

Applying OCP

To avoid the later direct changes to accommodate the “VIP” customer type, we want our first implementation to be like the below-

abstract class Customer {
    protected String name;

    public Customer(String name) {
        this.name = name;
    }

    public abstract double getDiscount(double amount);
}

class RegularCustomer extends Customer {
    public RegularCustomer(String name) {
        super(name);
    }

    @Override
    public double getDiscount(double amount) {
        return amount * 0.05;  // 5% discount for regular customers
    }
}

class PremiumCustomer extends Customer {
    public PremiumCustomer(String name) {
        super(name);
    }

    @Override
    public double getDiscount(double amount) {
        return amount * 0.10;  // 10% discount for premium customers
    }
}

// Discount Calculation
public class Main {
    public static double calculateDiscount(Customer customer, double amount) {
        double discount = customer.getDiscount(amount);
        return amount - discount;
    }

    public static void main(String[] args) {
        Customer regularCustomer = new RegularCustomer("Simple Box");
        Customer premiumCustomer = new PremiumCustomer("Premium Box");
        
        System.out.println(calculateDiscount(regularCustomer, 100)); // Output: 95.0
        System.out.println(calculateDiscount(premiumCustomer, 100)); // Output: 90.0
    }
}
Java
# Base class
class Customer:
    def __init__(self, name: str)-> None:
        self.name = name

    def get_discount(self, amount: float)-> float:
        raise NotImplementedError("Subclasses must implement this method")

# Subclass for regular customers
class RegularCustomer(Customer):
    def get_discount(self, amount: float)-> float:
        return amount * 0.05  # 5% discount for regular customers

# Subclass for premium customers
class PremiumCustomer(Customer):
    def get_discount(self, amount: float)-> float:
        return amount * 0.10  # 10% discount for premium customers

# Discount Calculation
def calculate_discount(customer, amount):
    discount = customer.get_discount(amount)
    return amount - discount

# Demo usage
regular_customer = RegularCustomer("Simple Box")
premium_customer = PremiumCustomer("Premium Box")
vip_customer = VIPCustomer("VIP Box")

print(calculate_discount(regular_customer, 100))
print(calculate_discount(premium_customer, 100))
Python
package main

import (
    "fmt"
)

// Base class (interface)
type Customer interface {
    GetDiscount(amount float64) float64
}

// Subclass for regular customers
type RegularCustomer struct {
    Name string
}

func (rc *RegularCustomer) GetDiscount(amount float64) float64 {
    return amount * 0.05 // 5% discount for regular customers
}

// Subclass for premium customers
type PremiumCustomer struct {
    Name string
}

func (pc *PremiumCustomer) GetDiscount(amount float64) float64 {
    return amount * 0.10 // 10% discount for premium customers
}

// Discount Calculation
func calculateDiscount(customer Customer, amount float64) float64 {
    discount := customer.GetDiscount(amount)
    return amount - discount
}

func main() {
    regularCustomer := &RegularCustomer{"Simple Box"}
    premiumCustomer := &PremiumCustomer{"Premium Box"}

    fmt.Println(calculateDiscount(regularCustomer, 100)) // Output: 95
    fmt.Println(calculateDiscount(premiumCustomer, 100)) // Output: 90
}
Go
<?php

abstract class Customer {
    protected string $name;

    public function __construct(string $name) {
        $this->name = $name;
    }

    abstract public function getDiscount(float $amount): float;
}

class RegularCustomer extends Customer {
    public function getDiscount(float $amount): float {
        return $amount * 0.05;  // 5% discount for regular customers
    }
}

class PremiumCustomer extends Customer {
    public function getDiscount(float $amount): float {
        return $amount * 0.10;  // 10% discount for premium customers
    }
}

// Discount Calculation
function calculateDiscount(Customer $customer, float $amount): float {
    $discount = $customer->getDiscount($amount);
    return $amount - $discount;
}

// Demo usage
$regularCustomer = new RegularCustomer("Simple Box");
$premiumCustomer = new PremiumCustomer("Premium Box");

echo calculateDiscount($regularCustomer, 100) . "\n"; // Output: 95
echo calculateDiscount($premiumCustomer, 100) . "\n"; // Output: 90
PHP
abstract class Customer {
    protected name: string;

    constructor(name: string) {
        this.name = name;
    }

    abstract getDiscount(amount: number): number;
}

class RegularCustomer extends Customer {
    getDiscount(amount: number): number {
        return amount * 0.05;  // 5% discount for regular customers
    }
}

class PremiumCustomer extends Customer {
    getDiscount(amount: number): number {
        return amount * 0.10;  // 10% discount for premium customers
    }
}

// Discount Calculation
function calculateDiscount(customer: Customer, amount: number): number {
    const discount = customer.getDiscount(amount);
    return amount - discount;
}

// Demo usage
const regularCustomer = new RegularCustomer("Simple Box");
const premiumCustomer = new PremiumCustomer("Premium Box");

console.log(calculateDiscount(regularCustomer, 100)); // Output: 95
console.log(calculateDiscount(premiumCustomer, 100)); // Output: 90
TypeScript

Later if we just add a class for VIP customers and inherit from the customer class, and it will work without any change in the existing code.

abstract class Customer {
    protected String name;

    public Customer(String name) {
        this.name = name;
    }

    public abstract double getDiscount(double amount);
}

class RegularCustomer extends Customer {
    public RegularCustomer(String name) {
        super(name);
    }

    @Override
    public double getDiscount(double amount) {
        return amount * 0.05;  // 5% discount for regular customers
    }
}

class PremiumCustomer extends Customer {
    public PremiumCustomer(String name) {
        super(name);
    }

    @Override
    public double getDiscount(double amount) {
        return amount * 0.10;  // 10% discount for premium customers
    }
}

class VIPCustomer extends Customer {
    public VIPCustomer(String name) {
        super(name);
    }

    @Override
    public double getDiscount(double amount) {
        return amount * 0.15;  // 15% discount for VIP customers
    }
}

// Discount Calculation
public class Main {
    public static double calculateDiscount(Customer customer, double amount) {
        double discount = customer.getDiscount(amount);
        return amount - discount;
    }

    public static void main(String[] args) {
        Customer regularCustomer = new RegularCustomer("Simple Box");
        Customer premiumCustomer = new PremiumCustomer("Premium Box");
        Customer vipCustomer = new VIPCustomer("VIP Box");

        System.out.println(calculateDiscount(regularCustomer, 100)); // Output: 95.0
        System.out.println(calculateDiscount(premiumCustomer, 100)); // Output: 90.0
        System.out.println(calculateDiscount(vipCustomer, 100)); // Output: 85.0
    }
}
Java
# Base class
class Customer:
    def __init__(self, name: str)-> None:
        self.name = name

    def get_discount(self, amount: float)-> float:
        raise NotImplementedError("Subclasses must implement this method")

# Subclass for regular customers
class RegularCustomer(Customer):
    def get_discount(self, amount: float)-> float:
        return amount * 0.05  # 5% discount for regular customers

# Subclass for premium customers
class PremiumCustomer(Customer):
    def get_discount(self, amount: float)-> float:
        return amount * 0.10  # 10% discount for premium customers

# Subclass for VIP customers
class VIPCustomer(Customer):
    def get_discount(self, amount: float)-> float:
        return amount * 0.15  # 15% discount for VIP customers

# Discount Calculation
def calculate_discount(customer, amount):
    discount = customer.get_discount(amount)
    return amount - discount

# Demo usage
regular_customer = RegularCustomer("Simple Box")
premium_customer = PremiumCustomer("Premium Box")
vip_customer = VIPCustomer("VIP Box")

print(calculate_discount(regular_customer, 100))
print(calculate_discount(premium_customer, 100))
print(calculate_discount(vip_customer, 100))
Python
package main

import (
    "fmt"
)

// Base class (interface)
type Customer interface {
    GetDiscount(amount float64) float64
}

// Subclass for regular customers
type RegularCustomer struct {
    Name string
}

func (rc *RegularCustomer) GetDiscount(amount float64) float64 {
    return amount * 0.05 // 5% discount for regular customers
}

// Subclass for premium customers
type PremiumCustomer struct {
    Name string
}

func (pc *PremiumCustomer) GetDiscount(amount float64) float64 {
    return amount * 0.10 // 10% discount for premium customers
}

// Subclass for VIP customers
type VIPCustomer struct {
    Name string
}

func (vc *VIPCustomer) GetDiscount(amount float64) float64 {
    return amount * 0.15 // 15% discount for VIP customers
}

// Discount Calculation
func calculateDiscount(customer Customer, amount float64) float64 {
    discount := customer.GetDiscount(amount)
    return amount - discount
}

func main() {
    regularCustomer := &RegularCustomer{"Simple Box"}
    premiumCustomer := &PremiumCustomer{"Premium Box"}
    vipCustomer := &VIPCustomer{"VIP Box"}

    fmt.Println(calculateDiscount(regularCustomer, 100)) // Output: 95
    fmt.Println(calculateDiscount(premiumCustomer, 100)) // Output: 90
    fmt.Println(calculateDiscount(vipCustomer, 100))     // Output: 85
}
Go
<?php

abstract class Customer {
    protected string $name;

    public function __construct(string $name) {
        $this->name = $name;
    }

    abstract public function getDiscount(float $amount): float;
}

class RegularCustomer extends Customer {
    public function getDiscount(float $amount): float {
        return $amount * 0.05;  // 5% discount for regular customers
    }
}

class PremiumCustomer extends Customer {
    public function getDiscount(float $amount): float {
        return $amount * 0.10;  // 10% discount for premium customers
    }
}

class VIPCustomer extends Customer {
    public function getDiscount(float $amount): float {
        return $amount * 0.15;  // 15% discount for VIP customers
    }
}

// Discount Calculation
function calculateDiscount(Customer $customer, float $amount): float {
    $discount = $customer->getDiscount($amount);
    return $amount - $discount;
}

// Demo usage
$regularCustomer = new RegularCustomer("Simple Box");
$premiumCustomer = new PremiumCustomer("Premium Box");
$vipCustomer = new VIPCustomer("VIP Box");

echo calculateDiscount($regularCustomer, 100) . "\n"; // Output: 95
echo calculateDiscount($premiumCustomer, 100) . "\n"; // Output: 90
echo calculateDiscount($vipCustomer, 100) . "\n"; // Output: 85
PHP
abstract class Customer {
    protected name: string;

    constructor(name: string) {
        this.name = name;
    }

    abstract getDiscount(amount: number): number;
}

class RegularCustomer extends Customer {
    getDiscount(amount: number): number {
        return amount * 0.05;  // 5% discount for regular customers
    }
}

class PremiumCustomer extends Customer {
    getDiscount(amount: number): number {
        return amount * 0.10;  // 10% discount for premium customers
    }
}

class VIPCustomer extends Customer {
    getDiscount(amount: number): number {
        return amount * 0.15;  // 15% discount for VIP customers
    }
}

// Discount Calculation
function calculateDiscount(customer: Customer, amount: number): number {
    const discount = customer.getDiscount(amount);
    return amount - discount;
}

// Demo usage
const regularCustomer = new RegularCustomer("Simple Box");
const premiumCustomer = new PremiumCustomer("Premium Box");
const vipCustomer = new VIPCustomer("VIP Box");

console.log(calculateDiscount(regularCustomer, 100)); // Output: 95
console.log(calculateDiscount(premiumCustomer, 100)); // Output: 90
console.log(calculateDiscount(vipCustomer, 100));     // Output: 85
TypeScript

Example #2: Calculate Area

Initial Implementation (without OCP)

Let’s consider a system that calculates area. Initially, we implemented it for a circle. and later we want to introduce rectangles and triangles.

public class AreaCalculator {
    public double calculateArea(String shape, double... args) {
        switch (shape.toLowerCase()) {
            case "rectangle":
                return args[0] * args[1]; // width, height
            case "triangle":
                return 0.5 * args[0] * args[1]; // base, height
            case "circle":
                return Math.PI * args[0] * args[0]; // radius
            default:
                throw new IllegalArgumentException("Unknown shape");
        }
    }

    public static void main(String[] args) {
        AreaCalculator calculator = new AreaCalculator();
        double rectangleArea = calculator.calculateArea("rectangle", 5, 10);
        double triangleArea = calculator.calculateArea("triangle", 4, 8);
        double circleArea = calculator.calculateArea("circle", 3);

        System.out.println("Rectangle Area: " + rectangleArea);  // Output the area of rectangle
        System.out.println("Triangle Area: " + triangleArea);    // Output the area of triangle
        System.out.println("Circle Area: " + circleArea);        // Output the area of circle
    }
}
Java
class AreaCalculator:
    def calculate_area(self, shape, *args):
        if shape == "rectangle":
            width, height = args
            return width * height
        elif shape == "triangle":
            base, height = args
            return 0.5 * base * height
        elif shape == "circle":
            radius, = args
            return 3.14159 * (radius ** 2)
        else:
            raise ValueError("Unknown shape")

# Usage Demo
if __name__ == "__main__":
    calculator = AreaCalculator()
    rectangle_area = calculator.calculate_area("rectangle", 5, 10)
    triangle_area = calculator.calculate_area("triangle", 4, 8)
    circle_area = calculator.calculate_area("circle", 3)

    print(f"Rectangle Area: {rectangle_area}")  # Output the area of rectangle
    print(f"Triangle Area: {triangle_area}")    # Output the area of triangle
    print(f"Circle Area: {circle_area}")        # Output the area of circle
Python
package main

import (
    "fmt"
    "math"
)

// AreaCalculator struct
type AreaCalculator struct{}

// Method to calculate area based on shape type
func (ac *AreaCalculator) CalculateArea(shape string, args ...float64) float64 {
    switch shape {
    case "rectangle":
        return args[0] * args[1] // width, height
    case "triangle":
        return 0.5 * args[0] * args[1] // base, height
    case "circle":
        return math.Pi * args[0] * args[0] // radius
    default:
        panic("Unknown shape")
    }
}

func main() {
    calculator := &AreaCalculator{}
    rectangleArea := calculator.CalculateArea("rectangle", 5, 10)
    triangleArea := calculator.CalculateArea("triangle", 4, 8)
    circleArea := calculator.CalculateArea("circle", 3)

    fmt.Printf("Rectangle Area: %f\n", rectangleArea)  // Output the area of rectangle
    fmt.Printf("Triangle Area: %f\n", triangleArea)    // Output the area of triangle
    fmt.Printf("Circle Area: %f\n", circleArea)        // Output the area of circle
}
Go
<?php

class AreaCalculator {
    public function calculateArea($shape, ...$args) {
        switch (strtolower($shape)) {
            case 'rectangle':
                return $args[0] * $args[1]; // width, height
            case 'triangle':
                return 0.5 * $args[0] * $args[1]; // base, height
            case 'circle':
                return pi() * $args[0] * $args[0]; // radius
            default:
                throw new InvalidArgumentException("Unknown shape");
        }
    }
}

// Usage
$calculator = new AreaCalculator();
$rectangleArea = $calculator->calculateArea('rectangle', 5, 10);
$triangleArea = $calculator->calculateArea('triangle', 4, 8);
$circleArea = $calculator->calculateArea('circle', 3);

echo "Rectangle Area: $rectangleArea\n";  // Output the area of rectangle
echo "Triangle Area: $triangleArea\n";    // Output the area of triangle
echo "Circle Area: $circleArea\n";        // Output the area of circle
PHP
class AreaCalculator {
    calculateArea(shape: string, ...args: number[]): number {
        switch (shape.toLowerCase()) {
            case 'rectangle':
                return args[0] * args[1]; // width, height
            case 'triangle':
                return 0.5 * args[0] * args[1]; // base, height
            case 'circle':
                return Math.PI * args[0] * args[0]; // radius
            default:
                throw new Error("Unknown shape");
        }
    }
}

// Usage
const calculator = new AreaCalculator();
const rectangleArea = calculator.calculateArea('rectangle', 5, 10);
const triangleArea = calculator.calculateArea('triangle', 4, 8);
const circleArea = calculator.calculateArea('circle', 3);

console.log(`Rectangle Area: ${rectangleArea}`);  // Output the area of rectangle
console.log(`Triangle Area: ${triangleArea}`);    // Output the area of triangle
console.log(`Circle Area: ${circleArea}`);        // Output the area of circle
TypeScript

WARNING

If we have only a few shapes in the initial implementation and later we want to introduce a rectangle and triangle. So we need to change the current implementation.

However, we want to avoid any direct change in the current implementation.

Let’s see how we can do it using OCP-

Applying OCP

To avoid later changes, we should implement it like below-

import java.util.ArrayList;
import java.util.List;

// Base class for shapes
abstract class Shape {
    public abstract double area();
}

// Subclass for Circle
class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

// Area Calculation
class AreaCalculator {
    private List<Shape> shapes = new ArrayList<>();

    public void addShape(Shape shape) {
        shapes.add(shape);
    }

    public double calculateTotalArea() {
        double totalArea = 0;
        for (Shape shape : shapes) {
            totalArea += shape.area();
        }
        return totalArea;
    }
}

// Usage Demo
public class Main {
    public static void main(String[] args) {
        AreaCalculator calculator = new AreaCalculator();
        calculator.addShape(new Circle(3));
        calculator.addShape(new Circle(10));

        System.out.println("Total Area: " + calculator.calculateTotalArea());
    }
}
Java
from abc import ABC, abstractmethod
from typing import List

# Base class for shapes
class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

# Subclass for Circle
class Circle(Shape):
    def __init__(self, radius: float) -> None:
        self.radius = radius

    def area(self) -> float:
        return 3.14159 * (self.radius ** 2)

# Area Calculation
class AreaCalculator:
    def __init__(self):
        self.shapes: List[Shape] = []

    def add_shape(self, shape: Shape) -> None:
        self.shapes.append(shape)

    def calculate_total_area(self) -> float:
        total_area = sum(shape.area() for shape in self.shapes)
        return total_area

# Usage demo
calculator = AreaCalculator()
calculator.add_shape(Circle(3))
calculator.add_shape(Circle(10))

print(f"Total Area: {calculator.calculate_total_area()}")
Python
package main

import (
    "fmt"
    "math"
)

// Base class (interface)
type Shape interface {
    Area() float64
}

// Subclass for Circle
type Circle struct {
    Radius float64
}

func (c *Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

// Area Calculation
type AreaCalculator struct {
    shapes []Shape
}

func (ac *AreaCalculator) AddShape(shape Shape) {
    ac.shapes = append(ac.shapes, shape)
}

func (ac *AreaCalculator) CalculateTotalArea() float64 {
    totalArea := 0.0
    for _, shape := range ac.shapes {
        totalArea += shape.Area()
    }
    return totalArea
}

// Usage demo
func main() {
    calculator := &AreaCalculator{}
    calculator.AddShape(&Circle{3})
    calculator.AddShape(&Circle{10})

    fmt.Printf("Total Area: %f\n", calculator.CalculateTotalArea())
}
Go
<?php

// Base class for shapes
abstract class Shape {
    abstract public function area(): float;
}

// Subclass for Circle
class Circle extends Shape {
    private float $radius;

    public function __construct(float $radius) {
        $this->radius = $radius;
    }

    public function area(): float {
        return pi() * $this->radius * $this->radius;
    }
}

// Area Calculation
class AreaCalculator {
    private array $shapes = [];

    public function addShape(Shape $shape): void {
        $this->shapes[] = $shape;
    }

    public function calculateTotalArea(): float {
        $totalArea = 0;
        foreach ($this->shapes as $shape) {
            $totalArea += $shape->area();
        }
        return $totalArea;
    }
}

// Usage demo
$calculator = new AreaCalculator();
$calculator->addShape(new Circle(3));
$calculator->addShape(new Circle(10));

echo "Total Area: " . $calculator->calculateTotalArea() . "\n";
PHP
// Base class for shapes
abstract class Shape {
    abstract area(): number;
}

// Subclass for Circle
class Circle extends Shape {
    constructor(private radius: number) {
        super();
    }

    area(): number {
        return Math.PI * this.radius * this.radius;
    }
}

// Area Calculation
class AreaCalculator {
    private shapes: Shape[] = [];

    addShape(shape: Shape): void {
        this.shapes.push(shape);
    }

    calculateTotalArea(): number {
        return this.shapes.reduce((total, shape) => total + shape.area(), 0);
    }
}

// Usage
const calculator = new AreaCalculator();
calculator.addShape(new Circle(3));
calculator.addShape(new Circle(10));

console.log(`Total Area: ${calculator.calculateTotalArea()}`);
TypeScript

And when we introduce rectangles and triangles, we do not need to make any changes in the existing code.

import java.util.ArrayList;
import java.util.List;

// Base class for shapes
abstract class Shape {
    public abstract double area();
}

// Subclass for Rectangle
class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double area() {
        return width * height;
    }
}

// Subclass for Triangle
class Triangle extends Shape {
    private double base;
    private double height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    public double area() {
        return 0.5 * base * height;
    }
}

// Subclass for Circle
class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

// Area Calculation
class AreaCalculator {
    private List<Shape> shapes = new ArrayList<>();

    public void addShape(Shape shape) {
        shapes.add(shape);
    }

    public double calculateTotalArea() {
        double totalArea = 0;
        for (Shape shape : shapes) {
            totalArea += shape.area();
        }
        return totalArea;
    }
}

// Usage Demo
public class Main {
    public static void main(String[] args) {
        AreaCalculator calculator = new AreaCalculator();
        calculator.addShape(new Rectangle(5, 10));
        calculator.addShape(new Triangle(4, 8));
        calculator.addShape(new Circle(3));

        System.out.println("Total Area: " + calculator.calculateTotalArea()); // Output total area
    }
}
Java
from abc import ABC, abstractmethod
from typing import List

# Base class for shapes
class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

# Subclass for Rectangle
class Rectangle(Shape):
    def __init__(self, width: float, height: float) -> None:
        self.width = width
        self.height = height

    def area(self) -> float:
        return self.width * self.height

# Subclass for Triangle
class Triangle(Shape):
    def __init__(self, base: float, height: float) -> None:
        self.base = base
        self.height = height

    def area(self) -> float:
        return 0.5 * self.base * self.height

# Subclass for Circle
class Circle(Shape):
    def __init__(self, radius: float) -> None:
        self.radius = radius

    def area(self) -> float:
        return 3.14159 * (self.radius ** 2)

# Area Calculation
class AreaCalculator:
    def __init__(self):
        self.shapes: List[Shape] = []

    def add_shape(self, shape: Shape) -> None:
        self.shapes.append(shape)

    def calculate_total_area(self) -> float:
        total_area = sum(shape.area() for shape in self.shapes)
        return total_area

# Usage demo
calculator = AreaCalculator()
calculator.add_shape(Rectangle(5, 10))
calculator.add_shape(Triangle(4, 8))
calculator.add_shape(Circle(3))

print(f"Total Area: {calculator.calculate_total_area()}")
Python
package main

import (
    "fmt"
    "math"
)

// Base class (interface)
type Shape interface {
    Area() float64
}

// Subclass for Rectangle
type Rectangle struct {
    Width  float64
    Height float64
}

func (r *Rectangle) Area() float64 {
    return r.Width * r.Height
}

// Subclass for Triangle
type Triangle struct {
    Base   float64
    Height float64
}

func (t *Triangle) Area() float64 {
    return 0.5 * t.Base * t.Height
}

// Subclass for Circle
type Circle struct {
    Radius float64
}

func (c *Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

// Area Calculation
type AreaCalculator struct {
    shapes []Shape
}

func (ac *AreaCalculator) AddShape(shape Shape) {
    ac.shapes = append(ac.shapes, shape)
}

func (ac *AreaCalculator) CalculateTotalArea() float64 {
    totalArea := 0.0
    for _, shape := range ac.shapes {
        totalArea += shape.Area()
    }
    return totalArea
}

// Usage demo
func main() {
    calculator := &AreaCalculator{}
    calculator.AddShape(&Rectangle{5, 10})
    calculator.AddShape(&Triangle{4, 8})
    calculator.AddShape(&Circle{3})

    fmt.Printf("Total Area: %f\n", calculator.CalculateTotalArea())
}
Go
<?php

// Base class for shapes
abstract class Shape {
    abstract public function area(): float;
}

// Subclass for Rectangle
class Rectangle extends Shape {
    private float $width;
    private float $height;

    public function __construct(float $width, float $height) {
        $this->width = $width;
        $this->height = $height;
    }

    public function area(): float {
        return $this->width * $this->height;
    }
}

// Subclass for Triangle
class Triangle extends Shape {
    private float $base;
    private float $height;

    public function __construct(float $base, float $height) {
        $this->base = $base;
        $this->height = $height;
    }

    public function area(): float {
        return 0.5 * $this->base * $this->height;
    }
}

// Subclass for Circle
class Circle extends Shape {
    private float $radius;

    public function __construct(float $radius) {
        $this->radius = $radius;
    }

    public function area(): float {
        return pi() * $this->radius * $this->radius;
    }
}

// Area Calculation
class AreaCalculator {
    private array $shapes = [];

    public function addShape(Shape $shape): void {
        $this->shapes[] = $shape;
    }

    public function calculateTotalArea(): float {
        $totalArea = 0;
        foreach ($this->shapes as $shape) {
            $totalArea += $shape->area();
        }
        return $totalArea;
    }
}

// Usage demo
$calculator = new AreaCalculator();
$calculator->addShape(new Rectangle(5, 10));
$calculator->addShape(new Triangle(4, 8));
$calculator->addShape(new Circle(3));

echo "Total Area: " . $calculator->calculateTotalArea() . "\n";
PHP
// Base class for shapes
abstract class Shape {
    abstract area(): number;
}

// Subclass for Rectangle
class Rectangle extends Shape {
    constructor(private width: number, private height: number) {
        super();
    }

    area(): number {
        return this.width * this.height;
    }
}

// Subclass for Triangle
class Triangle extends Shape {
    constructor(private base: number, private height: number) {
        super();
    }

    area(): number {
        return 0.5 * this.base * this.height;
    }
}

// Subclass for Circle
class Circle extends Shape {
    constructor(private radius: number) {
        super();
    }

    area(): number {
        return Math.PI * this.radius * this.radius;
    }
}

// Area Calculation
class AreaCalculator {
    private shapes: Shape[] = [];

    addShape(shape: Shape): void {
        this.shapes.push(shape);
    }

    calculateTotalArea(): number {
        return this.shapes.reduce((total, shape) => total + shape.area(), 0);
    }
}

// Usage
const calculator = new AreaCalculator();
calculator.addShape(new Rectangle(5, 10));
calculator.addShape(new Triangle(4, 8));
calculator.addShape(new Circle(3));

console.log(`Total Area: ${calculator.calculateTotalArea()}`);
TypeScript

Cognitive Clarifications

Should we follow OCP strictly in case of a bug fix?

Ideally, YES.

A bug fix still means a change in the code, and that change can have side effects in a large code base.

So, in the OCP approach, in the case of bug bix, we should-

– extend the broken class.
– create a bug-free subclass, and use that in our use case.
– leave the old(buggy) class in the codebase.

We can mark the old(buggy) classes as deprecated, and can remove those in the next big refactor.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.