Observer Pattern
Video Lecture
Overview
... To read hidden text, either pause Video Lectures, refer to Book or subscribe to Medium Membership.
Terminology
... To read hidden text, either pause Video Lectures, refer to Book or subscribe to Medium Membership.
Observer UML Diagram

Source Code
... To read hidden text, either pause Video Lectures, refer to Book or subscribe to Medium Membership.
./src/observer/observer-concept.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 | // Observer Design Pattern Concept
interface IObservable {
// The Subject Interface
subscribe(observer: IObserver): void
// The subscribe method
unsubscribe(observer: IObserver): void
// The unsubscribe method
notify(...args: unknown[]): void
// The notify method
}
class Subject implements IObservable {
// The Subject (a.k.a Observable)
#observers: Set<IObserver>
constructor() {
this.#observers = new Set()
}
subscribe(observer: IObserver) {
this.#observers.add(observer)
}
unsubscribe(observer: IObserver) {
this.#observers.delete(observer)
}
notify(...args: unknown[]) {
this.#observers.forEach((observer) => {
observer.notify(...args)
})
}
}
interface IObserver {
// A method for the Observer to implement
notify(...args: unknown[]): void
// Receive notifications"
}
class Observer implements IObserver {
// The concrete observer
#id: number
constructor(observable: IObservable) {
this.#id = COUNTER++
observable.subscribe(this)
}
notify(...args: unknown[]) {
console.log(
`OBSERVER_${this.#id} received ${JSON.stringify(args)}`
)
}
}
// The Client
let COUNTER = 1 // An ID to help distinguish between objects
const SUBJECT = new Subject()
const OBSERVER_1 = new Observer(SUBJECT)
const OBSERVER_2 = new Observer(SUBJECT)
SUBJECT.notify('First Notification', [1, 2, 3])
// Unsubscribe OBSERVER_2
SUBJECT.unsubscribe(OBSERVER_2)
SUBJECT.notify('Second Notification', { A: 1, B: 2, C: 3 })
|
Output
node ./dist/observer/observer-concept.js
OBSERVER_1 received ["First Notification",[1,2,3]]
OBSERVER_2 received ["First Notification",[1,2,3]]
OBSERVER_1 received ["Second Notification",{"A":1,"B":2,"C":3}]
Observer Use Case
... To read hidden text, either pause Video Lectures, refer to Book or subscribe to Medium Membership.
Example UML Diagram

Source Code
./src/observer/client.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | // Observer Design Pattern Concept
import { DataController } from './data-controller'
import { DataModel } from './data-model'
import { BarGraphView, PieGraphView, TableView } from './data-view'
// A local data view that the hypothetical external controller updates
const DATA_MODEL = new DataModel()
// Add some visualisation that use the dataview
const PIE_GRAPH_VIEW = new PieGraphView(DATA_MODEL)
const BAR_GRAPH_VIEW = new BarGraphView(DATA_MODEL)
const TABLE_VIEW = new TableView(DATA_MODEL)
// A hypothetical data controller running in a different process
const DATA_CONTROLLER = new DataController() // (Singleton)
// The hypothetical external data controller updates some data
DATA_CONTROLLER.notify([1, 2, 3])
// Client now removes a local BAR_GRAPH
BAR_GRAPH_VIEW.delete()
// The hypothetical external data controller updates the data again
DATA_CONTROLLER.notify([4, 5, 6])
|
./src/observer/data-view.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 | import { IDataModel } from './data-model'
export interface IDataView {
// A Subject Interface
notify(data: number[]): void
draw(data: number[]): void
delete(): void
}
export class BarGraphView implements IDataView {
// A concrete observer
#observable: IDataModel
#id: number
constructor(observable: IDataModel) {
this.#observable = observable
this.#id = this.#observable.subscribe(this)
}
notify(data: number[]): void {
console.log(`BarGraph, id:${this.#id}`)
this.draw(data)
}
draw(data: number[]): void {
console.log(
`Drawing a Bar graph using data:${JSON.stringify(data)}`
)
}
delete(): void {
this.#observable.unsubscribe(this.#id)
}
}
export class PieGraphView implements IDataView {
// A concrete observer
#observable: IDataModel
#id: number
constructor(observable: IDataModel) {
this.#observable = observable
this.#id = this.#observable.subscribe(this)
}
notify(data: number[]): void {
console.log(`PieGraph, id:${this.#id}`)
this.draw(data)
}
draw(data: number[]): void {
console.log(`Drawing a Pie graph using data:${data}`)
}
delete(): void {
this.#observable.unsubscribe(this.#id)
}
}
export class TableView implements IDataView {
// A concrete observer
#observable: IDataModel
#id: number
constructor(observable: IDataModel) {
this.#observable = observable
this.#id = this.#observable.subscribe(this)
}
notify(data: number[]): void {
console.log(`TableView, id:${this.#id}`)
this.draw(data)
}
draw(data: number[]): void {
console.log(`Drawing a Table using data:${JSON.stringify(data)}`)
}
delete(): void {
this.#observable.unsubscribe(this.#id)
}
}
|
./src/observer/data-model.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 | import { IDataController, DataController } from './data-controller'
import { IDataView } from './data-view'
export interface IDataModel {
// A Subject Interface
subscribe(observer: IDataView): number
unsubscribe(observerId: number): void
notify(data: number[]): void
}
export class DataModel implements IDataModel {
// A Subject (a.k.a Observable)
#observers: { [id: number]: IDataView } = {}
#dataController: IDataController
#counter: number
constructor() {
this.#counter = 0
this.#dataController = new DataController()
this.#dataController.subscribe(this)
}
subscribe(observer: IDataView): number {
this.#counter++
this.#observers[this.#counter] = observer
return this.#counter
}
unsubscribe(observerId: number): void {
delete this.#observers[observerId]
}
notify(data: number[]): void {
Object.keys(this.#observers).forEach((observer) => {
this.#observers[parseInt(observer)].notify(data)
})
}
}
|
./src/observer/data-controller.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | import { IDataModel } from './data-model'
// A Data Controller Interface
export interface IDataController {
// A Subject Interface
subscribe(observer: IDataModel): void
unsubscribe(observer: IDataModel): void
notify(data: number[]): void
}
export class DataController implements IDataController {
// A Subject (a.k.a Observable)
static instance: DataController
#observers: Set<IDataModel> = new Set()
constructor() {
if (DataController.instance) {
return DataController.instance
}
DataController.instance = this
}
subscribe(observer: IDataModel): void {
this.#observers.add(observer)
}
unsubscribe(observer: IDataModel): void {
this.#observers.delete(observer)
}
notify(data: number[]): void {
this.#observers.forEach((observer) => {
observer.notify(data)
})
}
}
|
Output
node ./dist/observer/client.js
PieGraph, id:1
Drawing a Pie graph using data:1,2,3
BarGraph, id:2
Drawing a Bar graph using data:[1,2,3]
TableView, id:3
Drawing a Table using data:[1,2,3]
PieGraph, id:1
Drawing a Pie graph using data:4,5,6
TableView, id:3
Drawing a Table using data:[4,5,6]
Summary
... To read hidden text, either pause Video Lectures, refer to Book or subscribe to Medium Membership.