Summary
Principle Name | Open/Closed Principle |
Acronym | OCP |
Other Names | Protected Variations |
Principle Type | SOLID |
Tagline | Software 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)-
- Interface Segregation Principle(ISP) segregates the interfaces and keeps the class closed for modification.
- Liskov Substitution Principle(LSP) ensures that subclasses are extensions of a superclass, which creates classes that are open to extension.
Let’s clearly define, what we can and can not do as part of this principle-
Open for Extension (What You Can Do)
Closed for Modification (What You Cannot Do)
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));
}
}
Javaclass 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))
Pythonpackage 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);
PHPclass 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));
TypeScriptWARNING
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))
Pythonpackage 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
PHPabstract 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
TypeScriptLater 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))
Pythonpackage 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
PHPabstract 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
TypeScriptExample #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
}
}
Javaclass 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
Pythonpackage 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
PHPclass 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
TypeScriptWARNING
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());
}
}
Javafrom 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()}")
Pythonpackage 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()}`);
TypeScriptAnd 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
}
}
Javafrom 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()}")
Pythonpackage 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()}`);
TypeScriptCognitive Clarifications
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.