Security Theater
SecTheater is an online teaching community that targets the IT department. We do our best to produce for you high quality and well-edited screen casts about web development.
Object oriented design
SOLID - Design patterns
> Software designing principles for your objects made by Robert C. Martin in order to produce a cleaner object communication
Single
responsibility
Open closed
Liskov
substitution
Interface
segregation
Dependency Inversion
> A class/function must be responsible only for one thing. (Do one job only)
> There must be only one reason for which a class/function can be modified
> Use semantic names, very specific ones for small classes/utils and more generic ones for bigger entities.
export class Blog<T extends Article> {
private _articles: T[];
constructor() {
this._articles = [];
}
get articles(): T[] {
return this._articles;
}
}
export default interface Article {
id: number
title: string
url: string
image: string
}
The entity Blog represents the model that talks to the database which is an in-memory array in our case for simplicity.
export class Blog<T extends Article> {
private _articles: T[];
constructor() {
this._articles = [];
}
get articles(): T[] {
return this._articles;
}
add(article: T): number {
this._articles.push(article);
return article.id;
}
}
export class Blog<T extends Article> {
...
add(article: T): number {
this._articles.push(article);
return article.id;
}
remove(id: number): T | undefined {
const article = this._articles.find(
(article) => article.id == id,
);
this._articles = this._articles.filter(
(article) => article.id !== id,
);
return article;
}
}
What about storing articles?
import fs from 'fs'
export class Blog<T extends Article> {
...
toString(): string {
return this._articles
.map(
(article) =>
`${article.title} can be accessed through ${article.url}`,
)
.join('\n');
}
saveToFile(filename: string) {
fs.writeFileSync(
`${__dirname}/${filename}`,
this.toString(),
);
}
}
Alright, sounds good, doesn't it?
Why don't we support multi-format storage
like .txt, .json?
export class Blog<T extends Article> {
...
toJson(): string {
return JSON.stringify(this._articles, null, 2);
}
saveToFile(filename: string, format: string = 'txt') {
if (format == 'txt')
fs.writeFileSync(
`${__dirname}/${filename}`,
this.toString(),
);
if (format == 'json')
fs.writeFileSync(
`${__dirname}/${filename}`,
this.toJson(),
);
}
}
Alright, provide me with file extension and I'll figure out how to store it
Aight aight, the last update
can we read from online APIs? 😁
Hopefully, by now, you've noticed the anti-pattern we're heading towards...
THE GOD CLASS
The god class is a term or an anti-pattern which indicates that you're building a class that literally does every single job your entity wants to have
So, How to fix this issue?!
import Article from './types/Article';
export class Blog<T extends Article> {
private _articles: T[];
constructor() {
this._articles = [];
}
get articles(): T[] {
return this._articles;
}
add(article: T): number {}
remove(id: number): T | undefined {}
toString(): string {}
toJson(): string {}
saveToFile(filename: string, format: string = 'txt') {}
}
import PersistencyManager from './PersistencyManager';
import Article from './types/Article';
export class Blog<T extends Article> {
private _articles: T[];
constructor() {
this._articles = [];
this._pm = new PersistencyManager();
}
// ...
get pm(): PersistencyManager {
return this._pm;
}
// ...
}
import fs from 'fs';
export default class PersistencyManager {
saveToFile(filename: string, data: any) {
fs.writeFileSync(`${__dirname}/${filename}`, data);
}
}
b.add(/*some article*/);
b.add(/*some article*/);
b.add(/*some article*/);
b.pm.saveToFile('articles.txt', b.toString());
b.pm.saveToFile('articles.json', b.toJson());
> Entities should be open for extension, closed for modification
> Extend your functionality by adding new code not modifying existing one
> Separate your business logic so that your code can become more loosely coupled and never breaks
import { Color } from './types/Color';
import { Size } from './types/Size';
export class Product {
constructor(
public name: string,
public color: Color,
public size: Size,
) {}
}
Product.ts
export enum Size {
'small',
'medium',
'large',
}
Size.ts
export enum Color {
'green',
'red',
'blue',
}
Color.ts
import { Product } from './Product';
import { Color } from './types/Color';
export default class ProductFilter {
filterByColor(products: Product[], color: Color) {
return products.filter(
(product: Product) => product.color === color,
);
}
}
ProductFilter.ts
What if the customer wants to filter by size?
import { Product } from './Product';
import { Color } from './types/Color';
import { Size } from './types/Size';
export default class ProductFilter {
filterByColor(products: Product[], color: Color) {
return products.filter(
(product: Product) => product.color === color,
);
}
filterBySize(products: Product[], size: Size) {
return products.filter(
(product: Product) => product.size === size,
);
}
}
ProductFilter.ts
What if the customer wants to filter by size and color?
What if the customer wants to filter by size or color?
We also need to filter other items like categories, courses
Hey, we forgot to tell you, wen eed a price filter :D
Abstraction
It gets you out
of clowny zone
Filtering
Not only responsible
for filtering products
it filters any item
Filters
Filter
Adheres to
Filterar
import { Product } from '../../Product';
export interface Filter {
isSatisfied(item: Product): boolean;
}
Filter.ts
import { Product } from '../../Product';
import { Color } from '../../types/Color';
import { Filter } from '../Contracts/Filter';
export default class ColorFilter implements Filter {
constructor(private color: Color) {}
isSatisfied(item: Product): boolean {
return item.color === this.color;
}
}
ColorFilter.ts
import { Product } from '../../Product';
import { Size } from '../../types/Size';
import { Filter } from '../Contracts/Filter';
export default class SizeFilter implements Filter {
constructor(private size: Size) {}
isSatisfied(item: Product): boolean {
return item.size === this.size;
}
}
SizeFilter.ts
Contract
Concrete
classes/implementations
import { Product } from '../Product';
import { Filter } from './Contracts/Filter';
export default class Filterar {
filter(items: Product[], filter: Filter) {
return items.filter((item) => filter.isSatisfied(item));
}
}
Filterar.ts
The filterar doesn't care what filter it gets
But it must have isSatisfied method a.k.a implements the Filter interface
What about filtering using color and size?!
import { Product } from '../../Product';
import { Filter } from '../Contracts/Filter';
export default class AndFilter implements Filter {
constructor(private filters: Filter[]) {}
isSatisfied(item: Product): boolean {
return this.filters.every((filter) =>
filter.isSatisfied(item),
);
}
}
AndFilter.ts
import { Product } from '../../Product';
import { Filter } from '../Contracts/Filter';
export default class OrFilter implements Filter {
constructor(private filters: Filter[]) {}
isSatisfied(item: Product): boolean {
return this.filters.some((filter) =>
filter.isSatisfied(item),
);
}
}
OrFilter.ts
Rather than accepting a value
like green for color or large
for size, we expect the
filter instance itself
actually, an array of filter
instances
The every function returns true only if all items within the filters array return true a.k.a
all filters are satisfied
The some function returns true only if at least one of the items within the filters array return true a.k.a
any filter is satisfied
ANOTHER
MENTAL
MODEL
Don't extract to an interface
but extend and override
I highly recommend that you code to an interface
cause abstraction reduces coupling
Let's inspect it through an example...
class EmailService {
public sendEmail(email: string, message: string): void {
console.log(`Email Sent: ${message} to ${email}`);
}
}
Some third-party service to send emails to users
class NotificationService {
private _emailService: EmailService;
constructor() {
this._emailService = new EmailService();
}
public sendNotification(email: string, message: string) {
this._emailService.sendEmail(email, message);
}
}
Maybe our implementation of how to notify user about news
What if users want to be notified
by SMS?
class SMSService {
public sendSms(phone: number, message: string): void {
console.log(`Message ${message} sent to ${phone}`);
}
}
class NotificationService {
private _emailService: EmailService;
private _smsService: SMSService;
constructor() {
this._emailService = new EmailService();
this._smsService = new SMSService();
}
public sendNotification(
email: string,
message: string,
phone?: number,
smsMessage?: string
) {
this._emailService.sendEmail(email, message);
if (phone && smsMessage) {
this._smsService.sendSms(phone, smsMessage);
}
}
}
We're by default emailing the user
If user has a phone number, we'll sms him
class NotificationService {
private _emailService: EmailService;
constructor() {
this._emailService = new EmailService();
}
public sendNotification(email: string, message: string) {
this._emailService.sendEmail(email, message);
}
}
class OrderNotificationService extends NotificationService {
private _smsService: SMSService;
constructor() {
super();
this._smsService = new SMSService();
}
public sendOrderNotification(
email: string,
emailMessage: string,
phone?: number,
smsMessage?: string
) {
if (email && emailMessage) {
this.sendNotification(email, emailMessage);
}
if (phone && smsMessage) {
this._smsService.sendSms(phone, smsMessage);
}
}
}
The previous implementation is also an implementation of the OCP
But why?
Well, we're not modifying the base functionality, we're extending and producing a new one on top of it.
So the class NotificationService is considered open for extension, closed for modification.
But, this implementation produces another issue.
Inheritance Hell
Inheritance Hell
NotificationService
SMSNotificationService
CallNotificationService
MessengerNotificationService
And any other creative way of disturbing your user to inform him that you published a blog or something
HomeNotificationService
Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.
Barbara Liskov
To build software systems from interchangeable parts, those parts must adhere to a contract that allows those parts to be substituted one for another.
Robert C. Martin
> Any derived class can be substituted with its parent class without the consumer knowing it
> Every class that implements an interface should be suitable to substitute any other reference that implements the interface
> Every part of the code should get the same results no matter what instance you inject into it, on the condition that this instance implements the same interface
class Rectangle {
constructor(
private _width: number,
private _height: number,
) {}
get width() {
return this._width
}
get height() {
return this._height
}
area(): number {
return this._width * this._height
}
}
class Rectangle {
constructor(
private _width: number,
private _height: number,
) {}
area(): number {
return this.width * this.height
}
toString() {
return `${this._width}×${this._height}`
}
}
let rc = new Rectangle(2, 3)
console.log(rc.area()) // 6
console.log(rc.toString()) // 2x3
class Rectangle {
set width(v: number) {
this._width = v
}
set height(v: number) {
this._height = v
}
}
let rc = new Rectangle(2, 3)
console.log(rc.area()) // 6
console.log(rc.toString()) // 2x3
rc.width = 4
console.log(rc.area()) // 12
console.log(rc.toString()) // 4x3
class Square extends Rectangle {
constructor(private size: number) {
super(size, size)
}
}
class Square extends Rectangle {
constructor(private size: number) {
super(size, size)
}
}
let sq = new Square(5)
console.log(sq.area) // 25
console.log(sq.toString()) // 5x5
sq.height = 35
console.log(sq.area) // 175
console.log(sq.toString()) // 5x35
class Square extends Rectangle {
set width(v: number) {
this._height = this._width = v
}
set height(v: number) {
this._height = this._width = v
}
}
let sq = new Square(5)
console.log(sq.area) // 25
console.log(sq.toString()) // 5x5
sq.height = 35
console.log(sq.area) // 1225
console.log(sq.toString()) // 35x35
function useIt(rc: any) {
let { _width } = rc
rc.height = 10
console.log(
`Expected area of ${10 * _width}` +
` got instead ${rc.area}`,
)
}
let rc = new Rectangle(5, 3)
let sq = new Square(5)
useIt(rc) // Expected area of 50 got instead 50
useIt(sq) // Expected area of 50 got instead 100
interface Shape {
get area(): number
}
class Rectangle implements Shape {
constructor(
private _width: number,
private _height: number,
) {}
set width(v: number) {
this._width = v
}
set height(v: number) {
this._height = v
}
get area(): number {
return this._height * this._width
}
}
class Square implements Shape {
constructor(private _size: number) {}
set size(v: number) {
this._size = v
}
get area(): number {
return this._size * this._size
}
}
interface InterviewResult {
status: 'Accepted' | 'Rejected'
message: string
from: Mail
to: Mail[]
}
interface InterviewResult {
status: 'Accepted' | 'Rejected'
message: string
from: Mail
to: Mail[]
}
interface MailService {
send(from: Mail, to: Mail[]): Promise<InterviewResult>
}
interface InterviewResult {
status: 'Accepted' | 'Rejected'
message: string
from: Mail
to: Mail[]
}
interface MailService {
send(from: Mail, to: Mail[]): Promise<InterviewResult>
}
class SendGridEmail implements MailService {
send(from: Mail, to: Mail[]): Promise<InterviewResult> {
// Some specific service logic
return Promise.resolve({
status: 'Accepted',
message: 'Hello world',
from,
to,
})
}
}
interface InterviewResult {
status: 'Accepted' | 'Rejected'
message: string
from: Mail
to: Mail[]
}
interface MailService {
send(from: Mail, to: Mail[]): Promise<InterviewResult>
}
class MailGunService implements MailService {
send(from: Mail, to: Mail[]): Promise<InterviewResult> {
// Some specific service logic
return Promise.resolve({
status: 'Rejected',
message: 'Sorry :/',
from,
to,
})
}
}
class UserController {
constructor(private emailSerivce: MailService) {}
notifyUser() {
this.emailSerivce.send('cto@somesite.com', [
'candidate_one@abc.com',
'candidate_two@abc.com',
])
}
}
const someUser = new UserController(new SendGridEmail)
const anotherUser = new UserController(new MailGunService)
> A client is not meant to be forced to implement methods it won't use
> A client should only and only depend on the methods it's calling
> Modifying a method in a class should not break other classes that do not depend on that method but depend on the interface
> Replace fat interfaces with smaller ones that are specific to the job
type Size = 'A4' | 'A3'
class Doc {}
interface Machine {
print(doc: Doc): string
fax(doc: Doc): { to: string; content: string }
scan(doc: Doc): { size: Size; content: string }
}
class MultiFunctionPrinter implements Machine {
print(doc: Doc): string {
return `Print ${doc}`
}
fax(doc: Doc): { to: string; content: string } {
return {
to: 'ahmed',
content: doc as string,
}
}
scan(doc: Doc): { size: Size; content: string } {
return {
size: 'A4',
content: doc as string,
}
}
}
class OldFashionPrinter implements Machine {
print(doc: Doc): string {
return `Print ${doc}`
}
}
Compilation error that it doesn't implement Machine correctly, missing 2 methods
class OldFashionPrinter implements Machine {
print(doc: Doc): string {
return `Print ${doc}`
}
fax(doc: Doc): { to: string; content: string } {
throw new Error('Method not implemented.')
}
scan(doc: Doc): { size: Size; content: string } {
throw new Error('Method not implemented.')
}
}
interface Machine {
print(doc: Doc): string
fax(doc: Doc): { to: string; content: string }
scan(doc: Doc): { size: Size; content: string }
}
interface IFax {
fax(doc: Doc): {
to: string
content: Doc
}
}
interface IPrint {
print(doc: Doc): string
}
interface IScan {
scan(doc: Doc): {
date: Date
content: Doc
}
}
class MutliFunctionPrinter implements IPrint, IFax, IScan {
fax(doc: Doc): { to: string; content: Doc } {
return {
to: 'someone',
content: doc,
}
}
print(doc: Doc): string {
return `Printing ${doc}`
}
scan(doc: Doc): { date: Date; content: Doc } {
return {
date: new Date(),
content: doc,
}
}
}
class OldFashionedPrinter implements IPrint {
print(doc: Doc): string {
return `Printing ${doc}`
}
}
interface Formatable<T> {
toArray(): T[]
toJson(): Json
toString(): string
}
type Primitive =
| string
| number
| null
| boolean
| undefined
| bigint
type JsonArray = Json[]
type JsonObject = { [k: string]: Json }
type Json = Primitive | JsonObject | JsonArray
abstract class Model<T> implements Formatable<T> {
toArray(): T[] {
throw new Error('Method not implemented.')
}
toJson(): Json {
return JSON.stringify(this)
}
}
interface Arrayable<T> {
toArray(): T[]
}
interface Jsonable {
toJson(): Json
}
abstract class Model<T> implements Arrayable<T>, Jsonable {
toArray(): T[] {
throw new Error('Method not implemented.')
}
toJson(): Json {
return JSON.stringify(this)
}
}
> High level modules are not meant to depend on low level modules
> High level modules should not depend on concrete implementations but on abstraction
> Low level modules should depend on abstraction
> Changing an implementation in low level modules should not cause any modifications in high level modules
enum Relationship {
parent = 0,
child = 1,
sibling = 2,
}
class Person {
constructor(public name: string) {}
}
interface Relation {
from: Person
type: Relationship
to: Person
}
class Relationships {
private _relations: Relation[] = []
public get relations(): Relation[] {
return this._relations
}
constructor() {
this._relations = []
}
addParentAndChild(parent: Person, child: Person) {
this._relations.push({
from: parent,
type: Relationship.parent,
to: child,
})
}
}
class Research {
constructor(private relationships: Relationships) {
let { relations } = relationships
for (let rel of relations.filter(
(r) =>
r.from.name == 'ahmed' &&
r.type == Relationship.parent,
)) {
console.log(`Ahmed has ${rel.to.name}`)
}
}
}
let parent = new Person('ahmed')
let child1 = new Person('khaled')
let child2 = new Person('mona')
let rels = new Relationships()
rels.addParentAndChild(parent, child1)
rels.addParentAndChild(parent, child2)
new Research(rels)
class Research {
constructor(private browser: RelationshipBrowser) {
for (let p of browser.findAllChildren('ahmed')) {
console.log(p)
}
}
}
interface RelationshipBrowser {
findAllChildren(name: string): Person[]
}
class Relationships implements RelationshipBrowser {
findAllChildren(name: string): Person[] {
return this._relations
.filter(
(r) =>
r.from.name == name &&
r.type == Relationship.parent,
)
.map((r) => r.to)
}
}
A design problem is a general problem that hasn't occurred in a single program but many programs if followed the same technique.
Personal definition
a design pattern is a general repeatable solution to a commonly occurring problem in software design
Design patterns are typical solutions to commonly occurring problems in software design. They are like pre-made blueprints that you can customize to solve a recurring design problem in your code.
Intention
Problem
Solution
Structural
Creational
Behavioral
Creational patterns are related to the mechanisms of constructing your objects
Dependency injection was some sort of a pattern to make communication between objects
But sometimes these constructions become so complex that DI isn't sufficient.
Abstract factory
Builder
Factory method
Singleton
Prototype
Object pool
Simple Factory
Abstract Factory
Factory Method
Object creation can become more complex even if it's one object
Optional parameter hell
Intent The factory method provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
Image from refactoring.guru
MySQL or SQLite
Local or AWS
Can be omitted
Can be Database, Storage
interface Shape {
area(): number
}
class Square implements Shape {
constructor(private side: number) {}
area(): number {
return this.side * this.side
}
}
class Circle implements Shape {
constructor(private radius: number) {}
area(): number {
return this.radius * this.radius * Math.PI
}
}
class Rectangle implements Shape {
constructor(
private width: number,
private height: number,
) {}
area(): number {
return this.width * this.height
}
}
interface ShapeFactory {
createShape(): Shape
}
class RectangleFactory implements ShapeFactory {
createShape(): Shape {
return new Rectangle(3, 2)
}
}
class CircleFactory implements ShapeFactory {
createShape(): Shape {
return new Circle(3)
}
}
class SquareFactory implements ShapeFactory {
createShape(): Shape {
return new Square(5)
}
}
Shape
Rectangle
Square
Circle
ShapeFactory
Rectangle
Factory
Square
Factory
Circle
Factory
Abstract Contracts
Concrete Implementations
Intent Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.
Image from refactoring.guru
The consumer
Some contract that produces multiple instances that are some how related
Some implementation
that produces related instances of type X maybe
Some implementation
that produces related instances of type Y maybe
Maybe Database and Storage drivers
Button
Dialog
MacOS
Linux
Windows
MacOS
Linux
Windows
Concrete Implementations
Abstract Contracts
Intent 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.
Image from refactoring.guru
Can be omitted in some cases.
The consumer
Directs the usage of the builder, which instance to create
and maybe change the builder during the runtime
The contract itself which specifies how to build instances
The implementations that produce complex objects of some type
The problem: class constructor is overloaded with parameters a.k.a parameter hell
class User {
first_name: string
last_name: string
email: string
github_link: string
twitter_link: string
cart: { id: number; price: number }[]
constructor(
first_name: string,
last_name: string,
email: string,
github_link: string,
twitter_link: string,
cart: { id: number; price: number }[],
) {
this.first_name = first_name
this.last_name = last_name
this.github_link = github_link
this.twitter_link = twitter_link
this.email = email
this.cart = cart
}
}
The problem: class constructor is overloaded with parameters a.k.a parameter hell
class User {
constructor(
public first_name: string,
public last_name: string,
public email: string,
public github_link: string,
public twitter_link: string,
public cart: { id: number; price: number }[],
) {}
}
Intent Singleton is a creational design pattern that lets you ensure that a class has only one instance while providing a global access point to this instance.
Image from refactoring.guru
class Logger {
private static instance: Logger
private constructor() {}
static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger()
}
return Logger.instance
}
}
let logger = Logger.getInstance()
let logger1 = Logger.getInstance()
console.log(logger == logger1) // true
Intent Prototype is a creational design pattern that lets you copy existing objects without making your code dependent on their classes.
Image from refactoring.guru
Consumer
The contract specifies how
to clone objects
The concrete implementations which control the cloning of each objects and handle edge cases like deep copying objects
interface Cloneable {
clone(): Cloneable
}
class Person implements Cloneable {
clone(): Cloneable {
return new Person() // different details
}
}
class Address {
constructor(
public street: string,
public city: string,
public country: string,
) {}
}
class Person {
constructor(
public name: string,
public address: Address,
) {}
}
class Address {
constructor(
public street: string,
public city: string,
public country: string,
) {}
}
class Person {
constructor(
public name: string,
public address: Address,
) {}
}
let john = new Person(
'John doe',
new Address('123 coding street', 'London', 'UK'),
)
let jane = new Person(
'Jane Doe',
new Address('123 coding street', 'London', 'UK'),
)
Potential duplication for all users
that live in London :"
interface Cloneable {
clone(): Cloneable
}
class Address {
constructor(
public street: string,
public city: string,
public country: string,
) {}
}
class Person implements Cloneable {
constructor(
public name: string,
public address: Address,
) {}
clone(): Person {
return new Person(this.name, this.address)
}
}
interface Cloneable {
clone(): Cloneable
}
class Address {/*...*/}
class Person implements Cloneable {
// ...
clone(): Person {
return new Person(this.name, this.address)
}
}
let john = new Person(
'John Doe',
new Address('123 coding st', 'London', 'UK'),
)
let jane = john.clone()
jane.name = 'Jane Doe'
jane.address.street = '123 London road'
console.log(john)
console.log(jane)
An inner object
which is passed by
reference
class Address implements Cloneable {
constructor(
public street: string,
public city: string,
public country: string,
) {}
clone(): Address {
return new Address(this.street, this.city, this.country)
}
}
class Person implements Cloneable {
constructor(
public name: string,
public address: Address,
) {}
clone(): Person {
return new Person(this.name, this.address.clone())
}
}
This approach is fine by now,
but if we have several objects nested
each object must implement the Cloneable
SERIALIZATION
Serialization
Serialization is the action of transform an object-like to another storable reprsentation like a string.
> Serializing your objects can be used for persisting them against your database or a log file, therefore it can be used for persisting your sessions into your database or queuing jobs.
class Address {
constructor(
public street: string,
public city: string,
public country: string,
) {}
}
class Person {
constructor(
public name: string,
public address: Address,
) {}
}
class Address {...}
class Person {...}
let john = new Person(
'John Doe',
new Address('123 coding street', 'London', 'UK'),
)
let jane = JSON.parse(JSON.stringify(john))
jane.name = 'Jane Doe'
jane.address.street = '123 London road'
console.log(john)
console.log(jane)
Jane loses the type Person
and the property address loses
the type Address
class Serializer {
constructor(
private types: { new (...args: any[]): any }[],
) {}
}
Storing an array of constructable objects
to serialize and clone
class Serializer {
constructor(
private types: { new (...args: any[]): any }[],
) {}
clone(object: object) {
this.mark(object)
let copy = JSON.parse(JSON.stringify(object))
return this.reconstruct(copy)
}
}
class Serializer {
private mark(object: Record<string, any>) {
let idx = this.types.findIndex(
(t) => t.name === object.constructor.name,
)
if (idx !== -1) {
object['typeIndex'] = idx
for (let k in object) {
if (object.hasOwnProperty(k)) {
this.mark(object[k])
}
}
}
}
clone(object: object) {
this.mark(object)
}
}
Applying this approach which includes any nested objects of other types
Checking if the registered types has
an object with the name constructor name
Marking each object with a special key that it has a type
class Serializer {
private reconstruct(object: Record<string, any>) {
if (object.hasOwnProperty('typeIndex')) {
let obj = new this.types[object.typeIndex]()
for (let key in object) {
if (
object.hasOwnProperty(key) &&
object[key] !== null
)
obj[key] = this.reconstruct(object[key])
}
delete obj.typeIndex
return obj
}
return object
}
}
If the object has the special key, it's newable
Recursively newing every nested object
Removing the special keys and returning the reconstructed object
Return the object untouched if the object has no special key
class Serializer {
constructor(
private types: { new (...args: any[]): any }[],
) {}
private reconstruct(object: Record<string, any>) {/*...*/}
private mark(object: Record<string, any>) {/*...*/}
clone(object: object) {
this.mark(object)
let copy = JSON.parse(JSON.stringify(object))
return this.reconstruct(copy)
}
}
import { Address } from './Address'
import { Person } from './Person'
import { Serializer } from './Serializer'
let john = new Person(
'John Doe',
new Address('123 coding street', 'London', 'UK'),
)
let s = new Serializer([Person, Address])
let jane = s.clone(john) as Person
jane.name = 'Jane Doe'
jane.address.street = '123 London road'
console.log(john.greet())
console.log(jane.toString())
console.log(jane.greet())
They are concerned about the assembly of classes and objects to build bigger structures
A better way to manage Object and Class composition
Providing your objects with more functionality through composing them with other objects.
Adapter
Bridge
Composite
Decorator
Facade
Flyweight
Private class data
Proxy
Intent Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate
Image from refactoring.guru
The consumer
of the Service
The contract which specifies what the client needs
Some service like mailing or caching
The adapter is a class that can work with both
the client and the service
by implementing the Client Interface and delegate to the service
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 {
constructor(private 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())
}
console.log(
'Client: I can work just fine with the Target objects:',
)
const target = new Target()
clientCode(target)
console.log('')
const adaptee = new Adaptee()
console.log(
"Client: The Adaptee class has a weird interface. See, I don't understand it:",
)
console.log(`Adaptee: ${adaptee.specificRequest()}`)
console.log('')
console.log(
'Client: But I can work with it via the Adapter:',
)
const adapter = new Adapter(adaptee)
clientCode(adapter)
Intent Bridge is a structural design pattern that lets you split a large class or a set of closely related classes into two separate hierarchies—abstraction and implementation—which can be developed independently of each other.
Image from refactoring.guru
The abstraction provides a high level control
The implementation defines the interface for common implementations
Concrete implementations contain
specific logic for some service
More like concrete
implementation of abstraction A
The client only uses the provided abstraction
class Abstraction {
protected implementation: Implementation
constructor(implementation: Implementation) {
this.implementation = implementation
}
public operation(): string {
const result =
this.implementation.operationImplementation()
return `Abstraction: Base operation with:\n${result}`
}
}
class ExtendedAbstraction extends Abstraction {
public operation(): string {
const result =
this.implementation.operationImplementation()
return `ExtendedAbstraction: Extended operation with:\n${result}`
}
}
interface Implementation {
operationImplementation(): string;
}
function clientCode(abstraction: Abstraction) {
console.log(abstraction.operation())
}
let implementation = new ConcreteImplementationA()
let abstraction = new Abstraction(implementation)
clientCode(abstraction)
console.log('')
implementation = new ConcreteImplementationB()
abstraction = new ExtendedAbstraction(implementation)
clientCode(abstraction)
Intent Composite is a structural design pattern that lets you compose objects into tree structures and then work with these structures as if they were individual objects.
Image from refactoring.guru
The client should be able
to work with Leaf as it does
with a Composite
Component describes how
each Leaf/Composite shall
behave
Leaf is a single element that has no sub-elements underneath it
Composite is the same as a Leaf, it adheres to the Component interface, but it can have multiple Leaves underneath it as well as having multiple Composites.
As the name suggests, a Composite is a composition of Leaves/Composites
export interface Component<T> {
execute(): string
}
abstract class Leaf<T> implements Component<T> {
execute(): string {
return 'hello from leaf'
}
}
export abstract class Composite<T> implements Component<T> {
private _children: Component<T>[] = []
public get children(): Component<T>[] {
return this._children
}
execute(): string {
return 'Hello from composite'
}
operation() {
this._children.forEach((child) => {
child.execute()
})
}
add(component: Component<T>) {
this._children.push(component)
}
remove(component: Component<T>) {
this._children.splice(
this._children.indexOf(component),
1,
)
}
}
export class Todo<T> extends Leaf<T> {}
export class Project<T> extends Composite<Todo<T>> {}
Intent Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.
Image from refactoring.guru
The common interface for both components and their decorators
The wrappable component that may need extra functionality
The client can wrap a component in multiple decorators to achieve more functional component
Provides the contract for all decorators to add extra behavior for our components
Variant extra functionalities that can be nested
interface Component {
operation(): string;
}
class ConcreteComponent implements Component {
public operation(): string {
return 'ConcreteComponent'
}
}
class Decorator implements Component {
protected component: Component
constructor(component: Component) {
this.component = component
}
public operation(): string {
return this.component.operation()
}
}
Can be an abstraction aswell
class ConcreteDecoratorA extends Decorator {
public operation(): string {
return `ConcreteDecoratorA(${super.operation()})`
}
}
class ConcreteDecoratorB extends Decorator {
public operation(): string {
return `ConcreteDecoratorB(${super.operation()})`
}
}
function clientCode(component: Component) {
console.log(`RESULT: ${component.operation()}`)
}
const simple = new ConcreteComponent()
console.log("Client: I've got a simple component:")
clientCode(simple)
console.log('')
const decorator1 = new ConcreteDecoratorA(simple)
const decorator2 = new ConcreteDecoratorB(decorator1)
console.log("Client: Now I've got a decorated component:")
clientCode(decorator2)
Intent Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.
Image from refactoring.guru
The facade produces
a suitable wrapper to handle subsystem functionality
Can be added to remove any complex functionality from the main facade, can be used by both the facade and the client
Some complex functionality like handling the Filesystem entity or some other entity that must be used altogether
The client uses the facade instead of the subsystem directly.
Note: The construction of a facade may
be convoluted, thus, other patterns can be used
class Facade {
constructor(
protected subsystem1: Subsystem1,
protected subsystem2: Subsystem2,
) {}
public operation(): string {
let result = 'Facade initializes subsystems:\n'
result += this.subsystem1.operation1()
result += this.subsystem2.operation1()
result +=
'Facade orders subsystems to perform the action:\n'
result += this.subsystem1.operationN()
result += this.subsystem2.operationZ()
return result
}
}
class Subsystem1 {
public operation1(): string {
return 'Subsystem1: Ready!\n'
}
// ...
public operationN(): string {
return 'Subsystem1: Go!\n'
}
}
class Subsystem2 {
public operation1(): string {
return 'Subsystem2: Get ready!\n'
}
// ...
public operationZ(): string {
return 'Subsystem2: Fire!'
}
}
function clientCode(facade: Facade) {
// ...
console.log(facade.operation())
// ...
}
const subsystem1 = new Subsystem1()
const subsystem2 = new Subsystem2()
const facade = new Facade(subsystem1, subsystem2)
clientCode(facade)
Intent Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of the state between multiple objects instead of keeping all of the data in each object
Image from refactoring.guru
The class we want to create multiple instances of
The class we want to create multiple instances of
The class we want to create multiple instances of
The class we want to create multiple instances of
class Flyweight {
private sharedState: any
constructor(sharedState: any) {
this.sharedState = sharedState
}
public operation(uniqueState): void {
const s = JSON.stringify(this.sharedState)
const u = JSON.stringify(uniqueState)
console.log(
`Flyweight: Displaying shared (${s}) and unique (${u}) state.`,
)
}
}
class FlyweightFactory {
private flyweights: { [key: string]: Flyweight } = <any>{}
constructor(initialFlyweights: string[][]) {
for (const state of initialFlyweights) {
this.flyweights[this.getKey(state)] = new Flyweight(
state,
)
}
}
private getKey(state: string[]): string {
return state.join('_')
}
}
class FlyweightFactory {
/* Previous logic */
public getFlyweight(sharedState: string[]): Flyweight {
const key = this.getKey(sharedState)
if (!(key in this.flyweights)) {
console.log(
"FlyweightFactory: Can't find a flyweight, creating new one.",
)
this.flyweights[key] = new Flyweight(sharedState)
}
return this.flyweights[key]
}
}
class FlyweightFactory {
/* Previous logic */
public listFlyweights(): void {
const count = Object.keys(this.flyweights).length
console.log(
`\nFlyweightFactory: I have ${count} flyweights:`,
)
for (const key in this.flyweights) {
console.log(key)
}
}
}
Intent Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object.
Image from refactoring.guru
Some contract for service,
maybe a payment gateway
and you can have multiple concretes out of it
The actual concrete
implementation of the service
contract, maybe Stripe API
The proxy that provides the
same API code as the concrete
Service but provides different
behaviour that suits your need
The client should be using
the proxy as if it was the service
without any difference in his
application work flow
interface Subject {
request(): void;
}
class RealSubject implements Subject {
public request(): void {
console.log('RealSubject: Handling request.')
}
}
class Proxy implements Subject {
private realSubject: RealSubject
constructor(realSubject: RealSubject) {
this.realSubject = realSubject
}
public request(): void {
if (this.checkAccess()) {
this.realSubject.request()
this.logAccess()
}
}
private checkAccess(): boolean {
console.log(
'Proxy: Checking access prior to firing a real request.',
)
return true
}
private logAccess(): void {
console.log('Proxy: Logging the time of request.')
}
}
function clientCode(subject: Subject) {
// ...
subject.request()
// ...
}
console.log(
'Client: Executing the client code with a real subject:',
)
const realSubject = new RealSubject()
clientCode(realSubject)
console.log('')
console.log(
'Client: Executing the same client code with a proxy:',
)
const proxy = new Proxy(realSubject)
clientCode(proxy)
Adapter
Facade
Proxy
Bridge
Decorator
Builts a communication channel between two different interfaces
Provides a way of modifying the behaviour that an object does.
Provides a communication channel between different abstractions
Builts a communication channel between two different interfaces
Provides a simple/single interface to cover up for a whole mess of decoupled system
Builts a communication channel between two different interfaces
Provides the same interface of some functionality with additional/less functionality or different accessibility levels
Chain of resp
Command
Interpreter
Iterator
Mediator
Memento
Observer
Strategy
State
Temp Method
Visitor
Intent Chain of Responsibility is a behavioural design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
Image from refactoring.guru
Defines an interface for handling a request.
Maintains a reference (successor) to the next Handler object on the chain.
The client wants to perform some tasks via composing multiple sub-tasks
Multiple handlers to process the request/code in several steps.
Base handler is optional but, if existed, it provides the skeleton for handling some logic.
interface Handler {
setNext(handler: Handler): Handler
handle(request: string): string | null
}
abstract class AbstractHandler implements Handler {
private nextHandler!: Handler
public setNext(handler: Handler): Handler {
this.nextHandler = handler
return handler
}
public handle(request: string): string | null {
if (this.nextHandler) {
return this.nextHandler.handle(request)
}
return null
}
}
class SquirrelHandler extends AbstractHandler {
public handle(request: string): string | null {
if (request === 'Nut') {
return `Squirrel: I'll eat the ${request}.`
}
return super.handle(request)
}
}
class DogHandler extends AbstractHandler {
public handle(request: string): string | null {
if (request === 'MeatBall') {
return `Dog: I'll eat the ${request}.`
}
return super.handle(request)
}
}
class SquirrelHandler extends AbstractHandler {
public handle(request: string): string | null {
if (request === 'Nut') {
return `Squirrel: I'll eat the ${request}.`
}
return super.handle(request)
}
}
function clientCode(handler: Handler) {
const foods = ['Nut', 'Banana', 'Cup of coffee']
for (const food of foods) {
console.log(`Client: Who wants a ${food}?`)
const result = handler.handle(food)
if (result) {
console.log(` ${result}`)
} else {
console.log(` ${food} was left untouched.`)
}
}
}
const monkey = new MonkeyHandler()
const squirrel = new SquirrelHandler()
const dog = new DogHandler()
monkey.setNext(squirrel).setNext(dog)
console.log('Chain: Monkey > Squirrel > Dog\n')
clientCode(monkey)
console.log('')
console.log('Subchain: Squirrel > Dog\n')
clientCode(squirrel)
Intent Command is a behavioral design pattern that turns a request into a stand-alone object that contains all information about the request. This transformation lets you pass requests as a method arguments, delay or queue a request’s execution, and support undoable operations.
Image from refactoring.guru
The invoker is like the
remote controller or the Commander
The entity that's responsible for sending commmands
The command is the contract that all our concretes must adhere to in order to have unified API
Obviously, concrete implementations, a.k.a actual commnds
The receiver is what we're operating our commands on
interface Command {
execute(): void
}
class SimpleCommand implements Command {
private payload: string
constructor(payload: string) {
this.payload = payload
}
public execute(): void {
console.log(
`SimpleCommand: See, I can do simple things like printing (${this.payload})`,
)
}
}
class ComplexCommand implements Command {
constructor(
private receiver: Receiver,
private a: string,
private b: string,
) {
this.receiver = receiver
this.a = a
this.b = b
}
public execute(): void {
console.log(
'ComplexCommand: Complex stuff should be done by a receiver object.',
)
this.receiver.doSomething(this.a)
this.receiver.doSomethingElse(this.b)
}
}
class Receiver {
public doSomething(a: string): void {
console.log(`Receiver: Working on (${a}.)`)
}
public doSomethingElse(b: string): void {
console.log(`Receiver: Also working on (${b}.)`)
}
}
class Invoker {
private onStart: Command
private onFinish: Command
public setOnStart(command: Command): void {
this.onStart = command
}
public setOnFinish(command: Command): void {
this.onFinish = command
}
private isCommand(object): object is Command {
return object.execute !== undefined
}
}
class Invoker {
/* previous logic */
public doSomethingImportant(): void {
console.log(
'Invoker: Does anybody want something done before I begin?',
)
if (this.isCommand(this.onStart)) {
this.onStart.execute()
}
console.log(
'Invoker: ...doing something really important...',
)
console.log(
'Invoker: Does anybody want something done after I finish?',
)
if (this.isCommand(this.onFinish)) {
this.onFinish.execute()
}
}
}
const invoker = new Invoker()
invoker.setOnStart(new SimpleCommand('Say Hi!'))
const receiver = new Receiver()
invoker.setOnFinish(
new ComplexCommand(receiver, 'Send email', 'Save report'),
)
invoker.doSomethingImportant()
Intent Iterator is a behavioral design pattern that lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).
Image from refactoring.guru
Defines the contract for all iterators, whether they support backward iterations, resetting cursor, and other operations
The iterator implementation itself, keeps track of the current position in the traversal of the aggregation.
Also known as the Aggregator
Defines the contract for creating iterator objects.
Implements the Aggregate/IterableCollection contract to return some specific iterator.
interface Iterator<T> {
current(): T
next(): T
key(): number
valid(): boolean
rewind(): void
}
interface Aggregator {
getIterator(): Iterator<string>
}
interface IterableCollection {
getIterator(): Iterator<string>
}
export class WordsCollection implements Aggregator {
private words: string[] = []
getIterator(): Iterator<string> {
return new AlphabetIterator(this)
}
getCount(): number {
return this.words.length
}
getItems(): string[] {
return this.words
}
addWord(word: string): void {
this.words.push(word)
}
getReverseIterator(): Iterator<string> {
return new AlphabetIterator(this, true)
}
}
export class AlphabetIterator implements Iterator<string> {
private position: number = 0
constructor(
private collection: WordsCollection,
private reverse: boolean = false,
) {
if (reverse) {
this.position = collection.getCount() - 1
}
}
current(): string {...}
next(): string {...}
key(): number {...}
valid(): boolean {...}
rewind(): void {...}
}
Intent Observer is a behavioural design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.
Image from refactoring.guru
Also known as the Subject or Observable, it defines the thing we subscribe to or observe
The observer itself that waits for actions from whatever it subscribes to
The implementations of our subscriber a.k.a observer.
interface Observable {
// Attach an observer to the subject.
attach(observer: Observer): void
// Detach an observer from the subject.
detach(observer: Observer): void
// Notify all observers about an event.
notify(): void
}
/**
* The Observer interface declares the update method, used by subjects.
*/
interface Observer {
// Receive update from subject.
update(observable: Observable): void
}
class ConcreteObserverA implements Observer {
public update(subject: Subject): void {
if (
subject instanceof ConcreteSubject &&
subject.state < 3
) {
console.log(
'ConcreteObserverA: Reacted to the event.',
)
}
}
}
class ConcreteObserverB implements Observer {
public update(subject: Subject): void {
if (
subject instanceof ConcreteSubject &&
(subject.state === 0 || subject.state >= 2)
) {
console.log(
'ConcreteObserverB: Reacted to the event.',
)
}
}
}
class ConcreteObservable implements Observable {
public state: number
private observers: Observer[] = []
public attach(observer: Observer): void {
const isExist = this.observers.includes(observer)
if (isExist) {
return console.log(
'Subject: Observer has been attached already.',
)
}
console.log('Subject: Attached an observer.')
this.observers.push(observer)
}
}
class ConcreteObservable implements Observable {
// previous implementation
public detach(observer: Observer): void {
const observerIndex = this.observers.indexOf(observer)
if (observerIndex === -1) {
return console.log('Subject: Nonexistent observer.')
}
this.observers.splice(observerIndex, 1)
console.log('Subject: Detached an observer.')
}
}
class ConcreteObservable implements Observable {
// previous implementation
public notify(): void {
console.log('Subject: Notifying observers...')
for (const observer of this.observers) {
observer.update(this)
}
}
}
class ConcreteObservable implements Observable {
// previous implementation
public someBusinessLogic(): void {
console.log("\nSubject: I'm doing something important.")
this.state = Math.floor(Math.random() * (10 + 1))
console.log(
`Subject: My state has just changed to: ${this.state}`,
)
this.notify()
}
}
// client code
const subject = new ConcreteSubject()
const observer1 = new ConcreteObserverA()
subject.attach(observer1)
const observer2 = new ConcreteObserverB()
subject.attach(observer2)
subject.someBusinessLogic()
subject.someBusinessLogic()
subject.detach(observer2)
subject.someBusinessLogic()
Intent State is a behavioural design pattern that lets an object alter its behaviour when its internal state changes. It appears as if the object changed its class.
Image from refactoring.guru
The API expects to have variant states and act upon these variations.
It accepts a State implementation, maybe it's initialState, however, it delegates its work to it
The contract that defines how your state will be represented, it must have the same methods your Context wants to have delegated
The concrete implementations of your state contract, which defines how your class will behave due to the current state
class Context {
constructor(private state: State) {
this.transitionTo(state)
}
public transitionTo(state: State): void {
console.log(
`Context: Transition to ${
(<any>state).constructor.name
}.`,
)
this.state = state
this.state.setContext(this)
}
public request1(): void {
this.state.handle1()
}
public request2(): void {
this.state.handle2()
}
}
abstract class State {
protected context!: Context
public setContext(context: Context) {
this.context = context
}
public abstract handle1(): void
public abstract handle2(): void
}
class ConcreteStateA extends State {
public handle1(): void {
console.log('ConcreteStateA handles request1.')
console.log(
'ConcreteStateA wants to change the state of the context.',
)
this.context.transitionTo(new ConcreteStateB())
}
public handle2(): void {
console.log('ConcreteStateA handles request2.')
}
}
class ConcreteStateB extends State {
public handle1(): void {
console.log('ConcreteStateB handles request1.')
}
public handle2(): void {
console.log('ConcreteStateB handles request2.')
console.log(
'ConcreteStateB wants to change the state of the context.',
)
this.context.transitionTo(new ConcreteStateA())
}
}
/* client code */
const context = new Context(new ConcreteStateA())
context.request1()
context.request2()
Intent Strategy is a behavioural design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.
Image from refactoring.guru
The class which will make use of one strategy, can be thought of as the client code that uses the exported strategy via some API code.
The contract that all our strategies have to adhere to
The actual strategies we'll use during the runtime
class Context {
private strategy: Strategy
constructor(strategy: Strategy) {
this.strategy = strategy
}
public setStrategy(strategy: Strategy) {
this.strategy = strategy
}
public doSomeBusinessLogic(list: string[]): void {
console.log(
"Context: Sorting data using the strategy (not sure how it'll do it)",
)
const result = this.strategy.doAlgorithm(list)
console.log(result.join(','))
}
}
interface Strategy {
doAlgorithm(data: string[]): string[]
}
class ConcreteStrategyA implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.sort()
}
}
class ConcreteStrategyB implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.reverse()
}
}
const context = new Context(new ConcreteStrategyA())
console.log('Client: Strategy is set to normal sorting.')
context.doSomeBusinessLogic(['c', 'd', 'b', 'e', 'a'])
console.log('')
console.log('Client: Strategy is set to reverse sorting.')
context.setStrategy(new ConcreteStrategyB())
context.doSomeBusinessLogic(['a', 'b', 'c', 'd', 'e'])
Raise up your techinal skills
By Security Theater
A theoretical walkthrough on how to design your application in a clean way using the SOLID principles and Design patterns and practical implementation in PHP (Laravel), JavaScript (Node) and GoLang.
SecTheater is an online teaching community that targets the IT department. We do our best to produce for you high quality and well-edited screen casts about web development.