Object Oriented principles (OOP) differ from Object Oriented concepts (OOC). Yet OOP
derive from OOC
4 object oriented concepts are:
Abstraction
Encapsulation
Inheritance
Polymorphism
Whereas, 5 OOPs are defined as SOLID.
S - Single Responsibility Principle
O - Open Closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle
Single Responsibility Principle -
A Class should be designed in a way that only performs well-defined one task only. It should not couple with multiple tasks. In nutshell, Class should be high cohesion.
If a Class performed multiple tasks and there was a requirement change, the class would need to be changed multiple places and times due to overlapping of responsibilities. This leads to maintenance difficulty.The changes will occur in minimal places without affecting rest of the codes
However, this does not mean that the class must only contain one attribute and method. A class can contain multiple attributes and methods but collaboratively it satisfies well-defined one requirement only. On the other hand, the more functionalities are added to a class the more difficult in testing as well.
Following class has more than one responsibility to change the code.
1. If the content changes from String to HTML
2. If the message format changes
Good design
We create a separate interface called Content and inject the appropriate type at run time.
Open Close Principle
This simply says class is closed to modification but open for extension.
Take the above example. Lets say we want to process the content of the message and there are multiple message types such as SMS, Email, Voice mail. Due to different types of Messages, the processing functionality should be different from each other. Also, in the future new message types and processing requirements could be added.
If we implemented a processing controller class to handle above requirement, it would be difficult to change when new message type is added.
In above code, the controller class has the knowledge of how to process a message. And if new message type is added, the above code needs to be changed incorporating a new process method and modifying the condition block.
The better approach is the process method itself contains the process and injects to the controller.
Here Interface Content defines method signature of process and its concrete classes hold the implementations. Whenever new message type is added the developer will implement the process in new message type class and no need to change the ContentProcessing.
Liskov Substitution Principle
Derived types must be completely substitutable for their base types. Classic example is Rectangle, Square relationship.
Square is a type of Rectangle. Which means Rectangle is the super class and its sub class Square can substitute it.
Following code shows a bad design and an incorrect output is generated.
class Rectangle {
int width;
int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
public void setWidth(int width) {
this.width = width;
this.height = width;
}
public void setHeight(int height) {
this.width = height;
this.height = height;
}
}
public class LspTest {
private static Rectangle getNewRectangle() {
return new Square();
}
public static void main(String args[]) {
Rectangle r = LspTest.getNewRectangle();
r.setWidth(5);
r.setHeight(10);
// expected output is = 50 , actual output = 100
}
}
Interface Segregation Principle
Clients should not be forced to depend on interfaces that they don't use. When we extend a class or implement an interface owing to inheritance nature of Object Oriented Languages leads to high coupling design. Therefore, create separate interfaces representing different behaviours.
In above situation both Dog and Cat confirm and override super class Mammal behaviour. However some mammals do not breed rather lay eggs like Platypus. In such situation the above hierarchy will break.
Here, we do not enforce to implement unnecessary behaviour rather create separate interfaces according to the concrete class requirements.
Dependency Inversion Principle
High-level modules should not depend on low-level modules. Both should depend on abstractions.Abstractions should not depend on details. Details should depend on abstractions.
It is a type of Inversion of control(IOC). here, we prefer aggregation to inheritance due to low coupling between classes. We use abstraction and polymorphism when referencing.
The problem with the above code when a dog makes a different sound, how to incorporate it with existing design. The solution is to make Sound an interface and create separate concrete classes for different noises.
public interface Sound {
void makeSound();
}
class Bark implements Sound {
@Override
public void makeSound() {
// barking
}
}
class Howl implements Sound {
@Override
public void makeSound() {
// howling
}
}
class Dog {
private Sound sound;
public void bark(Sound sound) {
sound.makeSound();
}
public void setSound(Sound sound) {
this.sound = sound;
}
}
However, this does not mean that the class must only contain one attribute and method. A class can contain multiple attributes and methods but collaboratively it satisfies well-defined one requirement only. On the other hand, the more functionalities are added to a class the more difficult in testing as well.
Following class has more than one responsibility to change the code.
1. If the content changes from String to HTML
2. If the message format changes
Good design
We create a separate interface called Content and inject the appropriate type at run time.
Open Close Principle
This simply says class is closed to modification but open for extension.
Take the above example. Lets say we want to process the content of the message and there are multiple message types such as SMS, Email, Voice mail. Due to different types of Messages, the processing functionality should be different from each other. Also, in the future new message types and processing requirements could be added.
If we implemented a processing controller class to handle above requirement, it would be difficult to change when new message type is added.
In above code, the controller class has the knowledge of how to process a message. And if new message type is added, the above code needs to be changed incorporating a new process method and modifying the condition block.
The better approach is the process method itself contains the process and injects to the controller.
Here Interface Content defines method signature of process and its concrete classes hold the implementations. Whenever new message type is added the developer will implement the process in new message type class and no need to change the ContentProcessing.
Liskov Substitution Principle
Derived types must be completely substitutable for their base types. Classic example is Rectangle, Square relationship.
Square is a type of Rectangle. Which means Rectangle is the super class and its sub class Square can substitute it.
Following code shows a bad design and an incorrect output is generated.
class Rectangle {
int width;
int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
public void setWidth(int width) {
this.width = width;
this.height = width;
}
public void setHeight(int height) {
this.width = height;
this.height = height;
}
}
public class LspTest {
private static Rectangle getNewRectangle() {
return new Square();
}
public static void main(String args[]) {
Rectangle r = LspTest.getNewRectangle();
r.setWidth(5);
r.setHeight(10);
// expected output is = 50 , actual output = 100
}
}
Interface Segregation Principle
Clients should not be forced to depend on interfaces that they don't use. When we extend a class or implement an interface owing to inheritance nature of Object Oriented Languages leads to high coupling design. Therefore, create separate interfaces representing different behaviours.
In above situation both Dog and Cat confirm and override super class Mammal behaviour. However some mammals do not breed rather lay eggs like Platypus. In such situation the above hierarchy will break.
Here, we do not enforce to implement unnecessary behaviour rather create separate interfaces according to the concrete class requirements.
Dependency Inversion Principle
High-level modules should not depend on low-level modules. Both should depend on abstractions.Abstractions should not depend on details. Details should depend on abstractions.
It is a type of Inversion of control(IOC). here, we prefer aggregation to inheritance due to low coupling between classes. We use abstraction and polymorphism when referencing.
The problem with the above code when a dog makes a different sound, how to incorporate it with existing design. The solution is to make Sound an interface and create separate concrete classes for different noises.
public interface Sound {
void makeSound();
}
class Bark implements Sound {
@Override
public void makeSound() {
// barking
}
}
class Howl implements Sound {
@Override
public void makeSound() {
// howling
}
}
class Dog {
private Sound sound;
public void bark(Sound sound) {
sound.makeSound();
}
public void setSound(Sound sound) {
this.sound = sound;
}
}
No comments:
Post a Comment