The Best Design Patterns

Long Do
4 min readOct 14, 2020

Design patterns represent the best practices used by experienced object-oriented software developers. Design patterns are solutions to general problems that software developers faced during software development.

This post will show you the best practice Design Patterns in Software Developing.

1. Creation Patterns

1.1 Singleton Pattern

Singleton pattern is one of the simplest design patterns. This type of design pattern comes under a creational pattern as this pattern provides one of the best ways to create an object.

// Example 1
object TimeConverter {
fun dateToString(date: Date, format: String): String {}
fun StringToDate(date: Date, format: String): Date {}
}
// Example 2
class StyleManager private constructure(context: Context) {
companion object {
const val INSTANCE = WeakPreference<StyleManager>(null)

fun getInstance(context: Context): StyleManager {
if (INSTANCE.get() == null) {
INSTANCE.set(StyleManager(context))
}
return INSTANCE.get()
}

1.2 Factory

The Factory pattern is one of the most used design patterns. This type of design pattern comes under a creational pattern as this pattern provides one of the best ways to create an object.

public 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 Circle implements Shape {

@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
public class ShapeFactory {

//use getShape method to get object of type shape
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();

} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();

} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}

return null;
}
}

1.3 Builder

Builder pattern builds a complex object using simple objects and using a step by step approach. This type of design pattern comes under a 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

class Message private constructor(
val id: String,
val message: String,
val createdAt: Long,
val sender: Sender
) {
internal Builder {
lateinit var id: String
lateinit var message: String
lateinit var createdAt: Long
lateinit var sender: Sender
init {
id = randomUUID()
createdAt = System.currentMillisecond()
sender = currentUser()
}

fun setMessage(message: String): Builder {
this.message = message
return this
}
fun build(): Message {
return Message(id, message, createdAt, sender)
}
}
}

1.4 Dependency Injection

2. Structure Patterns

2.1 Adapter

The Adapter pattern works as a bridge between two incompatible interfaces. This type of design pattern comes under a structural pattern as this pattern combines the capability of two independent interfaces.

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) {
//do nothing
}
}
public class Mp4Player implements AdvancedMediaPlayer{

@Override
public void playVlc(String fileName) {
//do nothing
}

@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: "+ fileName);
}
}

The adapter

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);
}
}
}

3. Behavior

3.1 Command

The Command pattern is a data-driven design pattern and falls under the behavioral pattern category. A request is wrapped under an object as command and passed to the invoker object. The invoker object looks for the appropriate object which can handle this command and passes the command to the corresponding object which executes the command.

public interface Order {
void execute();
}
public class Stock {

private String name = "ABC";
private int quantity = 10;

public void buy(){
System.out.println("Stock [ Name: "+name+",
Quantity: " + quantity +" ] bought");
}
public void sell(){
System.out.println("Stock [ Name: "+name+",
Quantity: " + quantity +" ] sold");
}
}

Command class

public class BuyStock implements Order {
private Stock abcStock;

public BuyStock(Stock abcStock){
this.abcStock = abcStock;
}

public void execute() {
abcStock.buy();
}
}

3.2 State Pattern

In State pattern, a class behavior changes based on its state. This type of design pattern comes under behavior pattern.

public interface State {
public void doAction(Context context);
}
public class StartState implements State {

public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}

public String toString(){
return "Start State";
}
}
public class StopState implements State {

public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}

public String toString(){
return "Stop State";
}
}

3.3 Null Object Pattern

In Null Object pattern, a null object replaces check of NULL object instance. Instead of putting if check for a null value, Null Object reflects a do nothing relationship. Such Null object can also be used to provide default behaviour in case data is not available.

public abstract class AbstractCustomer {
protected String name;
public abstract boolean isNil();
public abstract String getName();
}
public class RealCustomer extends AbstractCustomer {

public RealCustomer(String name) {
this.name = name;
}

@Override
public String getName() {
return name;
}

@Override
public boolean isNil() {
return false;
}
}

And Null Object

public class NullCustomer extends AbstractCustomer {

@Override
public String getName() {
return "Not Available in Customer Database";
}

@Override
public boolean isNil() {
return true;
}
}

Hope you have a better Design Pattern knowledge.

--

--