SOLID principles are fundamental for any developer aiming to build robust, maintainable systems. These principles not only enhance code quality but also facilitate teamwork and scalability of projects. Let’s delve into each of these principles with practical examples in Java, highlighting both common violations and recommended practices.
1. Single Responsibility Principle (SRP)
Principle: A class should have only one reason to change.
Violating the SRP:
public class User {
private String name;
private String email;
public void saveUser() {
// Logic to save the user in the database
}
public void sendEmail() {
// Logic to send an email to the user
}
}
In this example, the User
class has more than one responsibility: managing user data and sending emails.
Applying the SRP:
public class User {
private String name;
private String email;
}
public class UserRepository {
public void saveUser(User user) {
// Logic to save the user in the database
}
}
public class EmailService {
public void sendEmail(User user) {
// Logic to send an email to the user
}
}
Here, we have separated the responsibilities into different classes, adhering to the SRP.
2. Open/Closed Principle (OCP)
Principle: Classes should be open for extension, but closed for modification.
Violating the OCP:
public class DiscountCalculator {
public double calculateDiscount(String type) {
if (type.equals("NORMAL")) {
return 0.05;
} else if (type.equals("SPECIAL")) {
return 0.1;
}
return 0;
}
}
In this example, any new discount type would require modifying the DiscountCalculator
class.
Applying the OCP:
public interface Discount {
double calculateDiscount();
}
public class NormalDiscount implements Discount {
public double calculateDiscount() {
return 0.05;
}
}
public class SpecialDiscount implements Discount {
public double calculateDiscount() {
return 0.1;
}
}
public class DiscountCalculator {
public double calculateDiscount(Discount discount) {
return discount.calculateDiscount();
}
}
In this case, the DiscountCalculator
is closed for modification but open for extension through the implementation of new discount types.
3. Liskov Substitution Principle (LSP)
Principle: Subclasses should be replaceable by their base classes without affecting the correctness of the program.
Violating the LSP:
public class Bird {
public void fly() {
// flying implementation
}
}
public class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Penguins can't fly");
}
}
Here, the Penguin
class cannot replace Bird
without affecting the program’s correctness.
Applying the LSP:
public abstract class Bird {
}
public class FlyingBird extends Bird {
public void fly() {
// flying implementation
}
}
public class Penguin extends Bird {
}
Now, FlyingBird
and Penguin are separated, respecting the ability of Bird
to be replaced by its subclasses.
4. Interface Segregation Principle (ISP)
Principle: Clients should not be forced to depend on interfaces they do not use.
Violating the ISP:
public interface Animal {
void walk();
void fly();
void swim();
}
public class Dog implements Animal {
public void walk() {
// walking implementation
}
public void fly() {
throw new UnsupportedOperationException();
}
public void swim() {
// dogs can swim
}
}
Here, Dog
is forced to implement fly
, which is not relevant.
Applying the ISP:
public interface Walkable {
void walk();
}
public interface Flyable {
void fly();
}
public interface Swimmable {
void swim();
}
public class Dog implements Walkable, Swimmable {
public void walk() {
// walking implementation
}
public void swim() {
// swimming implementation
}
}
Now, Dog
implements only the interfaces relevant to its actions.
5. Dependency Inversion Principle (DIP)
Principle: High-level modules should not depend on low-level modules. Both should depend on abstractions.
Violating the DIP:
public class LightBulb {
public void turnOn() {
// turn on the light bulb
}
}
public class ElectricPowerSwitch {
private LightBulb lightBulb = new LightBulb();
public void press() {
lightBulb.turnOn();
}
}
ElectricPowerSwitch
directly depends on LightBulb
, a low-level module.
Applying the DIP:
public interface Switchable {
void turnOn();
}
public class LightBulb implements Switchable {
public void turnOn() {
// turn on the light bulb
}
}
public class ElectricPowerSwitch {
private Switchable client;
public ElectricPowerSwitch(Switchable client) {
this.client = client;
}
public void press() {
client.turnOn();
}
}
Now, ElectricPowerSwitch
depends on an abstraction (Switchable
), which makes the design more flexible and sustainable.
Conclusion
Applying the SOLID principles in Java is not just good theoretical practice but a proven strategy for keeping software flexible, sustainable, and comprehensible. I hope these examples help illustrate how you can implement these principles in your own software projects.
Top comments (0)