Adopt Good Software Design & Write a Clean and Maintainable Codebase(SOLID) Part 1/2.

Now is time to avoid this

*The above picture describes the reason i decided to write this article.*

Ever wondered why its this difficult to understand your own code after a while?
Why its difficult to unit test your classes, methods/functions?
Why changes and new features becomes so expensive?

It’s most likely as a result of poorly design software practices, thous maintainability becomes a very high risk, and it becomes more error prone. 
Some developers are a victim of what the above picture describes. So lets look at the principles that can help avoid poor software design practices.

The Single Responsibility Principles (SRP)

This suggest a class should only do one thing. A change in a class should not cause a change in another class. If for any reason we need to make changes to a class, it should be done in one class, else the class could be split into two or more classes.

Single Responsibility Principles

This is a simple and intuitive principle, but in practice, it is sometimes hard to get it right.

A method/function could do a mini part of what a class is doing hence should be unit tested.

Example:

Bad practice:

/**
* Created by Edeh Kingsley Chigozie on 7/27/16.
*/
public interface IFileStore {
void save(int id, String message, String fileName) throws FileNotFoundException, UnsupportedEncodingException;
}
/**
* Created by Edeh Kingsley Chigozie on 7/27/16.
*/

public class FileStore implements IFileStore {

@Override
public void save(int id, String message, String fileName) throws FileNotFoundException, UnsupportedEncodingException {
Logger logger = Logger.getLogger("LoggerFile");
logger.log(Level.INFO, "Storing message at "+ id);
PrintWriter writer = new PrintWriter(fileName, "UTF-8");
writer.println(message);
System.out.println("Stored message at " + id);
}
}

Notice the above class FileStore with the save method have more than one reason to change:
1. Log messages can change,
2. Storage option can change from file to database.

Solutions:

Create and move our log messages to StoreLogger class below, we give the FileStore class one reason to change. Hence we have only one reason for each of the class to change.

/**
* Created by Edeh Kingsley Chigozie on 7/27/16.
*/
public interface IStoreLogger {
void saving(int id);
void saved(int id);
}
/**
* Created by Edeh Kingsley Chigozie on 7/27/16.
*/
public class StoreLogger implements IStoreLogger {

private Logger logger = Logger.getLogger("LoggerFile");


@Override
public void saving(int id) {
logger.log(Level.INFO, "Storing message at " + id);
}

@Override
public void saved(int id) {
logger.log(Level.INFO, "Saved message at " + id);
}

}
/**
* Created by Edeh Kingsley Chigozie on 7/27/16.
*/

public class FileStore implements IFileStore {

private StoreLogger storeLogger;

public FileStore(StoreLogger storeLogger) {
this.storeLogger = storeLogger;
}

@Override
public void save(int id, String message, String fileName) throws FileNotFoundException, UnsupportedEncodingException {
storeLogger.saving(id);
PrintWriter writer = new PrintWriter(fileName, "UTF-8");
writer.println(message);
storeLogger.saved(id);
}

}

(SRP) Conclusion:

The single responsibility principle is a good way to identify packages, classes, and methods during the design phase of an application, it helps to consider all the ways a class can evolve.

A good separation of responsibilities is done only when the full picture of how the application could work is understand. When we start a project, it’s tempting and dangerous to identify as many violation as we want. From the design point of view, excessive Single Responsibility Principle implementation can lead to premature optimization.

When a class do more than it should, don’t hesitate to take the necessary steps to respect the Single Responsibility Principle.

Liskov’s Substitution Principle(LSP)

If a program module use a base class, then the reference to the base class(parent class) can be replaced with a derived subclass(child class) without affecting the functionalities of the program module.

When a class inherit from a base class, the reference to the base class can be replaced with a reference to the subclass without breaking the program module.

Hint: Don’t override a method/function of the base class with the exact signature in the subclass without maintaining the uniqueness and functionalities of the method/function found in the base class. LSP is often violated by an attempt to remove features.

Example:

Consider the below:

/**
* Created by Edeh Kingsley Chigozie on 7/27/16.
*/
public interface IMachine {
void print(List<Item> item);

void scan(List<Item> item);

void photoCopy(List<Item> item);

void staple();
}
/**
* Created by Edeh Kingsley Chigozie on 7/27/16.
*/
public class Machine implements IMachine{

@Override
public void print(List<Item> item) {
// Print the items.
System.out.println("All Items printed");
}

@Override
public void scan(List<Item> item) {
// Scan the items.
System.out.println("All Items Scanned");
}

@Override
public void photoCopy(List<Item> item) {
//photo copy the item
System.out.println("All Items Photo copied");
}

@Override
public void staple() {

}

}
/**
* Created by Edeh Kingsley Chigozie on 7/27/16.
*/

/*This is a violation of Liskov's substitution principle
* Machine cannot be replaced with photocopy machine*/
public class PhotoCopyMachine extends Machine {

@Override
public void photoCopy(List<Item> item) {
//photocopy the item
}

@Override
public void print(List<Item> item) {
// Print the items.
}

//This fails LSP
@Override
public void scan(List<Item> item) throws NotImplementedException{}

//This fails LSP
@Override
public void staple() throws NotImplementedException{}

}

Notice the scan(List<Item> item) and staple() method is not needed in PhotoCopyMachine class implementation hence throw NotImplementedException

An attempt to replace a reference to the Machine object with a reference to the PhotoCopyMachine object is a violation of Liskov’s substitution principle
* Machine cannot be replaced with photocopy machine * this will throw
NotImplementedException when we try to call scan(List<Item> item) or staple(), this leads to a break in the program module or functionalities.

Conclusion:

This principle is an extension of the open close principle. Derived classes must extend the base classes without a change in their behavior.

Look out for the second part of this article and leave a comment bellow if you have any question or observations. Don’t forget to click 💚 button below so others can see this, if you believe this is helpful.