As a Developer you should know SOLID
Solid stands for five principles of object-oriented design: Single Responsibility Principle, Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle. These principles were proposed by Robert C. Martin as a way to guide developers in creating code that is easy to understand, maintain, and extend over time.
The Single Responsibility Principle (SRP) is a principle of object-oriented design that states that a class should have a single responsibility or purpose. This means that a class should only have one reason to change, and it should be focused on a specific task or set of tasks. Adhering to the SRP can help to reduce complexity and improve the modularity of the codebase.
Here is an example of how the Single Responsibility Principle can be applied in Java:
public class Employee {
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public double getSalary() {
return salary;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
In this example, the Employee
class has a single responsibility: storing information about an employee. It has no other responsibilities, such as calculating taxes or sending emails. This adheres to the Single Responsibility Principle, because the class has a single reason to change: if the information that needs to be stored about an employee changes, the Employee
class would need to be modified.
Here is an example of how the Single Responsibility Principle can be violated:
public class Employee {
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public double getSalary() {
return salary;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double calculateTax() {
// Calculate and return the tax for the employee
}
public void sendEmail(String message) {
// Send an email to the employee
}
}
In this example, the Employee
class has multiple responsibilities: it stores information about the employee, it calculates the tax for the employee, and it sends emails to the employee. This violates the Single Responsibility Principle, because the class has multiple reasons to change. To fix this, we could split the class into two separate classes: one for storing employee information, and another for sending emails.
The Open-Closed Principle (OCP) is a principle of object-oriented design that states that a class should be open for extension but closed for modification. This means that developers should aim to design classes in a way that allows them to be easily extended to meet new requirements, without the need to modify the existing code. Adhering to the OCP can help to reduce the risk of breaking existing code when making changes, and it can make the codebase easier to maintain over time.
Here is an example of how the Open-Closed Principle can be applied in Java:
public abstract class Shape {
public abstract double calculateArea();
}
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
public 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 calculateArea() {
return width * height;
}
}
In this example, the Shape
class has a single responsibility: calculating the area of a shape. The Circle
and Rectangle
classes extend the Shape
class and override the calculateArea()
method to provide specific implementations for circles and rectangles. This adheres to the Open-Closed Principle, because we can easily add new shapes by creating new subclasses that extend the Shape
class, without having to modify the Shape
class itself.
Here is an example of how the Open-Closed Principle can be violated:
public class Shape {
private String type;
private double area;
public Shape(String type) {
this.type = type;
}
public double calculateArea() {
if (type.equals("circle")) {
// Calculate and return the area of a circle
} else if (type.equals("rectangle")) {
// Calculate and return the area of a rectangle
} else {
// Return 0 for unknown shape types
return 0;
}
}
}
In this example, the Shape
class has a single responsibility: calculating the area of a shape. However, it violates the Open-Closed Principle because the implementation of the calculateArea()
method is tightly coupled to the specific shape types that are supported. If we want to add support for a new shape, we would have to modify the calculateArea()
method to include the new type. To fix this, we could use inheritance and the Template Method pattern to allow new shapes to be added without modifying the Shape
class.
The Liskov Substitution Principle (LSP) is a principle of object-oriented design that states that objects of a subclass should be able to be used in place of objects of the base class without causing any issues. This means that subclasses should be able to fully and correctly implement the behavior defined in the base class, and they should not introduce any new behavior that would violate the contract established by the base class. Adhering to the LSP can help to improve the flexibility and maintainability of the codebase.
Here is an example of how the Liskov Substitution Principle can be applied in Java:
public abstract class Shape {
public abstract double calculateArea();
}
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
public 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 calculateArea() {
return width * height;
}
}
In this example, the Shape
class defines an abstract method for calculating the area of a shape. The Circle
and Rectangle
classes extend the Shape
class and provide concrete implementations of the calculateArea()
method. This adheres to the Liskov Substitution Principle, because the Circle
and Rectangle
classes correctly implement the behavior defined in the base class and do not introduce any new behavior that would violate the contract established by the base class.
Here is an example of how the Liskov Substitution Principle can be violated:
public abstract class Shape {
public abstract double calculateArea();
}
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
public class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height)
The Interface Segregation Principle (ISP) is a principle of object-oriented design that states that clients should not be forced to depend on interfaces they do not use. This means that developers should aim to create smaller, more focused interfaces that provide only the functionality that is needed, rather than creating large, monolithic interfaces that expose a lot of unnecessary functionality. Adhering to the ISP can help to reduce the complexity of the codebase and improve the modularity of the system.
Here is an example of how the Interface Segregation Principle can be applied in Java:
public interface Printable {
void print();
}
public interface Scannable {
void scan();
}
public interface Faxable {
void fax();
}
public class Printer implements Printable, Scannable {
@Override
public void print() {
// Implementation for printing
}
@Override
public void scan() {
// Implementation for scanning
}
}
public class FaxMachine implements Printable, Scannable, Faxable {
@Override
public void print() {
// Implementation for printing
}
@Override
public void scan() {
// Implementation for scanning
}
@Override
public void fax() {
// Implementation for faxing
}
}
In this example, we have three interfaces: Printable
, Scannable
, and Faxable
. The Printer
class implements the Printable
and Scannable
interfaces, and the FaxMachine
class implements all three interfaces. This adheres to the Interface Segregation Principle, because the Printer
class only needs to depend on the interfaces that it uses, and it is not forced to depend on the Faxable
interface.
Here is an example of how the Interface Segregation Principle can be violated:
public interface OfficeEquipment {
void print();
void scan();
void fax();
}
public class Printer implements OfficeEquipment {
@Override
public void print() {
// Implementation for printing
}
@Override
public void scan() {
// Implementation for scanning
}
@Override
public void fax() {
// Unused implementation for faxing
}
}
public class FaxMachine implements OfficeEquipment {
@Override
public void print() {
// Unused implementation for printing
}
@Override
public void scan() {
// Unused implementation for scanning
}
@Override
public void fax() {
// Implementation for faxing
}
}
In this example, the OfficeEquipment
interface defines methods for printing, scanning, and faxing. However, both the Printer
and FaxMachine
classes are forced to implement all three methods, even though they only use a subset of the functionality. This violates the Interface Segregation Principle, because the Printer
class is forced to depend on the faxing functionality that it does not use, and the FaxMachine
class is forced to depend on the printing and scanning functionality that it does not use. To fix this, we could split the OfficeEquipment
interface into three separate interfaces: Printable
, Scannable
, and `Faxable
The Dependency Inversion Principle (DIP) is a principle of object-oriented design that states that high-level modules should not depend on low-level modules, but rather both should depend on abstractions. This means that developers should aim to create a separation of concerns between different parts of the system, and they should use abstractions (such as interfaces or abstract classes) to define the relationships between those parts. Adhering to the DIP can help to improve the flexibility and maintainability of the codebase.
Here is an example of how the Dependency Inversion Principle can be applied in Java:
public interface Database {
void connect();
void disconnect();
void executeQuery(String query);
}
public class MySqlDatabase implements Database {
@Override
public void connect() {
// Implementation for connecting to a MySQL database
}
@Override
public void disconnect() {
// Implementation for disconnecting from a MySQL database
}
@Override
public void executeQuery(String query) {
// Implementation for executing a query on a MySQL database
}
}
public class UserRepository {
private Database database;
public UserRepository(Database database) {
this.database = database;
}
public void saveUser(User user) {
database.connect();
// Save the user to the database
database.disconnect();
}
}
In this example, the Database
interface defines the basic functionality that is needed to interact with a database. The MySqlDatabase
class implements this interface and provides a concrete implementation for connecting to, disconnecting from, and executing queries on a MySQL database. The UserRepository
class depends on the Database
interface, rather than a specific implementation of a database. This adheres to the Dependency Inversion Principle, because the UserRepository
class is not directly dependent on the MySqlDatabase
class, but rather on the abstract Database
interface. This allows us to easily switch to a different database implementation without having to modify the UserRepository
class.