Skip to content

Instantly share code, notes, and snippets.

@firhatsungkar
Last active January 30, 2019 01:05
Show Gist options
  • Save firhatsungkar/75f31641f4ea872eb4ab27b6506f2e04 to your computer and use it in GitHub Desktop.
Save firhatsungkar/75f31641f4ea872eb4ab27b6506f2e04 to your computer and use it in GitHub Desktop.
Javascript Design Pattern

Design Pattern

By � Muhamad Firhat (muhamad.firhat@gmail.com) Slide url: Google Slide

Design Pattern

Introduce By � “The gang of four”

Design Pattern is ….

“The way to approaching a problem.”

Common use

At least you must know...

  • Singleton
  • Factory
  • Abstract factory
  • Adapter
  • Facade
  • Builder
  • Decorator
  • Observer Pattern

Singleton Pattern

Ensure the class has only ONE instance and provide global access to it. The Singleton pattern is thus known because it restricts instantiation of a class to a single object. Classically, the Singleton pattern can be implemented by creating a class with a method that creates a new instance of the class if one doesn't exist. In the event of an instance already existing, it simply returns a reference to that object.

Factory Pattern

A Factory Method creates new objects as instructed by the client. One way to create objects in JavaScript is by invoking a constructor function with the new operator. There are situations however, where the client does not, or should not, know which one of several candidate objects to instantiate.

Abstract Factory Pattern

Encapsulate a group of individual factories with a common goal. It separates the details of implementation of a set of objects from their general usage. An Abstract Factory should be used where a system must be independent from the way the objects it creates are generated or it needs to work with multiple types of objects.

Adapter Pattern

The Adapter Pattern translates an interface for an object or class into an interface compatible with a specific system. Adapters basically allow objects or classes to function together which normally couldn't due to their incompatible interfaces. The adapter translates calls to its interface into calls to the original interface and the code required to achieve this is usually quite minimal.

Facade Pattern

The Façade pattern provides an interface which shields clients from complex functionality in one or more subsystems. It is a simple pattern that may seem trivial but it is powerful and extremely useful. It is often present in systems that are built around a multi-layer architecture. The intent of the Façade is to provide a high-level interface (properties and methods) that makes a subsystem or toolkit easy to use for the client.

Builder Pattern

The Builder pattern allows a client to construct a complex object by specifying the type and content only. Construction details are hidden from the client entirely. The client can still direct the steps taken by the Builder without knowing how the actual work is accomplished. Builders frequently encapsulate construction of Composite objects (another GoF design pattern) because the procedures involved are often repetitive and complex.

Decorator Pattern

The Decorator pattern extends (decorates) an object’s behavior dynamically. The ability to add new behavior at runtime is accomplished by a Decorator object which ‘wraps itself’ around the original object. Multiple decorators can add or override functionality to the original object.

Observer Pattern

The Observer is a design pattern where an object (known as a subject) maintains a list of objects depending on it (observers), automatically notifying them of any changes to state. When a subject needs to notify observers about something interesting happening, it broadcasts a notification to the observers (which can include specific data related to the topic of the notification).

References

dofactory Learning JavaScript Design Patterns - A book by Addy Osmani

DP Challenges

Make simple implementation of this all DP:

  • Singleton
  • Factory
  • Abstract factory
  • Adapter
  • Facade
  • Builder
  • Decorator
  • Observer Pattern
  • Plus 4 DP that not on the above list. (total 10)
class Vehicle {
constructor(color, wheels, state) {
if(this.constructor == Vehicle) throw new Error('Vehicle is abstarct class')
}
getColor() { throw new Error('getColor must be emplemented')}
getWheels() { throw new Error('getWheels must be emplemented')}
getState() { throw new Error('getState must be emplemented')}
}
class Car extends Vehicle {
constructor(color, wheels, state) {
super()
this.color = color
this.wheels = wheels
this.state = state
}
getColor() { return this.color }
getWheels() { return this.wheels }
getState() { return this.state }
}
class MotorCycle extends Vehicle {
constructor(color, wheels, state) {
super()
this.color = color
this.wheels = wheels
this.state = state
}
}
class AbstractVehicleFactory {
constructor() {
// if(this.constructor == AbstractVehicleFactory) throw new Error(`Can't instantiate AbstractVehicleFactory class`)
this.vehicles = {}
}
registerVehicle(name, obj) {
this.vehicles[name] = obj
}
getVehicle(name, initData={color:null, wheels:null, state:null}) {
if (this.vehicles.hasOwnProperty(name)) {
const instance = this.vehicles[name]
const { color, wheels, state } = initData
return new instance(color, wheels, state)
}
}
}
const vehiclesFactory = new AbstractVehicleFactory()
vehiclesFactory.registerVehicle('car', Car)
vehiclesFactory.registerVehicle('motorcycle', MotorCycle)
const jazz = vehiclesFactory.getVehicle(
'car',
{
color: 'blue',
wheels: 4,
state: 'New'
}
)
console.log(jazz instanceof Car) // true
console.log(jazz instanceof Vehicle) // true
console.log(jazz instanceof MotorCycle) // false
interface Vehicle {
color: string
wheels: number
state: string
}
class Car implements Vehicle {
color: string
wheels: number
state: string
constructor(color: string, wheels: number, state: string) {
this.color = color
this.wheels = wheels
this.state = state
}
}
class MotorCycle implements Vehicle {
color: string
wheels: number
state: string
constructor(color: string, wheels: number, state: string) {
this.color = color
this.wheels = wheels
this.state = state
}
}
abstract class AbstractVehicleFactory {
private static _vehicles : {} = {}
public static registerVehicle(name: string, vehicle: Function) {
this._vehicles[name] = vehicle
}
public static getVehicle(name: string, initData: Vehicle) :Vehicle {
if(this._vehicles.hasOwnProperty(name)) {
const { color, wheels, state } = initData
return new this._vehicles[name](color, wheels, state)
}
return null
}
}
AbstractVehicleFactory.registerVehicle('car', Car)
AbstractVehicleFactory.registerVehicle('motorcycle', MotorCycle)
const jazz = AbstractVehicleFactory.getVehicle(
'car',
{color: 'blue', wheels: 4, state: 'new'}
)
console.log(jazz instanceof Car) // true
console.log(jazz instanceof MotorCycle) // false
class Kindle {
constructor(config={state: 'off'}) {
this.config = config
}
turnOn() {
console.log('Turn on Kindle')
this.config.state = 'on'
console.log(`Kindle is ${this.config.state}`)
return this.config.state
}
turnOff() {
console.log('Turn off Kindle')
this.config.state = 'off'
console.log(`Kindle is ${this.config.state}`)
return this.config.state
}
readEbook(ebook) {
console.log(`Open libary`)
console.log(`Find Ebook with title: ${ebook}`)
console.log(`Open ebook with title: ${ebook}`)
return ebook
}
}
class Ipad {
constructor(config={state: 'off'}) {
this.config = config
}
weakup() {
console.log('Weakup the iPad')
this.config.state = 'on'
console.log(`Ipad is ${this.config.state}`)
return this.config.state
}
sleep() {
console.log('Turn iPad to sleep')
this.config.state = 'sleep'
console.log(`iPad is ${this.config.state}`)
return this.config.state
}
openIbook(ebook) {
console.log(`Open iBook App`)
console.log(`Find Ebook with title: ${ebook}`)
console.log(`Open ebook with title: ${ebook}`)
return ebook
}
}
class ReaderAbstract {
constructor(device) {
if (this.constructor === ReaderAbstract) throw new Error("Can't initiate ReaderInterface")
this.device = device
}
open() {
throw new Error('This class must implement open(device)')
}
read(book) {
throw new Error('This class must implement read(book)')
}
close() {
throw new Error('This class must implement read(device)')
}
}
class IpadAdaptee extends ReaderAbstract {
constructor(config) {
const device = new Ipad(config)
super(device)
}
open() { return this.device.weakup() }
read(book) { return this.device.openIbook(book) }
close() { return this.device.sleep() }
}
class KindleAdaptee extends ReaderAbstract {
constructor(config) {
const device = new Kindle(config)
super(device)
}
open() { return this.device.turnOn() }
read(book) { return this.device.readEbook(book) }
close() { return this.device.turnOff() }
}
class ReaderAdapter extends ReaderAbstract {
constructor(adapter) {
super(adapter)
}
open() { return this.device.open() }
read(book) { return this.device.read(book) }
close() { return this.device.close() }
}
const iPad = new IpadAdaptee({state: 'off'})
const reader = new ReaderAdapter(iPad)
reader.open()
reader.read('Introduction of OOP')
reader.close()
type config = {
state?: string
}
class Kindle {
protected config: config
constructor(config: config={state: 'off'}) {
this.config = config
}
turnOn() {
console.log('Turn on Kindle')
this.config.state = 'on'
console.log(`Kindle is ${this.config.state}`)
return this.config.state
}
turnOff() {
console.log('Turn off Kindle')
this.config.state = 'off'
console.log(`Kindle is ${this.config.state}`)
return this.config.state
}
readEbook(ebook: string) {
console.log(`Open libary`)
console.log(`Find Ebook with title: ${ebook}`)
console.log(`Open ebook with title: ${ebook}`)
return ebook
}
}
class Ipad {
protected config: config
constructor(config: config={state: 'off'}) {
this.config = config
}
weakup() {
console.log('Weakup the iPad')
this.config.state = 'on'
console.log(`Ipad is ${this.config.state}`)
return this.config.state
}
sleep() {
console.log('Turn iPad to sleep')
this.config.state = 'sleep'
console.log(`iPad is ${this.config.state}`)
return this.config.state
}
openIbook(ebook: string) {
console.log(`Open iBook App`)
console.log(`Find Ebook with title: ${ebook}`)
console.log(`Open ebook with title: ${ebook}`)
return ebook
}
}
interface readerInterface {
device: object
open()
read(book: string)
close()
}
class ReaderAbstract implements readerInterface {
device: any
constructor(device) {
if (this.constructor === ReaderAbstract) throw new Error("Can't initiate ReaderInterface")
this.device = device
}
open() {
throw new Error('This class must implement open(device)')
}
read(book) {
throw new Error('This class must implement read(book)')
}
close() {
throw new Error('This class must implement read(device)')
}
}
class IpadAdapter extends ReaderAbstract implements readerInterface {
constructor(config: config) {
const device = new Ipad(config)
super(device)
}
open() { return this.device.weakup() }
read(book: string) { return this.device.openIbook(book) }
close() { return this.device.sleep() }
}
class KindleAdapter extends ReaderAbstract implements readerInterface {
constructor(config: config) {
const device = new Kindle(config)
super(device)
}
open() { return this.device.turnOn() }
read(book: string) { return this.device.readEbook(book) }
close() { return this.device.turnOff() }
}
class ReaderAdapter extends ReaderAbstract implements readerInterface {
constructor(adapter: readerInterface) {
super(adapter)
}
open() { return this.device.open() }
read(book: string) { return this.device.read(book) }
close() { return this.device.close() }
}
const iPad = new IpadAdapter({state: 'off'})
const reader = new ReaderAdapter(iPad)
reader.open()
reader.read('Introduction of Design Pattern')
reader.close()
class ListBuilder {
constructor() {
this.list = []
}
add(item) {
this.list.push(item)
return this
}
remove(item) {
this.list.splice(this.list.indexOf(item), 1)
return this
}
total() {
return this.list.length
}
checkout() {
return `${this.total()} Item in the cart: ${this.list.join(', ')}`
}
build() {
return this.list
}
}
const fruitsBuilder = new ListBuilder()
fruitsBuilder.add('Apple')
fruitsBuilder.add('Pinapple')
fruitsBuilder
.add('Banana')
.remove('Pinapple')
.add('Mango')
console.log(fruitsBuilder.checkout())
console.log(fruitsBuilder.build())
class ListBuilder {
private list: Array<any>
constructor() {
this.list = []
}
add(item:any) :ListBuilder {
this.list.push(item)
return this
}
remove(item:any) :ListBuilder {
this.list.splice(this.list.indexOf(item), 1)
return this
}
total() :number {
return this.list.length
}
checkout() :string {
return `${this.total()} Item in the cart: ${this.list.join(', ')}`
}
build() : Array<any> {
return this.list
}
}
const fruitsBuilder = new ListBuilder()
fruitsBuilder.add('Apple')
fruitsBuilder.add('Pinapple')
fruitsBuilder
.add('Banana')
.remove('Pinapple')
.add('Mango')
console.log(fruitsBuilder.checkout())
console.log(fruitsBuilder.build())
class Macbook {
constructor() {
this.cost = 997
this.screenSize = 11.6
}
totalCost() {
return `$${this.cost}`
}
}
class Memory {
static decorate(macbook) {
const { cost } = macbook
macbook['cost'] = cost + 75
return macbook
}
}
class Engraving {
static decorate(macbook) {
const { cost } = macbook
macbook['cost'] = cost + 200
return macbook
}
}
class Insurance {
static decorate(macbook) {
const { cost } = macbook
macbook['cost'] = cost + 250
return macbook
}
}
let mb = new Macbook()
mb = Memory.decorate(mb)
mb = Engraving.decorate(mb)
mb = Insurance.decorate(mb)
console.log(mb.totalCost())
class Macbook {
cost: number
screenSize: number
constructor() {
this.cost = 997
this.screenSize = 11.6
}
totalCost() {
return `$${this.cost}`
}
}
class Memory {
static decorate(macbook) :Macbook {
const { cost } = macbook
macbook['cost'] = cost + 75
return macbook
}
}
class Engraving {
static decorate(macbook) :Macbook {
const { cost } = macbook
macbook['cost'] = cost + 200
return macbook
}
}
class Insurance {
static decorate(macbook) :Macbook {
const { cost } = macbook
macbook['cost'] = cost + 250
return macbook
}
}
let mb = new Macbook()
mb = Memory.decorate(mb)
mb = Engraving.decorate(mb)
mb = Insurance.decorate(mb)
console.log(mb.totalCost())
class Bank {
verify(name, amount) {
// Complex logic
return true
}
}
class Credit {
get(name) {
// Complex Logic
return true
}
}
class Background {
check(name) {
// Complex Logic
return true;
}
}
class Mortgage {
constructor(name) {
this.name = name
this.bank = new Bank()
this.credit = new Credit()
this.background = new Background()
}
applyFor(amount) {
let result = 'approved'
const { name, bank, credit, background} = this
if (!bank.verify(name, amount)) {
result = 'denied'
}
if (!credit.get(name)) {
result = 'denied'
}
if (!background.check(name)) {
result = 'denied'
}
return `${name} has been ${result} for a mount ${amount} mortgage.`
}
}
const mortgage = new Mortgage('Jon Doe')
const result = mortgage.applyFor("$100,000")
console.log(result)
class Bank {
verify(name, amount) {
// Complex logic
return true
}
}
class Credit {
get(name) {
// Complex Logic
return true
}
}
class Background {
check(name) {
// Complex Logic
return true;
}
}
class Mortgage {
protected name: string
protected bank: Bank
protected credit: Credit
protected background: Background
constructor(name) {
this.name = name
this.bank = new Bank()
this.credit = new Credit()
this.background = new Background()
}
applyFor(amount) {
let result = 'approved'
const { name, bank, credit, background} = this
if (!bank.verify(name, amount)) {
result = 'denied'
}
if (!credit.get(name)) {
result = 'denied'
}
if (!background.check(name)) {
result = 'denied'
}
return `${name} has been ${result} for a mount ${amount} mortgage.`
}
}
const mortgage = new Mortgage('Jon Doe')
const result = mortgage.applyFor("$100,000")
console.log(result)
class Vehicle {
constructor(color, wheels, state) {
if(this.constructor == Vehicle) throw new Error('Vehicle is abstarct class')
}
getColor() { throw new Error('getColor must be emplemented')}
getWheels() { throw new Error('getWheels must be emplemented')}
getState() { throw new Error('getState must be emplemented')}
}
class Car extends Vehicle {
constructor(color, wheels, state) {
super()
this.color = color
this.wheels = wheels
this.state = state
}
getColor() { return this.color }
getWheels() { return this.wheels }
getState() { return this.state }
}
class MotorCycle extends Vehicle {
constructor(color, wheels, state) {
super()
this.color = color
this.wheels = wheels
this.state = state
}
getColor() { return this.color }
getWheels() { return this.wheels }
getState() { return this.state }
}
class VehicleFactory {
static createCar(color, wheels, state) {
return new Car(color, wheels, state)
}
static createMotorCycle(color, wheels, state) {
return new MotorCycle(color, wheels, state)
}
}
const jazz = VehicleFactory.createCar('blue', 4, 'new')
console.log(jazz instanceof Car) // true
console.log(jazz instanceof Vehicle) // true
console.log(jazz instanceof MotorCycle) // false
interface Vehicle {
color: string
wheels: number
state: string
}
class Car implements Vehicle {
color: string
wheels: number
state: string
constructor(color: string, wheels: number, state: string) {
this.color = color
this.wheels = wheels
this.state = state
}
}
class MotorCycle implements Vehicle {
color: string
wheels: number
state: string
constructor(color: string, wheels: number, state: string) {
this.color = color
this.wheels = wheels
this.state = state
}
}
abstract class Vehiclefactory {
public static car(color, wheels, state) : Vehicle {
return new Car(color, wheels, state)
}
public static motorcycle(color, wheels, state) : Vehicle {
return new MotorCycle(color, wheels, state)
}
}
const jazz = Vehiclefactory.car('blue', 3, 'new')
console.log(jazz instanceof Car) // true
console.log(jazz instanceof MotorCycle) // false
class YoutubeChannel {
constructor(channelName) {
this.handlers = []
this.channelName = channelName
}
subscribe(subscriber) {
this.handlers.push(subscriber)
console.log(`${subscriber.username} subscribe to ${this.channelName} channel`)
}
unsubscribe(subscriber) {
this.handlers = this.handlers.filter(subs => subs !== subscriber)
console.log(`${subscriber.username} unsubscribe from ${this.channelName} channel`)
}
notify(info) {
this.handlers.forEach(subs => subs.notification(info, this.channelName) )
}
}
class YoutubeUser {
constructor(username) {
this.username = username
}
notification(info, from) {
console.log(`${this.username}: Notification from channel ${from}: ${info}`)
}
}
const personA = new YoutubeUser('personA')
const personB = new YoutubeUser('personB')
const personC = new YoutubeUser('personC')
const ToiletLovers = new YoutubeChannel('ToiletLovers')
ToiletLovers.subscribe(personA)
ToiletLovers.subscribe(personB)
ToiletLovers.subscribe(personC)
ToiletLovers.unsubscribe(personA)
ToiletLovers.notify('New toilet review video.')
interface Observer {
subscribers :Array<Observee>
subscribe(subscriber)
unsubscribe(subscriber)
notify(info)
}
interface Observee {
notification(info, from)
}
class YoutubeChannel implements Observer {
subscribers: Array<Observee>
channelName: string
constructor(channelName: string) {
this.subscribers = []
this.channelName = channelName
}
subscribe(subscriber:YoutubeUser) {
this.subscribers.push(subscriber)
console.log(`${subscriber.username} subscribe to ${this.channelName} channel`)
}
unsubscribe(subscriber:YoutubeUser) {
this.subscribers = this.subscribers.filter(subs => subs !== subscriber)
console.log(`${subscriber.username} unsubscribe from ${this.channelName} channel`)
}
notify(info:string) {
this.subscribers.forEach(subs => subs.notification(info, this.channelName) )
}
}
class YoutubeUser implements Observee {
username: string
constructor(username) {
this.username = username
}
notification(info:string, from:string) {
console.log(`${this.username}: Notification from channel ${from}: ${info}`)
}
}
const personA = new YoutubeUser('personA')
const personB = new YoutubeUser('personB')
const personC = new YoutubeUser('personC')
const ToiletLovers = new YoutubeChannel('ToiletLovers')
ToiletLovers.subscribe(personA)
ToiletLovers.subscribe(personB)
ToiletLovers.subscribe(personC)
ToiletLovers.unsubscribe(personA)
ToiletLovers.notify('New toilet review video.')
var Singleton = (function () {
var instance;
function createInstance() {
var object = new Object("I am the instance");
return object;
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
function run() {
var instance1 = Singleton.getInstance();
var instance2 = Singleton.getInstance();
console.log("Same instance? " + (instance1 === instance2));
}
run()
class MyClass {
private static _instance : MyClass
constructor() {}
public static get Instance() : MyClass
{
return this._instance || (this._instance = new this())
}
}
const singleton = MyClass.Instance
const other = MyClass.Instance
console.log(singleton instanceof MyClass)
console.log(singleton == other)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment