Design Principles & Design patterns
SOLID Design Principles
- Single Responsibility Pattern – class has a single responsibility like Class Sort won’t have functions to add or delete from the list. ex Microservices
- Open – Closed Principle – Open for extension and closed for modification. It tells you to write your code so that you will be able to add new functionality without changing the existing code.
//Bad – Are of shape is inside the caller class and not of shape. So when a new shape is introduced, it needs to be changed.
class GraphicEditor {
public void areaOfShape(Shape s) {
if (s.m_type==1)
areaOfRectangle(s);
else if (s.m_type==2)
areaOfCircle(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;
}
}
Good – Area method is in interface itself so, Shapes define it on their own
/ Open-Close Principle – Good example
class GraphicEditor {
public void areaOfShape(Shape s) {
s.area();
}
}
class Shape {
abstract void area();
}
class Rectangle extends Shape {
public void draw() {
// draw the rectangle
}
}
- Liskov Substitution Principle
The principle defines that objects of a superclass shall be replaceable/substituted with objects of its subclasses without breaking the application. Achieve this by using interfaces. It uses ISP and Open Closed Principle.
Example
Rectangle and Square example. Though every square is a rectangle but it will not pass the “substitutability” as it behaves differently. So if you increase the width of the rectangle by 1 then it will impact its area by a factor of 1. But if you do that with squares, it will also change its length.
Similarly, Bird interface has fly() and it can be inherited by Duck and Penguin. But Penguins can’t fly. So, it’s better to have another segregation of interfaces as the FlyingBird with fly() method.
- ISP – Interface Segregation Principle
It says the interface should contain the most minimal set of functions needed by an implementing class.
A class that might implement an interface that contains many functions may leave them blank or throw exceptions in them.
Example
Bad – Printer Interface with functions for print, fax and scan
Good – Printer print() , Fax Interface only has fax() and Scanner only scan().
Creational
- Dependency Inversion Principle (DIP) – focuses on the approach where the higher classes are not dependent on the lower classes instead depend upon the abstraction of the lower classes.
The basis for all other patterns. We should depend on interfaces and not directly on classes
Example – Manager could manage Developer, Designer, Testers
Good – Manager manages Employee – Developer, Designer, Testers(implement Employee)
Gang of Four
Creational Patterns
1. Object Pool – for large no of objects – sourcemaking.com
a. class which inherits an interface having three functions
create()
expire()
validate()
Objects r supposed to expire if not used after a certain time.
Maintain two hashmaps locked and unlocked of objects
so a new function checkOut() {
loop over unlocked list {
if ( now – unlocked.get(t) ) > expirationTime ) //condition to test if its invalid {
unlock.remove(t) …expire object
} else {
if( valid(t) ) {
unlocked.remove(t)
locked.put(t)
return t;
} else
{
unlock.remove(t) …expire
}
}
t = create();
locked.put(t, now);
return (t);
}
This enables reuse of created objects.
2. builder pattern – Separate the construction of a complex object from its representation so that the same construction process can create different representations.
An application needs to create the elements of a complex aggregate. The specification for the aggregate exists on secondary storage and one of many representations needs to be built in primary storage.
public class Computer {
//required parameters
private String HDD;
private String RAM;
//optional parameters
private boolean isGraphicsCardEnabled;
private boolean isBluetoothEnabled;
public String getHDD() {
return HDD;
}
public String getRAM() {
return RAM;
}
public boolean isGraphicsCardEnabled() {
return isGraphicsCardEnabled;
}
public boolean isBluetoothEnabled() {
return isBluetoothEnabled;
}
private Computer(ComputerBuilder builder) {
this.HDD=builder.HDD;
this.RAM=builder.RAM;
this.isGraphicsCardEnabled=builder.isGraphicsCardEnabled;
this.isBluetoothEnabled=builder.isBluetoothEnabled;
}
//Builder Class
public static class ComputerBuilder{
// required parameters
private String HDD;
private String RAM;
// optional parameters
private boolean isGraphicsCardEnabled;
private boolean isBluetoothEnabled;
public ComputerBuilder(String hdd, String ram){
this.HDD=hdd;
this.RAM=ram;
}
public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) {
this.isGraphicsCardEnabled = isGraphicsCardEnabled;
return this;
}
public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
this.isBluetoothEnabled = isBluetoothEnabled;
return this;
}
public Computer build(){
return new Computer(this);
}
}
}
Computer comp = new Computer.ComputerBuilder(
“500 GB”, “2 GB”).setBluetoothEnabled(true)
.setGraphicsCardEnabled(true).build();
3. abstract factory – Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
DocumentBuilder in java
Frameworks use abstract Factory
4. Factory Method
- Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
5. Singleton – Only single instance remains in the system
public final class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance(String value) {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Structural Pattern
1. Adapter – Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces. It is used for third party API / it is retrofitted for previous code.
public interface IWetherFinder {
public double getTemperature(String cityName);
}
class WeatherFinder implements IWetherFinder{
@Override
public double getTemperature(String cityName){
return 40;
}
}
interface IWeatherFinderClient
{
public double getTemperature(String zipcode);
}
public class WeatherAdapter implements IWeatherFinderClient {
@Override
public double getTemperature(String zipcode) {
//method to get cityname by zipcode
String cityName = getCityName(zipcode);
//invoke actual service
IWetherFinder wetherFinder = new WeatherFinder();
return wetherFinder.getTemperature(cityName);
}
private String getCityName(String zipCode) {
return “Bangalore”;
}
}
Here AudioPlayer can play mp3 files by default. If it uses MediaAdapter then it can play other formats too.
MediaAdapter and AudioPlayer both implement a MediaPlayer interface that only has a play function.
While AudioPlayer invokes mp3 based on filetype, MediaAdapter invokes different players in case of filetype.
AudioPlayer uses MediaAdapter and finds file formats of some other type.
This way AudioPlayer is decoupled from knowing the interface of VLC and MP4 players.
Adapter knows the interface of the existing system. It provides another interface to access multiple such objects.
2. Composite Design Pattern
Compose objects into tree structures to represent whole-part hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
Recursive composition
“Directories contain entries, each of which could be a directory.”
File and directories are the same as inodes in linux.
So if it is listing contents of a file – the list function will show filename for file and for folder it will loop over all files in the folder and show list for each of them.
In case it is not implemented then, for the list function of folders , we need to find the type of the object it is and then call its list method.
3. Decorator pattern
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
You want to add behavior or state to individual objects at run-time. Inheritance is not feasible because it is static and applies to an entire class.
- Create a “lowest common denominator” that makes classes interchangeable
- Create a second level base class for optional functionality
- “Core” class and “Decorator” class declare an “is a” relationship
- Decorator class “has a” instance of the “lowest common denominator”
- Decorator class delegates to the “has a” object
There is “ is a” and “has a” relationship together
Used for Readers, Writers in Java, UI components
4. Façade Pattern
Facade pattern hides the complexities of the system and provides an interface to the client using which the client can access the system.
5. Proxy Pattern
Use an extra level of indirection to support distributed, controlled, or intelligent access.
Proxy class has all methods and properties of the actual class. Also, it “has a” real class instance. Whenever a call goes to proxy class then it sees if the real object is instantiated or not, if not then it instantiates.
Proxy function does something extra and then calls the real class’s function.
Used for lazy initialization also for fallback methods in Hystrix
6. Bridge Design pattern
The Bridge design pattern allows you to separate the abstraction from the implementation.
There are 2 parts in Bridge design pattern :
Abstraction
Implementation
Example – JDBC driver connections
Similar to Adapter but it is made during the implementation of the code.
Behavioural
1. Template pattern
Define the skeleton of an algorithm in an operation, deferring some steps to client subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
2. Observer Pattern
Observer pattern is used when there is one-to-many relationship between objects such as if one object is modified, its dependent objects are to be notified automatically.
Very useful in UI – for inter widget communication
3. Strategy Pattern
In a Strategy pattern, a class behavior or its algorithm can be changed at run time.