Practice Problems for Open/ Closed Principle
Open/Closed Principle (OCP)
Practice Problems
To master the Open/Closed Principle, it's essential to engage in practical exercises and projects. Here are some complete coding exercises, sample projects, quizzes, and multiple-choice questions (MCQs) to help reinforce your understanding.
Coding Exercises
Exercise 1: Refactor a Class to Adhere to OCP:
Given:
- Java
- JavaScript
- Python
public class Invoice {
public double calculateDiscount(Invoice invoice, String type) {
if (type.equals("Regular")) {
return invoice.getAmount() * 0.1;
} else if (type.equals("VIP")) {
return invoice.getAmount() * 0.2;
}
return 0;
}
}
class Invoice {
calculateDiscount(invoice, type) {
if (type === 'Regular') {
return invoice.amount * 0.1
} else if (type === 'VIP') {
return invoice.amount * 0.2
}
return 0
}
}
class Invoice:
def calculate_discount(self, invoice, type):
if type == "Regular":
return invoice.amount * 0.1
elif type == "VIP":
return invoice.amount * 0.2
return 0
Refactored:
- Java
- JavaScript
- Python
public abstract class DiscountStrategy {
public abstract double calculateDiscount(Invoice invoice);
}
public class RegularDiscountStrategy extends DiscountStrategy {
@Override
public double calculateDiscount(Invoice invoice) {
return invoice.getAmount() * 0.1;
}
}
public class VipDiscountStrategy extends DiscountStrategy {
@Override
public double calculateDiscount(Invoice invoice) {
return invoice.getAmount() * 0.2;
}
}
public class Invoice {
private DiscountStrategy discountStrategy;
public Invoice(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double calculateDiscount() {
return discountStrategy.calculateDiscount(this);
}
}
class DiscountStrategy {
calculateDiscount(invoice) {
throw new Error('This method should be overridden')
}
}
class RegularDiscountStrategy extends DiscountStrategy {
calculateDiscount(invoice) {
return invoice.amount * 0.1
}
}
class VipDiscountStrategy extends DiscountStrategy {
calculateDiscount(invoice) {
return invoice.amount * 0.2
}
}
class Invoice {
constructor(discountStrategy) {
this.discountStrategy = discountStrategy
}
calculateDiscount() {
return this.discountStrategy.calculateDiscount(this)
}
}
class DiscountStrategy:
def calculate_discount(self, invoice):
raise NotImplementedError("This method should be overridden")
class RegularDiscountStrategy(DiscountStrategy):
def calculate_discount(self, invoice):
return invoice.amount * 0.1
class VipDiscountStrategy(DiscountStrategy):
def calculate_discount(self, invoice):
return invoice.amount * 0.2
class Invoice:
def __init__(self, discount_strategy):
self.discount_strategy = discount_strategy
def calculate_discount(self):
return self.discount_strategy.calculate_discount(self)
Exercise 2: Add New Functionality without Modifying Existing Code:
Given:
- Java
- JavaScript
- Python
public class NotificationService {
public void sendNotification(String type, String message) {
if (type.equals("Email")) {
sendEmail(message);
} else if (type.equals("SMS")) {
sendSMS(message);
}
}
private void sendEmail(String message) {
// Email sending logic
}
private void sendSMS(String message) {
// SMS sending logic
}
}
class NotificationService {
sendNotification(type, message) {
if (type === 'Email') {
this.sendEmail(message)
} else if (type === 'SMS') {
this.sendSMS(message)
}
}
sendEmail(message) {
// Email sending logic
}
sendSMS(message) {
// SMS sending logic
}
}
class NotificationService:
def send_notification(self, type, message):
if type == "Email":
self.send_email(message)
elif type == "SMS":
self.send_sms(message)
def send_email(self, message):
# Email sending logic
def send_sms(self, message):
# SMS sending logic
Refactored:
- Java
- JavaScript
- Python
public interface Notification {
void send(String message);
}
public class EmailNotification implements Notification {
@Override
public void send(String message) {
// Email sending logic
}
}
public class SMSNotification implements Notification {
@Override
public void send(String message) {
// SMS sending logic
}
}
public class NotificationService {
private Notification notification;
public NotificationService(Notification notification) {
this.notification = notification;
}
public void sendNotification(String message) {
notification.send(message);
}
}
class Notification {
send(message) {
throw new Error('This method should be overridden')
}
}
class EmailNotification extends Notification {
send(message) {
// Email sending logic
}
}
class SMSNotification extends Notification {
send(message) {
// SMS sending logic
}
}
class NotificationService {
constructor(notification) {
this.notification = notification
}
sendNotification(message) {
this.notification.send(message)
}
}
class Notification:
def send(self, message):
raise NotImplementedError("This method should be overridden")
class EmailNotification(Notification):
def send(self, message):
# Email sending logic
class SMSNotification(Notification):
def send(self, message):
# SMS sending logic
class NotificationService:
def __init__(self, notification):
self.notification = notification
def send_notification(self, message):
self.notification.send(message)
Exercise 3: Use OCP with a Strategy Pattern:
Given:
- Java
- JavaScript
- Python
public class PaymentService {
public void processPayment(String type, double amount) {
if (type.equals("CreditCard")) {
processCreditCardPayment(amount);
} else if (type.equals("PayPal")) {
processPayPalPayment(amount);
}
}
private void processCreditCardPayment(double amount) {
// Credit card payment logic
}
private void processPayPalPayment(double amount) {
// PayPal payment logic
}
}
class PaymentService {
processPayment(type, amount) {
if (type === 'CreditCard') {
this.processCreditCardPayment(amount)
} else if (type === 'PayPal') {
this.processPayPalPayment(amount)
}
}
processCreditCardPayment(amount) {
// Credit card payment logic
}
processPayPalPayment(amount) {
// PayPal payment logic
}
}
class PaymentService:
def process_payment(self, type, amount):
if type == "CreditCard":
self.process_credit_card_payment(amount)
elif type == "PayPal":
self.process_paypal_payment(amount)
def process_credit_card_payment(self, amount):
# Credit card payment logic
def process_paypal_payment(self, amount):
# PayPal payment logic
Refactored:
- Java
- JavaScript
- Python
public interface PaymentStrategy {
void pay(double amount);
}
public class CreditCardPaymentStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
// Credit card payment logic
}
}
public class PayPalPaymentStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
// PayPal payment logic
}
}
public class PaymentService {
private PaymentStrategy paymentStrategy;
public PaymentService(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void processPayment(double amount) {
paymentStrategy.pay(amount);
}
}
class PaymentStrategy {
pay(amount) {
throw new Error('This method should be overridden')
}
}
class CreditCardPaymentStrategy extends PaymentStrategy {
pay(amount) {
// Credit card payment logic
}
}
class PayPalPaymentStrategy extends PaymentStrategy {
pay(amount) {
// PayPal payment logic
}
}
class PaymentService {
constructor(paymentStrategy) {
this.paymentStrategy = paymentStrategy
}
processPayment(amount) {
this.paymentStrategy.pay(amount)
}
}
class PaymentStrategy:
def pay(self, amount):
raise NotImplementedError("This method should be overridden")
class CreditCardPaymentStrategy(PaymentStrategy):
def pay(self, amount):
# Credit card payment logic
class PayPalPaymentStrategy(PaymentStrategy):
def pay(self, amount):
# PayPal payment logic
class PaymentService:
def __init__(self, payment_strategy):
self.payment_strategy = payment_strategy
def process_payment(self, amount):
self.payment_strategy.pay(amount)
Sample Projects
- Project 1: Content Management System:
- Description: Design a content management system (CMS) with separate classes for content creation, content editing, and content publishing. Each class should be open for extension but closed for modification.
- Example:
- Java
- JavaScript
- Python
public abstract class Content {
public abstract void create();
public abstract void edit();
public abstract void publish();
}
public class BlogPost extends Content {
@Override
public void create() {
// Blog post creation logic
}
@Override
public void edit() {
// Blog post editing logic
}
@Override
public void publish() {
// Blog post publishing logic
}
}
public class Article extends Content {
@Override
public void create() {
// Article creation logic
}
@Override
public void edit() {
// Article editing logic
}
@Override
public void publish() {
// Article publishing logic
}
}
class Content {
create() {
throw new Error('This method should be overridden')
}
edit() {
throw new Error('This method should be overridden')
}
publish() {
throw new Error('This method should be overridden')
}
}
class BlogPost extends Content {
create() {
// Blog post creation logic
}
edit() {
// Blog post editing logic
}
publish() {
// Blog post publishing logic
}
}
class Article extends Content {
create() {
// Article creation logic
}
edit() {
// Article editing logic
}
publish() {
// Article publishing logic
}
}
class Content:
def create(self):
raise NotImplementedError("This method should be overridden")
def edit(self):
raise NotImplementedError("This method should be overridden")
def publish(self):
raise NotImplementedError("This method should be overridden")
class BlogPost(Content):
def create(self):
# Blog post creation logic
def edit(self):
# Blog post editing logic
def publish(self):
# Blog post publishing logic
class Article(Content):
def create(self):
# Article creation logic
def edit(self):
# Article editing logic
def publish(self):
# Article publishing logic
- Project 2: E-commerce System:
- Description: Build an e-commerce system where different types of payment methods and shipping methods can be added without modifying the existing codebase.
- Example:
- Java
- JavaScript
- Python
public interface PaymentMethod {
void pay(double amount);
}
public class CreditCardPayment implements PaymentMethod {
@Override
public void pay(double amount) {
// Credit card payment logic
}
}
public class PayPalPayment implements PaymentMethod {
@Override
public void pay(double amount) {
// PayPal payment logic
}
}
public class PaymentService {
private PaymentMethod paymentMethod;
public PaymentService(PaymentMethod paymentMethod) {
this.paymentMethod = paymentMethod;
}
public void processPayment(double amount) {
paymentMethod.pay(amount);
}
}
class PaymentMethod {
pay(amount) {
throw new Error('This method should be overridden')
}
}
class CreditCardPayment extends PaymentMethod {
pay(amount) {
// Credit card payment logic
}
}
class PayPalPayment extends PaymentMethod {
pay(amount) {
// PayPal payment logic
}
}
class PaymentService {
constructor(paymentMethod) {
this.paymentMethod = paymentMethod
}
processPayment(amount) {
this.paymentMethod.pay(amount)
}
}
class PaymentMethod:
def pay(self, amount):
raise NotImplementedError("This method should be overridden")
class CreditCardPayment(PaymentMethod):
def pay(self, amount):
# Credit card payment logic
class PayPalPayment(PaymentMethod):
def pay(self, amount):
# PayPal payment logic
class PaymentService:
def __init__(self, payment_method):
self.payment_method = payment_method
def process_payment(self, amount):
self.payment_method.pay(amount)
Quizzes
- Quiz 1: Identifying Violations of OCP:
- Question: Given the following class, identify the violation of the Open/Closed Principle and suggest how to refactor it.
- Java
- JavaScript
- Python
public class ReportGenerator {
public void generateReport(String type) {
if (type.equals("PDF")) {
generatePDFReport();
} else if (type.equals("Excel")) {
generateExcelReport();
}
}
private void generatePDFReport() {
// PDF report generation logic
}
private void generateExcelReport() {
// Excel report generation logic
}
}
class ReportGenerator {
generateReport(type) {
if (type === 'PDF') {
this.generatePDFReport()
} else if (type === 'Excel') {
this.generateExcelReport()
}
}
generatePDFReport() {
// PDF report generation logic
}
generateExcelReport() {
// Excel report generation logic
}
}
class ReportGenerator:
def generate_report(self, type):
if type == "PDF":
self.generate_pdf_report()
elif type == "Excel":
self.generate_excel_report()
def generate_pdf_report(self):
# PDF report generation logic
def generate_excel_report(self):
# Excel report generation logic
- Answer:
- Java
- JavaScript
- Python
public interface Report {
void generate();
}
public class PDFReport implements Report {
@Override
public void generate() {
// PDF report generation logic
}
}
public class ExcelReport implements Report {
@Override
public void generate() {
// Excel report generation logic
}
}
public class ReportGenerator {
private Report report;
public ReportGenerator(Report report) {
this.report = report;
}
public void generateReport() {
report.generate();
}
}
class Report {
generate() {
throw new Error('This method should be overridden')
}
}
class PDFReport extends Report {
generate() {
// PDF report generation logic
}
}
class ExcelReport extends Report {
generate() {
// Excel report generation logic
}
}
class ReportGenerator {
constructor(report) {
this.report = report
}
generateReport() {
this.report.generate()
}
}
class Report:
def generate(self):
raise NotImplementedError("This method should be overridden")
class PDFReport(Report):
def generate(self):
# PDF report generation logic
class ExcelReport(Report):
def generate(self):
# Excel report generation logic
class ReportGenerator:
def __init__(self, report):
self.report = report
def generate_report(self):
self.report.generate()
- Quiz 2: Benefits of OCP:
- Question: What are the main benefits of adhering to the Open/Closed Principle? Provide examples.
- Answer:
- Maintainability: By ensuring that classes are open for extension but closed for modification, code becomes easier to maintain. New functionality can be added without altering existing code, reducing the risk of introducing bugs.
- Scalability: Systems designed with OCP are more scalable because new features can be added independently of existing functionality.
- Flexibility: Adhering to OCP allows for more flexible code. Different implementations of a functionality can be swapped in and out without modifying the core logic.
- Examples:
- Adding new payment methods to an e-commerce system without modifying the existing payment processing logic.
- Implementing new report formats in a reporting system without changing the core report generation code.
Multiple-Choice Questions (MCQs)
MCQ 1: What does the Open/Closed Principle state?
- A) Software entities should be open for modification but closed for extension
- B) Software entities should be open for extension but closed for modification
- C) Software entities should be closed for both modification and extension
- D) Software entities should be open for both modification and extension
Answer: B) Software entities should be open for extension but closed for modification
MCQ 2: Which of the following is a benefit of adhering to OCP?
- A) Increased code complexity
- B) Reduced code maintainability
- C) Easier to add new functionality
- D) Higher risk of introducing bugs
Answer: C) Easier to add new functionality
MCQ 3: How can the Strategy Pattern help in adhering to OCP?
- A) By allowing multiple algorithms to be defined and swapped without modifying the client code
- B) By enforcing a single algorithm throughout the codebase
- C) By making the code more difficult to extend
- D) By combining multiple responsibilities into a single class
Answer: A) By allowing multiple algorithms to be defined and swapped without modifying the client code