What is a Design Pattern?
Design patterns are conventional answers to common software design challenges. Each pattern is similar to a blueprint that you may modify to tackle a specific design challenge in your code.
All designers need to be aware of these latest design trends. Click to explore about, UX UI Designs and Latest Trends
What are the types of design patter?
There are mainly 3 patterns:
- Creational Pattern: Give object creation tools that improve the flexibility and reusability of current code.
- Structural Pattern: Demonstrate how to combine objects and classes to create larger structures while keeping the structures flexible and efficient.
- Behavioral Pattern: Take care of good communication and the delegation of responsibility among objects.
Creational |
Structural |
Behavioural |
Factory Method |
Adapter |
Chain of responsibility |
Abstract Factory |
Bridge |
Command |
Builder |
Composite |
Iterator & Observer |
Prototype |
Decorator |
Mediator |
Singleton.. |
Facade.. |
Memento ... |
We will focus on a few patterns in this blog.
Creational Pattern
Object creation mechanisms are provided by creational patterns, which increase the flexibility and reuse of existing code.
Factory Method
One of the most prevalent creation patterns is the factory pattern. The factory implementation is in charge of creating object instances. The interface hides the implementation details from client code, resulting in the loose coupling of the application.
Usage: To reduce coupling between parent class & subclasses
abstract class Creator {
public abstract factoryMethod(): PaymentGateway;
public paymentOperation(): string {
const upiApp = this.factoryMethod();
return
`Creator: The same payment creator's code worked with ${upiApp.pay()}`;
}
}
class GPayApp extends Creator {
public factoryMethod(): PaymentGateway {
return new GPayApp();
}
}
class PhonePayApp extends Creator {
public factoryMethod(): PaymentGateway {
return new PhonePay();
}
}
interface PaymentGateway {
pay(): string;
}
class GPayAppPayment implements PaymentGateway {
Public pay(): string {
return '{Paid with GPayApp}';
}
}
class PhonePayAppPayment implements PaymentGateway {
public pay(): string {
return '{Paid with PhonePeApp}';
}
}
A few terms to keep in mind before starting SOLID are the principle of least knowledge, coupling, and cohesion. Click to explore about, SOLID Principles in JavaScript and TypeScript
Builder
Builder is a creational design pattern that enables the gradual construction of complicated objects.
Usage: When you need to construct an object with many configuration choices, this method comes in handy.
export interface PC {
name: string;
storageCapacity: number;
graphicCard: string;
}
export class PCBuilder {
private readonly _pc: PC;
constructor() {
this._pc = {
name: "",
storageCapacity: 0,
graphicCard: 0,
};
}
name(name: string): PCBuilder {
this._pc.name = name;
return this;
}
storageCapacity (storageCapacity: number): UserBuilder {
this._pc.storageCapacity: = storageCapacity:;
return this;
}
graphicCard (graphicCard: number): UserBuilder {
this._pc.graphicCard: = graphicCard;
return this;
}
build(): PC { return this._pc; }
}
const pcWithGraphicCard:PC = new PCBuilder()
.name("Dell XPS")
.graphicCard("NVIDIA GEFORCE GTX 3070")
.build();
Singleton
Singleton is a creational design pattern that assures that only one object of its kind exists and gives other code a single point of access to it.
Usage: If you want to prevent multiple instances of the same class. Real-world example: Services in angular
class Singleton {
private static dbInstance: Singleton;
private constructor() { }
public static getDbInstance(): Singleton {
if (!Singleton.dbInstance) {
Singleton.dbInstance = new Singleton();
}
return Singleton.dbInstance;
}
public getDBConnection() {
return “You are connected”
}
}
function createDBInstance() {
const first_instance = Singleton.getDbInstance();
const second_instance = Singleton.getDbInstance();
return s1 === s2 ? console.log('Single DB instance created');
: console.log('More Instance of a DB created');
}
clientCode();
Single DB instance created
A UX designer would apply similar ideas to producing a mobile app, just as an architect could design a house or building to make it easy for the occupant to explore the space. Click to explore about, User Experience in Software Product Development
Structural Pattern
Structural patterns describe how to put items and classes together to form larger structures that are both flexible and efficient.
Adapter Pattern
The adapter design pattern is a structural design pattern that allows items with mismatched interfaces to work together.
Usage: make new code compatible with Legacy code.
Few terms to be aware of:
- Client class containing application logic
- Adaptee is the class that is incompatible with the Target interface.
- The adapter is the class that allows the client to use it.
class Target {
public request(): string {
return 'Target: The default target\'s behavior.';
}
}
class Adaptee {
public specificRequest(): string {
return '.eetpadA eht fo roivaheb laicepS';
}
}
class Adapter extends Target {
private adaptee: Adaptee;
constructor(adaptee: Adaptee) {
super();
this.adaptee = adaptee;
}
public request(): string {
const result = this.adaptee.specificRequest().split('').reverse().join('');
return `Adapter: (TRANSLATED) ${result}`;
}
}
function clientCode(target: Target) {
console.log(target.request());
}
const target = new Target();
clientCode(target);
const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
clientCode(adapter);
Facade
The facade may be detected in a class with a simple interface but delegates most of the work to other classes. Usually, facades manage the complete life cycle of things they employ.
Usage: It comes in helpful, especially when working with sophisticated frameworks and APIs, as it creates abstraction and lets you focus on the main thing
Think of an App System which has to:
- connect to a DB
- show data in UI
- update a record
- Refresh data in UI
The facade can be used here to handle all of this subtask & create abstraction
class Facade {
Protected databaseSystem: DataBaseSystem;
Protected uiSystem: UISystem;
constructor(databaseSystem: DataBaseSystem = null, uiSystem: UiSystem = null) {
this.databaseSystem = databaseSystem || new DataBaseSystem();
this.uiSystem = uiSystem || new UiSystem();
}
public operation(): string {
console.log('Facade initializes subsystems UI & DB ::');
this.databaseSystem.connectPostgres()
this.uiSystem.showData();
result += this.databaseSystem.updateRecord();
result += this.uiSystem.refreshUIData();
return result;
}
}
class DataBaseSystem {
public connectPostgres(): string {
return 'System connected to PostgresDB';
}
public updateRecord(): string {
return 'update Record In DB';
}
}
class UISytem{
public showData(): string {
return 'Showing Data in Form of Tables';
}
public refreshUIData(): string {
return 'UI data is refreshing….';
}
}
function App(facade: Facade) {
console.log(facade.operation());
}
const databaseSystem = new DataBaseSystem();
const uiSystem = new UiSystem();
const facade = new Facade(databaseSystem, uiSystem);
App(facade);
Behavioural Pattern
Observer
The observer is a behavioral design technique that lets some things send notifications to other objects when their state changes.
Usage: To notify all objects who are subscribing if any event occurs. RxJS used in angular is an example where this pattern is used
interface Isubject {
subscribe(observer: Observer): void;// Attach an observer to the subject.
unSubscribe(observer: Observer): void; //Detach observer from the subject.
next(): void; // Notify all observers about an event.
}
interface Observer {
update(subject: Subject): void;
}
class Subject implements Isubject {
public state: number;
private observers: Observer[] = []; //List of subscribers
public subscribe(observer: Observer): void {
const isExist = this.observers.includes(observer);
if (isExist) {
return console.log('Subject: Observer has already subscribed');
}
console.log('Subject: Observer Subscribed');
this.observers.push(observer);
}
public unSubscribe(observer: Observer): void {
const observerIndex = this.observers.indexOf(observer);
if (observerIndex === -1) {
return console.log('Subject: Non existent observer.');
}
this.observers.splice(observerIndex, 1);
console.log('Subject: Unsubscribed.');
}
public next(): void {
console.log('Subject: Notifying observers...');
for (const observer of this.observers) {
observer.update(this);
}
}
public doStuff(): void {
const MAX_VAL = 10;
this.state = Math.floor(Math.random() * MAX_VAL);
console.log(`Subject: State Changed`);
this.next();
}
}
class ObserverA implements Observer {
public update(subject: Subject): void {
if (subject instanceof Subject) {
console.log('ObserverA Triggered');
}
}
}
class ObserverB implements Observer {
public update(subject: Subject): void {
if (subject instanceof Subject) {
console.log('ObserverB Triggered');
}
}
}
const subject = new Subject();
const observer1 = new ObserverA();
subject.subscribe(observer1);
const observer2 = new ObserverB();
subject.subscribe(observer2);
subject.doStuff();
subject.doStuff();
subject.unSubscribe(observer2);
subject.doStruff();
Conclusion
We are doing all these mainly to achieve Loose coupling, High Cohesion, Reusability, and Adaptability of old and new code.
- Click To explore about Design Thinking for Agile User Stories
- Explore more about Data Visualization Dashboard Designs