Cohesion refers to the degree to which the elements of a module belong together. In the context of SRP, it implies that a class should encapsulate only behaviors that are closely related to its functionality. High cohesion within methods suggests that they are focused on a single task or purpose, and thus, they should be grouped within the same class.
Loose coupling describes the level of interdependence between modules. A well-designed system minimizes dependencies between classes, allowing for easier maintenance and scalability. For instance, a controller class in an API should be responsible solely for handling HTTP requests from clients and sending responses back, rather than directly implementing business logic or interacting with the database.
public class Book {
private String title;
private String author;
private String content;
public void printBook() {
System.out.println("Printing book...");
}
public void save() {
System.out.println("Saving book...");
// Code to save the book to a database
}
}
In the given scenario, the Book class is overburdened with multiple responsibilities. It is tasked with managing the book’s data and overseeing its printing and saving processes. This design contravenes the Single Responsibility Principle (SRP), which stipulates that a class should have only one reason to change.
To align with SRP, we can refactor the Book class by segregating its responsibilities into distinct classes. The printBook method can be extracted to a BookPrinter class, dedicated solely to the presentation of the book’s content. Similarly, the save method should be relocated to a BookRepository class, which interacts with the database and handles data persistence. This separation ensures that each class has a single, well-defined role, enhancing maintainability and promoting a cleaner, more modular architecture.
public class Book {
private String title;
private String author;
private String content;
//getter, setter methods
}
public class BookPrinter {
public void printBook(Book book) {
System.out.println("Printing book...");
}
}
public class BookRepository {
public void save(Book book) {
System.out.println("Saving book...");
}
}
2. Open/Closed Principles
Risk Reduction: It minimizes the chance of introducing errors into an already functioning system when changes are required.
Enhanced Adaptability: By allowing new functionalities through extensions rather than modifications, the system becomes more flexible and easier to evolve over time.
Code Reusability: Extensibility inherently promotes the reuse of existing code and reduces redundancy and duplication.
Let’s examine InsurancePremiumDiscountCalculator class in Java that violates the OCP. This class is responsible for calculating insurance premiums and applying discounts.
public class InsurancePremiumDiscountCalculator {
private String insuranceType;
private CustomerProfile customerProfile;
public int calculatePremiumDiscountPercentage(HealthInsuranceProfile customer) {
if(customer.isLoyalCustomer()) {
return 20;
}
return 0;
}
}
public class HealthInsuranceProfile {
public boolean isLoyalCustomer() {
return true;
}
}
public class InsurancePremiumDiscountCalculator {
private String insuranceType;
private CustomerProfile customerProfile;
public int calculatePremiumDiscountPercentage(CustomerProfile customer) {
if(customer.isLoyalCustomer()) {
return 20;
}
return 0;
}
}
public interface CustomerProfile {
public boolean isLoyalCustomer();
}
public class HealthInsuranceProfile implements CustomerProfile {
@Override
public boolean isLoyalCustomer() {
return true;
}
}
public class VehicleInsuranceProfile implements CustomerProfile {
@Override
public boolean isLoyalCustomer() {
return true;
}
}
3. Liskov Substitution Principle
class Product {
protected double price;
public void setPrice(double price) {
this.price = price;
}
public double getPrice() {
return this.price;
}
}
class DiscountedProduct extends Product {
private double discount;
public void setDiscount(double discount) {
this.discount = discount;
}
public double getPrice() {
return this.price - this.price * this.discount;
}
}
The DiscountedProduct class has an additional discount field, and it overrides getPrice method to return discount price. Now let's say we have a function to set a new price and then get new price.
void printNewPrice(Product product, double newPrice) {
product.setPrice(newPrice);
System.out.println("New price: " + product.getPrice());
}
class DiscountedProduct extends Product {
private double discount;
public void setDiscount(double discount) { this.discount = discount; }
public double getDiscountedPrice() { return this.price - this.price * this.discount; }
}
By following LSP, we ensure that our classes are properly designed for inheritance, leading to a more robust and reliable system.
Comments
Post a Comment