Design patterns are general, reusable solutions to a commonly occurring problem within a given context in software design. They are highly used in normal day to day software developer work and it's expected for developers applying for interviews to know them.
I have written this post to summarize commonly used patterns so that they can be easily understood for interview purposes.
Here goes the list :
Factory Pattern
In Factory pattern, we create object without exposing the creation logic to the client and refer to newly created object using a common interface.
interface Shape{
void draw();
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class ShapeFactory {
Shape getShape(String shapeType) {
if(shapeType == null){
return null;
} if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
}
return null;
}
}
// Usage
Shape shape1 = shapeFactory.getShape("CIRCLE");
shape1.draw();
Singleton Pattern
This pattern involves a single class which is responsible to create an object while making sure that only single object gets created. There are many ways to create a singleton class. The best way is to to do lazy initialisation with synchronized method. In the below example which ever thread reaches the sync block first, Singleton.value
will be equivalent to that.
class Singleton {
private static volatile Singleton obj;
private int value;
public int getValue() {
return value;
}
private Singleton() {
}
private Singleton(int value) {
this.value = value;
}
public static Singleton getInstance(int value) {
if (obj == null) {
synchronized (Singleton.class) {
if (obj == null) {
obj = new Singleton(value);
}
}
}
return obj;
}
}
public class Learning {
void solve() {
int x =5;
while (x-->0) {
getThread(100, x).start();
}
}
Thread getThread(int sleepTime, int value) {
return new Thread(() -> {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
Singleton learnSingleton = Singleton.getInstance(value);
System.out.println(learnSingleton.getValue());
});
}
public static void main(String[] args) {
Learning learning = new Learning();
try {
learning.solve();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Builder pattern
Builder pattern builds a complex object using simple objects and using a step by step approach. This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object.
A Builder class builds the final object step by step. This builder is independent of other objects. Its solves the problem of creating multiple constructors when different number of fields are present during initialization
public class User
{
private final String firstName; // required
private final String lastName; // required
private final int age; // optional
private final String phone; // optional
private final String address; // optional
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
// Getter functions can be added to access User object
public static class UserBuilder {
private final String firstName;
private final String lastName;
private int age;
private String phone;
private String address;
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
public User build() {
User user = new User(this);
return user;
}
}
}
//Usage
User user1 = new User.UserBuilder("Lokesh", "Gupta")
.age(30)
.phone("1234567")
.address("Fake address 1234")
.build();
Adapter pattern
Adapter pattern works as a bridge between two incompatible interfaces. This type of design pattern comes under structural pattern as this pattern combines the capability of two independent interfaces.
This pattern involves a single class which is responsible to join functionalities of independent or incompatible interfaces. A real life example could be a case of card reader which acts as an adapter between memory card and a laptop. You plugin the memory card into card reader and card reader into the laptop so that memory card can be read via laptop.
public interface MediaPlayer {
public void play(String audioType, String fileName);
}
public interface AdvancedMediaPlayer {
public void playVlc(String fileName);
public void playMp4(String fileName);
}
public class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: " + fileName);
}
@Override
public void playMp4(String fileName) {
System.out.println("Cannot play mp4");
}
}
public class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Cannot play vlc");
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: " + fileName);
}
}
public class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer.playVlc(fileName);
} else if(audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer.playMp4(fileName);
}
}
}
//Usage
MediaAdapter mediaAdapter = new MediaAdapter("vlc");
mediaAdapter.play("vlc", "some_file_name.vlc");
Composite pattern
Composite pattern is used where we need to treat a group of objects in similar way as a single object. Composite pattern composes objects in term of a tree structure to represent part as well as whole hierarchy.
public class Employee {
private String name;
private String dept;
private int salary;
private List<Employee> subordinates;
public Employee(String name,String dept, int sal) {
this.name = name;
this.dept = dept;
this.salary = sal;
subordinates = new ArrayList<Employee>();
}
public void add(Employee e) {
subordinates.add(e);
}
public void remove(Employee e) {
subordinates.remove(e);
}
public List<Employee> getSubordinates(){
return subordinates;
}
}
//Usage
Employee CEO = new Employee("John","CEO", 30000);
Employee headSales = new Employee("Robert","Head Sales", 20000);
Employee headMarketing = new Employee("Michel","Head Marketing", 20000);
CEO.add(headSales);
CEO.add(headMarketing);
Decorator pattern
Decorator pattern allows a user to add new functionality to an existing object without altering its structure. This pattern creates a decorator class which wraps the original class and provides additional functionality keeping class methods signature intact.
interface Shape{
void draw();
}
class Rectangle implements Shape{
@Override
void draw(){
System.out.println("Draw rectangle");
}
}
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
}
class RedRectangleDecorator extends ShapeDecorator{
public RedRectangleDecorator(Shape decoratedShape){
super(decoratedShape);
}
@Override
public void draw(){
decoratedShape.draw();
colorRead();
}
public void colorRed(){
System.out.println("Red coloured");
}
}
//Usage
RedRectangleDecorator red = new RedRectangleDecorator(new Rectangle());
red.draw();
Strategy Pattern
In Strategy pattern, a class behavior or its algorithm can be changed at run time. This type of design pattern comes under behavior pattern.
interface Strategy {
int doOperation(int a, int b);
}
class AddStrategy implements Strategy {
public int doOperation(int a, int b) {
return a + b;
}
}
class Subtract implements Strategy {
public int doOperation(int a, int b) {
return a + b;
}
}
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
public class Learning {
public static void main(String[] args) {
Context context = new Context(new AddStrategy());
System.out.println(context.executeStrategy(1, 2));
}
}
There are more patterns that are also used, you can look find them in geeksforgeeks
Happy coding !