Read This Offline With Code With Me APP

SOLID Design Principle for better software architecture

Posted On: Aug 17, 2015

There are many articles about software design pattern and of course, SOLID design principle is there. In this article, I will try to explain SOLID design principle with java examples.

What is SOLID Design Principle
In Object Oriented Programming, SOLID design principle contains five basic principles for OOP and software design. The term was introduced by Michael Feathers for the "first five principles" named by Robert C. Martin in the early 2000s.


SOLID Stands for

S=Single Responsibility Principle
O=Open/Close Principle
L=Liskov substitution principle
I=Interface segregation principle
D=Dependency inversion principle



Single Responsibility Principle

SRP states that a class should have only single responsibility. i.e it should have only single reason to change.

Lets take an example of User Class

public class User{
	private String mName,mType;
	private int mRewards,mVotes;
	
	public void setName(String mName){
		this.mName=mName;
	}
	
	public void setType(String mType){
		this.mAddress=mAddress;
	}
	
	public String getName(){
		return mName;
	}
	
	public String getType(){
		return mType;
	}
	
	public void setVotes(int mVotes){
		this.mVotes=mVotes;
	}
	
	public int getVotes(){
		return mVotes;
	}
	
	public getRewards(){
			//logic for calculation of rewards based on total votes
			
			return mRewards;
	}
	
	public void printInfo(){
		String output="Name is "+mName;
		System.out.println(output);
	}
}



I above example class User has some methods. setName, getName, setType, getType are the methods closely related to User. Main problem is printInfo() method. User class has multiple responsibilities like geting/setting own info(seems to valid). it should not do printing activity because when you need to output data in various format like HTML, JSON? then you need to add more functions and class seems to have over responsibilities. So it is better to separate output logic to separate class

The code can be re-written as following to depict Single Responsibility
public class UserOutput{
        User user;
        public UserOutput(User user) {
               this.user=user;
        }
	public void print(){
		//code to print string output
		System.out.println("Name is"+user.getName());
	}
	
	public void printJson(){
		//code to json output
		System.out.println("{'name':'"+user.getName()+"'}");
	}
	
	public void printHTML(){
		//code to print html
		System.out.println("<b>Name is "+user.getName()+"</b>");
	}
}



and Our user class should look like

public class User{
	private String mName,mType;
	private int mRewards,mVotes;
	
	public void setName(String mName){
		this.mName=mName;
	}
	
	public void setType(String mType){
		this.mType=mType;
	}
	
	public String getName(){
		return mName;
	}
	
	public String getType(){
		return mType;
	}
	
	public void setVotes(int mVotes){
		this.mVotes=mVotes;
	}
	
	public int getVotes(){
		return mVotes;
	}
	
	public int getRewards(){
			//logic for calculation of rewards based on total votes
			
			return mRewards;
	}
	
}


public class SRPDemo {

	public static void main(String[] args) {
		User user=new User();
		user.setName("Sagar");
		
		UserOutput userOutput=new UserOutput(user);
		userOutput.print();
		userOutput.printJson();
		userOutput.printHTML();
		
		
	}

}



Open/Close Principle

Open/Close principle states that a class should be Open for extension but close for modification.

To understand the principle lets go to our User Class and see common implementation of getRewards() method.Suppose user reward is calculated according to total votes and user type. then the method looks like

public class User{
	...	
	
	public int getRewards() {
		if(mType.equals("GOLD")) {
			mRewards= (int) mVotes*50/100;
		}
		else if(mType.equals("SILVER")){
			mRewards= (int) mVotes*40/100;
		}	
		
		return mRewards;
	}


	...
}


The major problem in User class in getRewards method is that when user type is added i.e BRONZE then we need to modify getRewards method with another if-else block. so this violates Open/Close principle. This we can overcome this problem as following
1) Create new sub class of type
2) Move getRewards method to sub class

public class GoldUser extends User {

	public int getRewards() {
		return (int) getVotes()*50/100;
	}

}


public class SilverUser extends User {

	public int getRewards() {
		return (int) getVotes()*40/100;
	}

}


and Our Refined User class looks like

public class User{
	private String mName;
	private int mRewards,mVotes;
	
	public void setName(String mName){
		this.mName=mName;
	}
	
	
	public String getName(){
		return mName;
	}
	
	
	public void setVotes(int mVotes){
		this.mVotes=mVotes;
	}
	
	public int getVotes(){
		return mVotes;
	}
	
	

}


Liskov Substitution Principle

Liskov principle states that subclass/derived class should be substitutable for their base/parent class

Lets continue with above example, We added getRewards() method to child class like GoldUser, SilverUser to satisfy Open/Close principle. Now suppose you need to output reward points to UserOutput class, you may get into trouble as all print functions inside UserOutput class have User as parameter so user class can not replace its child class because there is no getRewards() method. We also can not replace User parameter oin all print function by its child classes because there will likely to be many child classes. so Liskov principle states all child classes should be replaced by its parent class. to overcome the issue, lets code differently.

public interface IRewards {
	public int getRewards();
}


public class GoldUser extends User implements IRewards {

	@Override
	public int getRewards() {
		return (int) getVotes()*50/100;
	}

}



And Our User class must look like

public class User implements IRewards{
	private String mName;
	private int mRewards,mVotes;
	
	public void setName(String mName){
		this.mName=mName;
	}
	
	
	public String getName(){
		return mName;
	}
	
	
	public void setVotes(int mVotes){
		this.mVotes=mVotes;
	}
	
	public int getVotes(){
		return mVotes;
	}

	@Override
	public int getRewards() {
		return 0;
	}
	
}




public class UserOutput{
        User user;
        public UserOutput(User user){
                this.user=user;
        }
	public void print(){
		//code to print string output
		System.out.println("Name is"+user.getName()+" and Reward is "+user.getRewards());
	}
	
	public void printJson(){
		//code to json output
		System.out.println("{'name':'"+user.getName()+"','rewards':'"+user.getRewards()+"'}");
	}
	
	public void printHTML(){
		//code to print html
		System.out.println("<b>Name is "+user.getName()+" and rewards is "+user.getRewards()+"</b>");
	}
}



Interface Segregation principle

This principle states that, No client class should be forced to implement interface method if not required by it. or they shouldn’t be forced to depend on methods they do not use
Lets go further, Again we required to save those rewards points to database, then we normally create another method called saveRewards to the IRewards interface. like this

public interface IRewards {
	public int getRewards();
	public void saveRewards();
}


Now suppose we have two clients and one asked they need saveRewards in GoldUser but not in SilverUser. then we likely come into trouble. SilverUser class will depend on saveRewards() method though it is not needed by it at all. so to overcome this issue, we need to create separate interface which handles save method so that client class can decide whether to implement or not. Lets go with this.

public interface IRewards {
	public int getRewards();
}


and

public interface IDatabase {
	public void saveRewards();
}


then our client classes will look like

public class User implements IRewards,IDatabase {
	...
	@Override
	public void getRewards(){
	
	}
	
	@Override
	public void saveRewards(){
	
	}
	...
}


public class GoldUser implements IRewards,IDatabase {
	...
	@Override
	public void getRewards(){
	
	}
	
	@Override
	public void saveRewards(){
	
	}
	...
}


This SilverUser can avoid saveRewards() method

public class SilverUser implements IRewards {
	...
	@Override
	public void getRewards(){
	
	}
	
	...
}



Dependency Inversion Principle

DIP states that High level modules should not depend on low level modules but should depend on abstraction.

Suppose you again required to add another Class called Guest with same properties of User and needed to output properties of Both User and Guest by UserOutput class, then? there will be a problem because UserOutput class tightly couples with User Class implementation. So we can not pass Guest class as parameter to print(), printJson() etc. functions. According to DIP, this situation should not occur. so lets start from abstraction,

public interface IUser extends IRewards {
	public void setName(String mName);
	public String getName();
	public void setVotes(int mVotes);
	public int getVotes();
	
}


Then make User, Guest class to implement this

public class User implements IUser{
	private String mName;
	private int mRewards,mVotes;
	
	@Override
	public void setName(String mName){
		this.mName=mName;
	}
	
	
	
	@Override
	public String getName(){
		return mName;
	}
	
	
	@Override
	public void setVotes(int mVotes){
		this.mVotes=mVotes;
	}
	
	@Override
	public int getVotes(){
		return mVotes;
	}

	@Override
	public int getRewards() {
		// TODO Auto-generated method stub
		return 0;
	}
	
}


And Guest Class like this,

public class Guest implements IUser{
	private String mName,mType;
	private int mRewards,mVotes;
	
	@Override
	public void setName(String mName) {
		// TODO Auto-generated method stub
		this.mName=mName;
		
	}
	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return mName;
	}
	@Override
	public void setVotes(int mVotes) {
		// TODO Auto-generated method stub
		this.mVotes=mVotes;
		
	}
	@Override
	public int getVotes() {
		// TODO Auto-generated method stub
		return mVotes;
	}
	
	@Override
	public int getRewards() {
		// TODO Auto-generated method stub
		return 0;
	}
	
}



And our UserOutput class looks like this.

public class UserOutput{
        IUser user;
        public UserOutput(IUser user){
                this.user=user;
        }
	public void print(){
		//code to print string output
		System.out.println("Name is"+user.getName()+" and Reward is "+user.getRewards());
	}
	
	public void printJson(){
		//code to json output
		System.out.println("{'name':'"+user.getName()+"','rewards':'"+user.getRewards()+"'}");
	}
	
	public void printHTML(){
		//code to print html
		System.out.println("<b>Name is "+user.getName()+" and rewards is "+user.getRewards()+"</b>");
	}
}


And final use in main function will be



public class SOLIDDemo {

	public static void main(String[] args) {
		User user=new GoldUser();
		Guest guest=new Guest();
		
		
		user.setName("Sagar");
		user.setVotes(457);
		
		
		guest.setName("Hari");
		guest.setVotes(200);
		
                UserOutput userOutput,guestOutput;
                userOutput=new UserOutput(user);
		userOutput.print();
		userOutput.printJson();
		userOutput.printHTML();
		
                guestOutput=new UserOutput(guest);
		guestOutput.print();
		guestOutput.printJson();
		guestOutput.printHTML();
		
		
	}

}



You can download full implementation of Solid Design

I appreciate your suggestions..


Tags:

  • design-principles
  • java
  • oop
  • solid

Share This On

By Juenish Shrestha On Aug 22, 2015

Thank you for your informative article.

Reply

Comments