What are the SOLID principles in Java?
The SOLID principles are design principles that, when applied, help create software designs that are easy to maintain and extend with new features. They are a set of object-oriented design guidelines created by Robert C.
SOLID is an acronym for five design principles intended to make software designs more understandable, flexible and maintainable. They are:
- Single Responsibility Principle (SRP) — A class should have one, and only one reason to change. Each module or class should have responsibility over a single part of the functionality provided by the software and that responsibility should be entirely encapsulated by the class.
How does this principle help us to build better software? Let’s see a few of its benefits:
- Testing — A class with one responsibility will have far fewer test cases.
- Lower coupling — Less functionality in a single class will have fewer dependencies.
- Organization — Smaller, well-organized classes are easier to search than monolithic ones.
Examples:
A class should have only a single responsibility (i.e. changes to only one part of the software’s specification should be made in the same class).
public class Car {
// Single responsibility of a car is to drive
public void drive() {
System.out.println("Car is driving");
}
}
public class Charger {
// Charges the battery
public void charge(){
// code logic
System.out.println("The battery is being charged");
}
}
2. Open-Closed Principle (OCP) — Software entities should be open for extension, but closed for modification. The design should allow a user to extend the behavior of an object without modifying it.
Example:
Objects or entities should be open for extension, but closed for modification.
public interface Animal{
void eat();
void move();
}
public class Lion implements Animal{
public void eat(){
//Lion eating
}
public void move(){
//Lion moving
}
//Added method in the implementation
public void roar(){
//Lion roaring
}
}
3. Liskov Substitution Principle (LSP) — Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
Example:
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
public interface ImageStorage {
void storeImage(String image);
}
public class CloudImageStorage implements ImageStorage {
@Override
public void storeImage(String image) {
// connect to cloud storage
// upload image
}
}
public class DatabaseImageStorage implements ImageStorage {
@Override
public void storeImage(String image) {
// connect to database
// save image
}
}
public class ImageUploader {
private ImageStorage imageStorage;
public ImageUploader(ImageStorage imageStorage) {
this.imageStorage = imageStorage;
}
public void uploadImage(String image) {
// compress image
// resize image
// ...
this.imageStorage.storeImage(image);
}
}
4. Interface Segregation Principle (ISP) — Clients should not be forced to depend on methods they do not use. Many client-specific interfaces are better than one general-purpose interface.
Example:
Clients should not be forced to depend on methods they do not use. Many client-specific interfaces are better than one general-purpose interface.
public interface Animal {
public void eat();
public void travel();
}
//interface for animals that can fly
public interface Flyable {
public void fly();
}
//interface for animals that can swim
public interface Swimmable {
public void swim();
}
//Bird class that implements both Flyable and Animal interfaces
public class Bird implements Flyable, Animal{
public void eat(){
//implement eat method
}
public void travel(){
//implement travel method
}
public void fly(){
//implement fly method
}
}
//Fish class that implements Swimmable and Animal interfaces
public class Fish implements Swimmable, Animal{
public void eat(){
//implement eat method
}
public void travel(){
//implement travel method
}
public void swim(){
//implement swim method
}
}
5. Dependency Inversion Principle (DIP) — Depend on abstractions, not on concretions. Dependency injection is a form of this principle.
Example:
Depend on abstractions, not on concretions. Dependency injection is a form of this principle.
public interface IDataAccessor {
public void saveData(String data);
public void loadData();
}
public class FileSystemAccessor implements IDataAccessor {
public void saveData(String data) {
// Implementation of saving data to a file system.
}
public void loadData() {
// Implementation of loading data from a file system.
}
}
public class DatabaseAccessor implements IDataAccessor {
public void saveData(String data) {
// Implementation of saving data to a database.
}
public void loadData() {
// Implementation of loading data from a database.
}
}
public class HighLevelModule {
private IDataAccessor dataAccessor;
public HighLevelModule(IDataAccessor dataAccessor) {
this.dataAccessor = dataAccessor;
}
public void saveData(String data) {
dataAccessor.saveData(data);
}
public void loadData() {
dataAccessor.loadData();
}
}