The world is a stage where all of us are artists and constant learning is the foundation of success. So, to improve ourselves here we are going to know more about coding style in which we will look deep into SOLID principles in Java. We will see why it is important and how to achieve that.
As in the previous blog, we saw several code formatting conventions, those were not the only ones we should know and care about. A readable and maintainable code can benefit from a large number of additional best practices that have been accumulated over time. To get the context Clean code and the previous blog please go through this link: Functional Java CodeStyle [Part: 1].
SOLID Principles
SOLID is a mnemonic acronym that draws from the five principles it sets forth for writing understandable and maintainable software.
The following five concepts make up our SOLID principles:
- Single Responsibility
- Open/Closed
- Liskov Substitution
- Interface Segregation
- Dependency Inversion
These principles establish practices that lend to developing software with considerations for maintaining and extending as the project grows. Adopting these practices can also contribute to avoiding code smells, refactoring code, and Agile or Adaptive software development.
Let’s see them one by one.
1. Single-Responsibility Principle
A class should have one and only one reason to change, meaning that a class should have only one job
If a Class has many responsibilities, it increases the possibility of bugs because making changes to one of its responsibilities, could affect the other ones without you knowing
For example, let’s look at a class to represent a simple Employee:
public class Employee {
private String name;
private String address;
private String phone;
//constructor, getters and setters
}
Let’s now add a couple of methods to query the phone:
public class Employee {
private String name;
private String address;
private String phone;
//constructor, getters and setters
// methods that directly relate to the Employee properties
public String replacePhone(String newPhone){
return text.replaceAll(newPhone, phone);
}
public boolean isPhonePresent(String phone){
return phone.contains(phone);
}
}
We are storing the information, How about if we can’t output the text to our console and read it?
public class Employee {
private String name;
private String address;
private String phone;
//constructor, getters and setters
// methods that directly relate to the Employee properties
public String replacePhone(String newPhone){
return text.replaceAll(newPhone, phone);
}
public boolean isPhonePresent(String phone){
return phone.contains(phone);
}
void printTextToConsole(){
// our code for formatting and printing the text
}
}
However, this code violates the single responsibility principle we outlined earlier.
To fix it, we should implement a separate class that deals only with printing our texts:
public class EmployeePrinter {
// methods for outputting text
void printtEmployeeToConsole(String text){
//our code for formatting and printing the text
}
void printEmployeeToAnotherMedium(String text){
// code for writing to any other location..
}
}
Not only we have developed a class that relieves the Employee of its printing duties, but we can also leverage our EmployeePrinter class to send our text to other media.
Whether it’s email, logging, or anything else, we have a separate class dedicated to this one concern.
2. Open-Closed Principle
Objects or entities should be open for extension but closed for modification.
The idea of open-closed principle is that existing, well-tested classes will need to be modified when something needs to be added. Yet, changing classes can lead to problems or bugs. Instead of changing the class, you simply want to extend it.
For eg,:- in our Employee class after a few months, we decide to add some optional features, like DOB and Designation.
At this point, it might be tempting to just open up the Employee class and add those features — but who knows what errors that might throw up in our application.
public class OptionalDetails extends Employee {
private String Designation;
private Date DOB;
//constructor, getters + setters
}
By extending the Employee class, we can be sure that our existing application won’t be affected.
3. Liskov Substitution Principle
if class A is a subtype of class B, we should be able to replace B with A without disrupting the behavior of our program.
This means that every subclass or derived class should be substitutable for their base or parent class.
Let’s look into an example
public interface Car {
void fillGas();
void accelerate();
}
public class MotorCar implements Car {
private Engine engine;
//Constructors, getters + setters
public void fillGas() {
//Fill the gas!
engine.filled();
}
public void accelerate() {
//move forward!
engine.powerOn(1000);
}
}
Above, we define a simple Car interface with a couple of methods that all cars should be able to fulfill: filling gas and accelerating forward.
As our code describes, we have an engine that we can turn on, and we can increase the power.
But also we have now Electric Cars. And they don’t have gas tanks:
public class ElectricCar implements Car {
public void fillGas() {
throw new AssertionError("I don't need Gas!");
}
public void accelerate() {
//move forward!
}
}
By throwing a car without a gas tank into the mix, we are inherently changing the behavior of our program. This is a violation of Liskov substitution.
We can change our Car interface to fix this one.
4. Interface Segregation Principle
Clients should not be forced to depend on methods that they do not use.
When a Class is required to perform actions that are not useful, it is wasteful and may produce unexpected bugs if the Class does not have the ability to perform those actions.
A Class should perform only actions that are needed to fulfill its role. Any other action should be removed completely or moved somewhere else if it might be used by another Class in the future.
Let’s look at an example:
public interface Car {
void fillGas();
void accelerate();
void chargeBattery();
}
Previously we saw that gas tanks are not present in Electric cars. Unfortunately, our interface is rather large, and we have no choice but to implement the code to fillGas and chargeBattery in the same class.
Let’s fix this by splitting this interface into smaller ones.
public interface GasFiller {
void fillGas();
}
public interface CarAcceleration {
void accelerate();
}
public interface BatteryCharger {
void chargeBattery();
}
Now, thanks to interface segregation, we’re free to implement only the methods that matter to us:
public class MotorCar implements GasFiller, CarAcceleration {
public void fillGas() {
//gas filled
}
public void accelerate() {
//move forward
}
}
And for Electric cars:
public class ElectricCar implements CarAcceleration, BatteryCharger {
public void chargeBattery() {
//battery full
}
public void accelerate() {
//move forward!
}
}
5. Dependency Inversion Principle
Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.
Firstly, let’s define the terms used here more simply
High-level Module(or Class): Class that executes an action with a tool.
Low-level Module (or Class): The tool that is needed to execute the action
Abstraction: Represents an interface that connects the two Classes.
Let’s see a different example for it:
public class Computer {}
Let’s add one of the monitors and keyboards to our constructor.
public class Computer {
private final StandardKeyboard keyboard;
private final Monitor monitor;
public Computer() {
monitor = new Monitor();
keyboard = new StandardKeyboard();
}
}
By declaring the StandardKeyboard and Monitor with the new keyword, we’ve tightly coupled these three classes together.
Not only does this make our Computer hard to test, but we’ve also lost the ability to switch out our StandardKeyboard class with a different one should the need arise. And we’re stuck with our Monitor class too.
Let’s decouple our machine:
public interface Keyboard { }
public class Computer{
private final Keyboard keyboard;
private final Monitor monitor;
public Windows98Machine(Keyboard keyboard, Monitor monitor) {
this.keyboard = keyboard;
this.monitor = monitor;
}
}
Here, we’re using the dependency injection pattern to facilitate adding the Keyboard dependency into our class.
also, modify our StandardKeyboard class to implement the Keyboard interface so that it’s suitable for injecting
public class StandardKeyboard implements Keyboard { }
Now our classes are decoupled and communicated through the Keyboard abstraction. If we want, we can easily switch out the type of keyboard in our machine with a different implementation of the interface. We can follow the same principle for the Monitor class.
That’s pretty much it from the article. We will come with 3rd Part in which we will include Tools and much more things.
If you have any feedback or queries, please do let me know in the comments. Also, if you liked the article, please give me a thumbs up and I will keep writing blogs like this for you in the future as well. Keep reading and Keep coding.

1 thought on “Functional Java CodeStyle [Part: 2]7 min read”
Comments are closed.