SOLID Design Principles

Author: Tran Tuan Quy

Date: Feb 18, 2015

CONTENT

Single Responsibility Principle

Open Closed Principle

Liskov Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

Single Responsibility Principle

A class should have one, and only one reason to change.

Example 01

// single responsibility principle - bad example

interface IEmail {
	public void setSender(String sender);
	public void setReceiver(String receiver);
	public void setContent(String content);
}

class Email implements IEmail {
	public void setSender(String sender) {// set sender; }
	public void setReceiver(String receiver) {// set receiver; }
	public void setContent(String content) {// set content; }
}

Example 01

// single responsibility principle - good example
interface IEmail {
	public void setSender(String sender);
	public void setReceiver(String receiver);
	public void setContent(IContent content);
}

interface Content {
	public String getAsString(); // used for serialization
}

class Email implements IEmail {
	public void setSender(String sender) {// set sender; }
	public void setReceiver(String receiver) {// set receiver; }
	public void setContent(IContent content) {// set content; }
}

Example 02

Tips:

  • It is simplest of principle to be understood, but one of the hardest to get right.

Open Close Principle

Software entities like classes, modules and functions should be open for extension but closed for modifications.

Example

// Open-Close Principle - Bad example
 class GraphicEditor {
 
 	public void drawShape(Shape s) {
 		if (s.m_type==1)
 			drawRectangle(s);
 		else if (s.m_type==2)
 			drawCircle(s);
 	}
 	public void drawCircle(Circle r) {....}
 	public void drawRectangle(Rectangle r) {....}
 }
 
 class Shape {
 	int m_type;
 }
 
 class Rectangle extends Shape {
 	Rectangle() {
 		super.m_type=1;
 	}
 }
 
 class Circle extends Shape {
 	Circle() {
 		super.m_type=2;
 	}
 }

Example

// Open-Close Principle - Good example
 class GraphicEditor {
 	public void drawShape(Shape s) {
 		s.draw();
 	}
 }
 
 class Shape {
 	abstract void draw();
 }
 
 class Rectangle extends Shape  {
 	public void draw() {
 		// draw the rectangle
 	}
 }

Tips:

  • Abstraction is the Key.

  • The following design patterns help achieve this principle:

  • Decorator

  • Factory

  • Observer

Liskov's Substitution Principle

Derived classes must be substitutable for their base classes.

Example

// Violation of Likov's Substitution Principle
class Rectangle
{
	protected int m_width;
	protected int m_height;

	public void setWidth(int width){
		m_width = width;
	}

	public void setHeight(int height){
		m_height = height;
	}

	public int getWidth(){
		return m_width;
	}

	public int getHeight(){
		return m_height;
	}

	public int getArea(){
		return m_width * m_height;
	}	
}

class Square extends Rectangle 
{
	public void setWidth(int width){
		m_width = width;
		m_height = width;
	}

	public void setHeight(int height){
		m_width = height;
		m_height = height;
	}

}

class LspTest
{
	private static Rectangle getNewRectangle()
	{
		// it can be an object returned by some factory ... 
		return new Square();
	}

	public static void main (String args[])
	{
		Rectangle r = LspTest.getNewRectangle();
        
		r.setWidth(5);
		r.setHeight(10);
		// user knows that r it's a rectangle. 
		// It assumes that he's able to set the width and height as for the base class

		System.out.println(r.getArea());
		// now he's surprised to see that the area is 100 instead of 50.
	}
}

Tips:

  • This principle is just an extension of the Open Close Principle.

  • Make sure that new derived classes are extending the base classes without changing their behavior.

Interface Segregation Principle

Clients should not be forced to implement interfaces they don't use

Example

// interface segregation principle - bad example
interface IWorker {
	public void work();
	public void eat();
}

class Worker implements IWorker{
	public void work() {
		// ....working
	}
	public void eat() {
		// ...... eating in launch break
	}
}

class SuperWorker implements IWorker{
	public void work() {
		//.... working much more
	}

	public void eat() {
		//.... eating in launch break
	}
}

class Manager {
	IWorker worker;

	public void setWorker(IWorker w) {
		worker=w;
	}

	public void manage() {
		worker.work();
	}
}
// interface segregation principle - good example
interface IWorker extends Feedable, Workable {
}

interface IWorkable {
	public void work();
}

interface IFeedable{
	public void eat();
}

class Worker implements IWorkable, IFeedable{
	public void work() {
		// ....working
	}

	public void eat() {
		//.... eating in launch break
	}
}

class Robot implements IWorkable{
	public void work() {
		// ....working
	}
}

class SuperWorker implements IWorkable, IFeedable{
	public void work() {
		//.... working much more
	}

	public void eat() {
		//.... eating in launch break
	}
}

class Manager {
	Workable worker;

	public void setWorker(Workable w) {
		worker=w;
	}

	public void manage() {
		worker.work();
	}
}

Tips:

  • If the design is already done, fat interfaces can be segregated using the Adapter pattern.

Dependency Inversion Principle

Depend on abstractions, not on concretions.

Example

// Dependency Inversion Principle - Bad example

class Worker {
	public void work() {
		// ....working
	}
}

class Manager {

	Worker worker;

	public void setWorker(Worker w) {
		worker = w;
	}

	public void manage() {
		worker.work();
	}
}

class SuperWorker {
	public void work() {
		//.... working much more
	}
}

Example

// Dependency Inversion Principle - Good example
interface IWorker {
	public void work();
}

class Worker implements IWorker{
	public void work() {
		// ....working
	}
}

class SuperWorker  implements IWorker{
	public void work() {
		//.... working much more
	}
}

class Manager {
	IWorker worker;

	public void setWorker(IWorker w) {
		worker = w;
	}

	public void manage() {
		worker.work();
	}
}

Tips:

  • Require more effort & time to implement

  • Make design flexible to extend

  • Not be able to use new operator to instantiate, but can use Factory, Prototype pattern to achieve the same goal

Conclusion

  • Most of Principles when applied require more effort & time to implement.

  • Need to be clear needs of project. And adopt in appropriate situation.

  • Use some existing design patterns to implement these principles such as: Factory, Adapter, Decorator, Observer,...

Q&A

SOLID Design Principles

By Quy Tran

SOLID Design Principles

  • 1,253