Design patterns

Design patterns are programming language independent strategies for solving the common object-oriented design problems. That means, a design pattern represents an idea, not a particular implementation. By using the design patterns you can make your code more flexible, reusable and maintainable. It is the most important part because java internally follows design patterns.

Advantage of design pattern:

  • They are reusable in multiple projects.
  • They provide the solutions that help to define the system architecture.
  • They capture the software engineering experiences.
  • They provide transparency to the design of an application.
  • They are well-proved and testified solutions since they have been built upon the knowledge and experience of expert software developers.

Design patterns don't guarantee an absolute solution to a problem. They provide clarity to the system architecture and the possibility of building a better system.

When should we use the design patterns

We must use the design patterns during the analysis and design requirement phase of SDLC(Software Development Life Cycle).

Design patterns ease the analysis and requirement phase of SDLC by providing information based on prior hands-on experiences.

1.Creational Design Pattern - provide a way to create objects while hiding the creation logic, rather than instantiating objects directly using new operator. This gives program more flexibility in deciding which objects need to be created for a given use case.

  • Factory Pattern
  • Abstract Factory Pattern
  • Singleton Pattern
  • Prototype Pattern
  • Builder Pattern.

2. Structural Design Pattern -These design patterns concern class and object composition. Concept of inheritance is used to compose interfaces and define ways to compose objects to obtain new functionalities.

  • Adapter Pattern
  • Bridge Pattern
  • Composite Pattern
  • Decorator Pattern
  • Facade Pattern
  • Flyweight Pattern
  • Proxy Pattern

3. Behavioral Design Pattern - These design patterns are specifically concerned with communication between objects.

  • Chain Of Responsibility Pattern
  • Command Pattern
  • Interpreter Pattern
  • Iterator Pattern
  • Mediator Pattern
  • Memento Pattern
  • Observer Pattern
  • State Pattern
  • Strategy Pattern
  • Template Pattern
  • Visitor Pattern
Creational patterns

Factory Pattern

Define an interface or abstract class for creating an object but let the subclasses decide which class to instantiate. In other words, subclasses are responsible to create the instance of the class.

Usage of Factory Design Pattern
  • When a class doesn't know what sub-classes will be required to create
  • When a class wants that its sub-classes specify the objects to be created.
  • When the parent classes choose the creation of objects to its sub-classes.
EX
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class FactoryPattern {
public static void main(String args[]) throws IOException {
GetRoomFactory roomFactory = new GetRoomFactory();

System.out.print("Enter the name of room for which the bill will be generated: ");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

String roomName = br.readLine();
System.out.print("Enter the number of people for bill will be calculated: ");
int opeople = Integer.parseInt(br.readLine());

Room p = roomFactory.getRoom(roomName);

System.out.print("Bill amount for " + roomName + " of " + opeople + " people is: ");
p.getRate();
p.calculateBill(opeople);
}
}

abstract class Room {

protected double rate;

abstract void getRate();

public void calculateBill(int people) {
System.out.println(people * rate);
}
}

class Luxury extends Room {
//@override
public void getRate() {
rate = 13.50;
}
}

class Normal extends Room {
//@override
public void getRate() {
rate = 7.50;
}
}

class GetRoomFactory {

//use getPlan method to get object of type Plan
public Room getRoom(String roomType) {
if (roomType == null) {
return null;
}
if (roomType.equalsIgnoreCase("Luxury")) {
return new Luxury();
} else if (roomType.equalsIgnoreCase("Normal")) {
return new Normal();
}

return null;
}
}

Singleton

A class must ensure that only single instance should be created and single object can be used by all other classes

There are two forms of singleton design pattern
Early Instantiation: creation of instance at load time.
Lazy Instantiation: creation of instance when required.

Advantage of Singleton design pattern is saves memory because object is not created at each request. Only single instance is reused again and again.

Singleton pattern is mostly used in multi-threaded and database applications. It is used in logging, caching, thread pools, configuration settings etc.

To create the singleton class, 
  •  static member of class,
  •  private constructor 
  • static factory method.
public class SingletonPrinter {
private static SingletonPrinter printer =new SingletonPrinter();//Early, instance will be created at load time

private SingletonPrinter(){}

public static SingletonPrinter getPrinter(){
return printer;
}

public static void main(String args[]){
getPrinter().hashCode();
}
}
Builder design pattern

Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

Make sure that you can clearly define the common construction steps for building all available product representations. Otherwise, you won’t be able to proceed with implementing the pattern.
Declare these steps in the base builder interface.
  • Create a concrete builder class for each of the product representations and implement their construction steps.
  • Don’t forget about implementing a method for fetching the result of the construction. The reason why this method can’t be declared inside the builder interface is that various builders may construct products that don’t have a common interface. Therefore, you don’t know what would be the return type for such a method. However, if you’re dealing with products from a single hierarchy, the fetching method can be safely added to the base interface.
  • Think about creating a Builder class. It may encapsulate various ways to construct a product using the same builder object.
  • The client code creates both the builder and the director objects. Before construction starts, the client must pass a builder object to the director. Usually, the client does this only once, via parameters of the director’s class constructor. The director uses the builder object in all further construction. There’s an alternative approach, where the builder is passed to a specific product construction method of the director.
  • The construction result can be obtained directly from the director only if all products follow the same interface. Otherwise, the client should fetch the result from the builder.

Below exaple shows how to create differetn type of profiles with the builder design pattern


class Profile{

}

interface ProfilesBuilder {
void setName();

void addMaterial();

void addVersion();

void addSettings();

void reSet();
}

class Builder implements ProfilesBuilder{
Profile profile;
Builder(){
reSet();
}
@Override
public void setName() {

}

@Override
public void addMaterial() {

}

@Override
public void addVersion() {

}

@Override
public void addSettings() {

}

@Override
public void reSet() {
this.profile = new Profile();
}

public Profile getProfile(){
profile = this.profile;
this.reSet();
return profile;
}
}

class Director {
private ProfilesBuilder builder;

void setBuilder(ProfilesBuilder builder){
this.builder = builder;
}

void makeSurgeonProfile(ProfilesBuilder surgeonProfile){
surgeonProfile.addMaterial();
surgeonProfile.addSettings();
surgeonProfile.addVersion();
}
void makeAnestheticProfile(ProfilesBuilder surgeonProfile){
surgeonProfile.addMaterial();
surgeonProfile.addSettings();
surgeonProfile.addVersion();
}
void makeSurgicleProfile(ProfilesBuilder surgeonProfile){
surgeonProfile.addMaterial();
surgeonProfile.addSettings();
surgeonProfile.addVersion();
}
}

public class BuilderDesignPattern {
public static void main(String args[]){
Director director = new Director();

Builder builder = new Builder();
director.makeSurgeonProfile(builder);
Profile surgeonProfile = builder.getProfile();
}
}
Functional patterns

Adapter Pattern

Adapter design pattern is one of the structural design pattern and its used so that two unrelated interfaces can work together. The object that joins these unrelated interface is called an Adapter. 

One of the great real life example of Adapter design pattern is mobile charger. Mobile battery needs 3 volts to charge but the normal socket produces either 120V (US) or 240V (India). So the mobile charger works as an adapter between mobile charging socket and the wall socket.

Advantage of Adapter Pattern
  • It allows two or more previously incompatible objects to interact.
  • It allows reusability of existing functionality
When we talk via mike the Amplifier/amp will act asa adapter to maximize the volume

public class AdapterDesignPattern {
public static void main(String args[]) {
testAdapter();
}

private static void testAdapter() {
Amplifier speaker = new Speaker();
System.out.println(getDesible(speaker, 3).getDesible());

}

private static Desible getDesible(Amplifier speaker, int i) {
switch (i) {
case 2:
return speaker.getVolume2();
case 3:
return speaker.getVolume3();
}
return new Desible(0);
}
}

//To measure sound
class Desible {

public Desible(int desible) {
this.desible = desible;
}

public int getDesible() {
return desible;
}

public void setDesible(int desible) {
this.desible = desible;
}

int desible;
}


class Mike {
public Desible getDesible() {
return new Desible(1000);
}
}

//Adapter class
interface Amplifier {
Desible getVolume1();

Desible getVolume2();

Desible getVolume3();

}


class Speaker extends Mike implements Amplifier {


@Override
public Desible getVolume1() {
return getDesible();
}

@Override
public Desible getVolume2() {
return convertTODesible(getDesible(), 2);
}

@Override
public Desible getVolume3() {
return convertTODesible(getDesible(), 3);
}

private Desible convertTODesible(Desible d, int i) {
return new Desible(d.getDesible() * i);
}
}

Decorator pattern

"attach a flexible additional responsibilities to an object dynamically".
Decorator pattern allows a user to add new functionality to an existing object without altering its structure. This type of design pattern comes under structural pattern as this pattern acts as a wrapper to existing class. This pattern creates a decorator class which wraps the original class and provides additional functionality keeping class methods signature intact.


public class DecoratorPattern {
public static void main(String args[]) {
Cake birthDayCake = new BirthDayCake();
System.out.println(birthDayCake.addDeco());
Cake icingBirthdayCake = new IcingDecorator(new BirthDayCake());
System.out.println(icingBirthdayCake.addDeco());
Cake anniversaryCakeWithStand = new CakeStandDecorator(new AnniversaryCake());
System.out.println(anniversaryCakeWithStand.addDeco());
}
}


interface Cake {
void makeMixture();

void bake();

String addDeco();
}

class BirthDayCake implements Cake {

@Override
public void makeMixture() {

}

@Override
public void bake() {

}

@Override
public String addDeco() {
return " Happy Birthday";
}
}

class AnniversaryCake implements Cake {

@Override
public void makeMixture() {

}

@Override
public void bake() {

}


@Override
public String addDeco() {
return " HAppy Anniversary";
}
}

abstract class CakeDecorator implements Cake {
protected Cake cakeDecorator;

public CakeDecorator(Cake decoratorCake) {
this.cakeDecorator = decoratorCake;
}

public String addDeco() {
return cakeDecorator.addDeco();
}
}

class IcingDecorator extends CakeDecorator {

public IcingDecorator(Cake decoratorCake) {
super(decoratorCake);
}

@Override
public void makeMixture() {

}

@Override
public void bake() {

}

@Override
public String addDeco() {
return super.addDeco() + " with icing Roses";
}
}

class CakeStandDecorator extends CakeDecorator {

public CakeStandDecorator(Cake decoratorCake) {
super(decoratorCake);
}

@Override
public void makeMixture() {

}

@Override
public void bake() {

}

@Override
public String addDeco() {
return super.addDeco() + " with wooden stand ";
}
}


Behavioural patterns

Observer pattern

Observer pattern is used when there is one-to-many relationship between objects such as if one object is modified, its depenedent objects are to be notified automatically.

import java.util.ArrayList;
import java.util.List;

class OperationCenter {

private List<Observer> observers = new ArrayList<Observer>();
private int roomID;

public int getRoomID() {
return roomID;
}

public void setRoomID(int roomID) {
this.roomID = roomID;
notifyAllObservers(roomID);
}

public void attach(Observer observer){
observers.add(observer);
}

public void notifyAllObservers(int roomID){
for (Observer observer : observers) {
observer.update(roomID);
}
}
}

abstract class Observer {
protected OperationCenter observerble;
public abstract void update(int room);
}

class operationOverview extends Observer{

public operationOverview(OperationCenter subject){
this.observerble = subject;
this.observerble.attach(this);
}

@Override
public void update(int room) {
System.out.println( "Update operation overview screen with new room ID " + room );
}
}

class operationMonitoring extends Observer{

public operationMonitoring(OperationCenter opCenter){
this.observerble = opCenter;
this.observerble.attach(this);
}

@Override
public void update(int room) {
System.out.println( "Update operation monitoring screen with room " + room);
}
}

public class ObserverDesignPattern {

public static void main(String args[]){
OperationCenter OperationCenter = new OperationCenter();

new operationOverview(OperationCenter);
new operationMonitoring(OperationCenter);
OperationCenter.setRoomID(15);
OperationCenter.setRoomID(7);
}
}

Strategy pattern

A class behavior or its algorithm can be changed at run time. This type of design pattern comes under behavior pattern.Strategy pattern, we create objects which represent various strategies and a context object whose behavior varies as per its strategy object. The strategy object changes the executing algorithm of the context object.


public class StratergyPattern {
public static void main(String args[]) {

context context = new context(new OptimalPlanningOperation());
context context1 = new context(new NormalPlanningOperation());
context.doPlanOperation();
context1.doPlanOperation();
}
}

class context{
private Stratergy stratergy;
public context(Stratergy stratergy){
this.stratergy = stratergy;
}
public boolean doPlanOperation(){
return stratergy.doPlanOperation();
}
}

interface Stratergy {
boolean doPlanOperation();
}

class OptimalPlanningOperation implements Stratergy {

@Override
public boolean doPlanOperation() {
System.out.println(" Do Optimal plan Logic...");
return true;
}
}

class NormalPlanningOperation implements Stratergy {

@Override
public boolean doPlanOperation() {
System.out.println(" Do Normal plan Logic...");
return true;
}
}




Comments