---
# System prepended metadata

title: JavaScript Design Patterns
tags: [javascript, design patterns]

---

###### tags: `javascript` `design patterns`

# JavaScript Design Patterns

## 前言： Concepts behind "javascript is a prototype-based language"
- 原型程式設計 - [wiki](https://zh.wikipedia.org/wiki/%E5%8E%9F%E5%9E%8B%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88)
- 深入了解物件模型 - [MDN](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Details_of_the_Object_Model)
- 該來理解 JavaScript 的原型鍊了 - [techbridge](https://blog.techbridge.cc/2017/04/22/javascript-prototype/)
- JavaScript 中的「繼承」 - [MDN](https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Objects/Inheritance)
- What does it mean that Javascript is a prototype-based language? - [stackoverflow](https://stackoverflow.com/questions/186244/what-does-it-mean-that-javascript-is-a-prototype-based-language)
- 世界上有哪些 [prototype-based languages](https://en.wikipedia.org/wiki/List_of_programming_languages_by_type#Object-oriented_prototype-based_languages)？
- 世界上有哪些 [class-based languages](https://en.wikipedia.org/wiki/List_of_programming_languages_by_type#Object-oriented_class-based_languages)？

## Quick review "inheritance" in JavaScript



**實例**(instance)、**類別**(class)，其實是 **class-based 的物件導向語言** (e.g. Java 和 C++) 所使用且有明確定義的術語。

**但 javascript (JS) 是 prototype-based 的語言，並沒有這些區別。** 在 JS 的世界裡，**所有物件都是實例**。

又**建構子**(constructor) 也是 class-based 的物件導向語言中，指用來**創建該 class 實例的特殊方法**；**繼承** (inheritance) 則是**物件導向程式設計**裡最重要的三大特色之一。

在 JS 的世界，**函式** (function) 本身也是一種物件，且我們**可以直接將函式充當建構子**、定義物件的屬性與初始值；而 JS 要達成繼承的目的，得靠所謂的**原型鍊** (prototype chain) 機制來實現

:::info
:information_source: 為何 programmer 想要「繼承」的機制？

繼承能夠幫助我們達到更好的「代碼重用」和「可擴展性」的需求，為物件導向程式設計 (OOP) 的特徵之一

而物件導向程式設計的總目標就是：提高軟體的重用性、靈活性和擴充性
:::

### Inheritance by prototype chain

要討論繼承的概念，首先要有個 **bass class**。

下例中的 `Person` 函式物件就扮演著 bass class 的角色。而此 function 被直接當作 `Person` 的建構子來用。函式內的 `this` 指實例化後的物件本身 (i.e. 12 行的 `p`)：
```javascript=
function Person (first, last, age, gender, interests) {
  this.name = {
    first,
    last
  }
  this.age = age
  this.gender = gender
  this.interests = interests
}

const p = new Person(
  'Max', 'Cian', 30, 'male', ['Javascript', 'Python', 'Golang'])

console.log(p)
// Person {
//   name: { first: 'Max', last: 'Cian' },
//   age: 30,
//   gender: 'male',
//   interests: [ 'Javascript', 'Python', 'Golang' ] }
```
> 這就是後述的第一個 pattern：**Constructor Pattern**

另外，我們在 class-based language 裡還可能替 class 添加 method。但如何在 `Person` 中添加**方法**呢？

此時，需要使用 Function Object 中的 `prototype`，對它做擴充：
```javascript=21
Person.prototype.greeting = function () {
  console.log(`Hi! I'm ${this.name.first}.`)
}

p.greeting()
// Hi! I'm Max.
```

:::info
:information_source: 為何不在建構子裡寫 method？
若直接寫 function 在建構子裡，未來每一個 person，都會有自己一份獨一無二的 function。

但寫 function 有一個目的叫 code reuse，不只是 programmer 少寫 code，也希望此 function 定義完後在記憶體內也能共享。

利用此法擴充的 methods 才不會因創建多個 Person 而重複佔用記憶體。
:::

接下來，**我們有個 `Teacher`，想要繼承自 `Person`**：
:::success
:green_apple: 所以**繼承**的目的是？

我們希望 Teacher 也跟 Person 一樣擁有 name, age, gender 等屬性（但我不想重複寫 code）、且我還想基於 Person，新增 subject 的屬性給 Teacher。
:::

```javascript=27
function Teacher (first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests) // Key 1
  this.subject = subject
}

Teacher.prototype = Object.create(Person.prototype) // Key 2

const teacher = new Teacher(
  'Max', 'Cian', 30, 'male', ['Javascript', 'Python', 'Golang'],
  'Object-Oriented Programming of Javascript')

console.log(`teacher's name: ${teacher.name.first} ${teacher.name.last}`)
console.log(`teacher's age: ${teacher.age}`)
console.log(`teacher's gender: ${teacher.gender}`)
console.log(`teacher's interests: ${teacher.interests}`)
console.log(`teacher's subject: ${teacher.subject}`)
// teacher's name: Max Cian
// teacher's age: 30
// teacher's gender: male
// teacher's interests: Javascript,Python,Golang
// teacher's subject: Object-Oriented Programming of Javascript

teacher.greeting() // Key 3
// Hi! I'm Max.
```
**Key 1**
- 透過 Function Object 的 `call()`，重新呼叫該函數
- 然後將 Teacher 的 `this` 放在第一個參數傳給 Person.call()，這樣呼叫 Person 建構子時內部的 this 就是 teacher 的 this
- 接著依序將 Person 建構子所需的參數 first, last, age, gender, interests 傳進去做初始化
- 此時 Teacher 就擁有 Person 所有屬性以及自己才有的 subject

**Key 2**
- 此步就是利用**原型鍊**機制，使用 Object.create()，創建一個 Person.prototype 的物件，並只派給 Teacher.prototype，就完成繼承了

**Key 3**
- 完成繼承之後我們呼叫 teacher.greeting()
- 此時會因為在 Teacher 中找不到，而去找 Teacher.prototype 裡有沒有 greeting，然後就在 Person.prototype 中找到 greeting 了

最後，如果我們想要更改 greeting()，Teacher 有自己的打招呼方式，一樣透過擴充 Teacher.prototype 來達成：

```javascript=51
Teacher.prototype.greeting = function () {
  let prefix

  if (this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
    prefix = 'Mr.'
  } else if (this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
    prefix = 'Ms.'
  } else {
    prefix = 'Mx.'
  }

  console.log(`Hello, My name is ${prefix} ${this.name.last}, and I teach ${this.subject}.`)
}

teacher.greeting()
// Hello, My name is Mr. Cian, and I teach Object-Oriented Programming of Javascript.
```

## Gentle introduction of JavaScript Design Patterns
> 本週分配的文章是 [Top 10 JavaScript Patterns Every Developer Likes](https://dev.to/shijiezhou/top-10-javascript-patterns-every-developers-like-168p)，但這篇其實是不老實的中國人求職前製作的履歷門面文章，文中談論的 10 個 patterns 皆**斷章取義**自著作－[Learning JavaScript Design Patterns](https://addyosmani.com/resources/essentialjsdesignpatterns/book/), a O'Reilly book by Addy Osmani
> 應直接看原作，看不懂的話拿關鍵字餵 google

### 1. The Constructor Pattern
- 見前節所述，JS 的世界裡直接使用函式來建構物件實體

### 2. The Module Pattern
- [Day06 - 常見的 JS 模組模式 (Pattern)](https://ithelp.ithome.com.tw/m/articles/10215129)
- JS 並不像其他程式語言有內建命名空間 (namespace) 的語法，但你還是可以透過結構化的方式組織你的程式碼達到類似的效果
    - 亦可參考 [namespacing patterns](https://addyosmani.com/resources/essentialjsdesignpatterns/book/#detailnamespacing)
- 利用 ***closure*** 的特性，將**函式**和**變數**限制在一個範疇內存取與使用，且也製作出一個 namespace 的概念

Example
```javascript=
const CarController = (() => {
  let carSpeed = 0
  return {
    speedUp: (speed) => { carSpeed += speed },
    slowDown: (speed) => { carSpeed -= speed },
    checkSpeed: () => carSpeed
  }
})()

console.log(CarController.checkSpeed())
CarController.speedUp(100)
console.log(CarController.checkSpeed())
CarController.slowDown(33)
console.log(CarController.checkSpeed())
```

### 3. The Revealing Module Pattern
- [Day06 - 常見的 JS 模組模式 (Pattern)](https://ithelp.ithome.com.tw/m/articles/10215129)
- Module Pattern 的進化改良版
- 基於 module pattern，內部可能有些複雜了，為了提高可讀性，選擇在最後的時候才決定哪些函式是應該被揭露出來
- 其實有點遵循 [Interface segregation principle](https://en.wikipedia.org/wiki/Interface_segregation_principle) 的感覺了

Example
```javascript=
const CarController = (() => {
  let carSpeed = 0
  function NitrogenAcceleration (speed) {
    carSpeed += speed
  }

  function LockFrontWheel (speed) {
    carSpeed -= speed
  }
  function LockReerWheel (speed) {
    carSpeed -= speed
  }
  function LockWheels (speed) {
    LockFrontWheel(speed * 0.6)
    LockReerWheel(speed * 0.4)
  }

  return {
    speedUp: NitrogenAcceleration,
    slowDown: LockWheels,
    checkSpeed: () => carSpeed
  }
})()

console.log(CarController.checkSpeed())
CarController.speedUp(100)
console.log(CarController.checkSpeed())
CarController.slowDown(33)
console.log(CarController.checkSpeed())
```

### 4. The Singleton Pattern
- [GoF Design Patterns](https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F_(%E8%AE%A1%E7%AE%97%E6%9C%BA)) 中的一種
- 我們總是有需要延遲實例化全域物件的需求，原因可能是某些參數得在 runtime 時才能得知
- singleton pattern 指的就是利用程式語言的特性製作出一個經過實例化之後、就永遠是唯一存在的實例

Conventional construct an "instance"
```javascript=
function Car (model, year, miles) {
  this.model = model
  this.year = year
  this.miles = miles
}

let car = new Car('M4', '2019', '1000')
console.log(car) // Car { model: 'M4', year: '2019', miles: '1000' }
car = new Car('M4', '2030', '12345')
console.log(car) // Car { model: 'M4', year: '2030', miles: '12345' }
```

Singleton "instance"
```javascript=
const SingletonCar = (() => {
  let car
  const init = (model, year, miles) => new Car(model, year, miles)
  return {
    getCar: (model, year, miles) => {
      if (car) {
        console.log('Car was created, return the old')
        return car
      }
      console.log('Lazy create a new car')
      car = init(model, year, miles)
      return car
    }
  }
})()

let car = SingletonCar.getCar('M4', '2019', '1000')
console.log(car) // Car { model: 'M4', year: '2019', miles: '1000' }
car = SingletonCar.getCar('M4', '2030', '12345')
console.log(car) // Car { model: 'M4', year: '2019', miles: '1000' }
```

### 5. The Observer Pattern
- [GoF Design Patterns](https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F_(%E8%AE%A1%E7%AE%97%E6%9C%BA)) 中的一種
- aka. **Pub**lication/**Sub**scription **Pattern**
- 實作一個 Subject 物件，它提供其他物件「訂閱」收到特定事件時，要「通知」它們
- 可參考 [JavaScript Observer Pattern](https://www.dofactory.com/javascript/design-patterns/observer) 的解釋與實作
- 此模式能夠**降低程式碼之間的耦合性**、同時也讓程式碼更加物件導向

```javascript=
function CelloNews () {
  this.handlers = [] // observers
}

CelloNews.prototype = {

  subscribe: function (fn) {
    this.handlers.push(fn)
  },

  unsubscribe: function (fn) {
    this.handlers = this.handlers.filter(item => item !== fn)
  },

  notify: function (args) {
    this.handlers.forEach(fn => fn(args))
  }
}

function MaxCian (args) {
  console.log(`[MaxCian] got message: ${args}`)
}

function JohnLee (args) {
  console.log(`[JohnLee] got message: ${args}`)
}

function HHTu (args) {
  console.log(`[HHTu] got message: ${args}`)
}

const cello = new CelloNews()

cello.subscribe(MaxCian)
cello.subscribe(JohnLee)
cello.notify('台塑家教專案上課啦')
// [MaxCian] got message: 台塑家教專案上課啦
// [JohnLee] got message: 台塑家教專案上課啦
console.log()

cello.unsubscribe(JohnLee)

cello.notify('吃飯啦')
// [MaxCian] got message: 吃飯啦
console.log()

cello.subscribe(HHTu)
cello.notify('爬山啦')
// [MaxCian] got message: 爬山啦
// [HHTu] got message: 爬山啦
```

### 6. The Mediator Pattern
- [GoF Design Patterns](https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F_(%E8%AE%A1%E7%AE%97%E6%9C%BA)) 中的一種
- 此模式指在需要交互通信、彼此依賴彼此的狀態的物件之間，封裝一個「中介者」，藉此減少物件之間的依賴、**降低耦合性**
- 例如飛機降落或起飛，是彼此依賴的關係
    - 你可以選擇讓機師直接溝通來協調跑到，但這就是物件之間有高度耦合的情況
    - 或是由「塔台」作為「中介者」，來掌握所有飛機起降的時機
- 可參考 [JavaScript Mediator Pattern](https://www.dofactory.com/javascript/design-patterns/mediator) 的解釋與實作

```javascript=
function Participant (name) {
  this.name = name
  this.chatroom = null
}

Participant.prototype = {
  send: function (message, to) {
    this.chatroom.send(message, this, to)
  },
  receive: function (message, from) {
    console.log(`[${from.name}]->[${this.name}]: ${message}`)
  }
}

function Chatroom () {
  const participants = {}

  return {

    register: function (participant) {
      participants[participant.name] = participant
      participant.chatroom = this
    },

    send: function (message, from, to) {
      if (to) { // single message
        to.receive(message, from)
      } else { // broadcast message
        for (const key in participants) {
          if (participants[key] !== from) {
            participants[key].receive(message, from)
          }
        }
      }
    }
  }
}

const chatroom = new Chatroom()
const maxcian = new Participant('Max Cian')
const johnlee = new Participant('John Lee')
const hhtu = new Participant('HH Tu')
chatroom.register(maxcian)
chatroom.register(johnlee)
chatroom.register(hhtu)

maxcian.send('要不要吃肥宅快樂餐', johnlee)
// [Max Cian]->[John Lee]: 要不要吃肥宅快樂餐
console.log()
johnlee.send('我就怕被罵咩', hhtu)
// [John Lee]->[HH Tu]: 我就怕被罵咩
console.log()
hhtu.send('開會囉')
// [HH Tu]->[Max Cian]: 開會囉
// [HH Tu]->[John Lee]: 開會囉
```

### 7. The Prototype Pattern
- [GoF Design Patterns](https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F_(%E8%AE%A1%E7%AE%97%E6%9C%BA)) 中的一種
-  關鍵觀念為：創建一個物件，不是從無開始實例化；而是從已存在的實體複製而來
- JS 作為 prototype based 的語言，當使用 `new` 與 `Object.prototype` 等語法時，底層其實就是根據此設計理念來實現
- [The Prototype Pattern in JavaScript](https://medium.com/better-programming/the-prototype-pattern-in-javascript-bfe9ff433e6c): 此篇展示了 JS 底層哪些語法使用了 prototype pattern 
- [JavaScript Prototype Pattern](https://www.dofactory.com/javascript/design-patterns/prototype): 此篇則是根據最純粹的 prototype pattern 觀念實作給你看什麼叫做「創建實例是從已存在的實例複製而來」

### 8. The Factory Pattern
- [GoF Design Patterns](https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F_(%E8%AE%A1%E7%AE%97%E6%9C%BA)) 中的一種
- 將創建物件的工作交給工廠來做，而不是自己手工 `new` 一個
- 有時候你不希望較 high-level 的程式碼知道太多，所以將實際創建物件的過程封裝起來。且當未來若需求變更甚至直接抽換掉底層的物件，high-level 的程式碼甚至不需要做變更

```javascript=
function FullTime () {
  this.hourly = '$12'
}

function PartTime () {
  this.hourly = '$10'
}

function Contractor () {
  this.hourly = '$15'
}

function JobTypeFactory () {
  this.createEmployee = function (name, type) {
    let employee

    if (type === 'fulltime') {
      employee = new FullTime()
    } else if (type === 'parttime') {
      employee = new PartTime()
    } else if (type === 'contractor') {
      employee = new Contractor()
    } else {
      throw new Error(`Company does not offer ${type} type job now`)
    }

    employee.name = name
    employee.type = type

    employee.say = function () {
      console.log(`${employee.name} is a ${employee.type}-type employee, wage is ${employee.hourly}/hr`)
    }

    return employee
  }
}

const employees = []
const hr = new JobTypeFactory()
employees.push(hr.createEmployee('Max', 'fulltime'))
employees.push(hr.createEmployee('John', 'parttime'))
employees.push(hr.createEmployee('HH', 'contractor'))

employees.forEach((employee) => { employee.say() })
// Max is a fulltime-type employee, wage is $12/hr
// John is a parttime-type employee, wage is $10/hr
// HH is a contractor-type employee, wage is $15/hr
```

### 9. The Mixin Pattern
- 此概念提供一種機制，在不允許多重繼承的語言中達到「多重繼承」的效果；也可避免「菱形繼承」的隱憂
- Mixin 與「繼承」的目的很像，但子類別與父類別之間並不需要有「is a」的關係
- 因為沒有「is a」的關係，此 pattern 更著重在只 **reuse desired functionality**、而**非父類別全部的屬性／方法**
- Mixin Pattern 其實也是 [Dependency inversion principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle) 的一種實現
- 簡單來說我只實作一個 mixin class，裡頭有一些方法用來操作某個 parent class，然後透過語法將 mixin 提供的方法複製給 parent class
- 簡單的實作方案可參考 [digitalocean tutorial](https://www.digitalocean.com/community/tutorials/js-using-js-mixins)

```javascript=
const Alligator = function (speed, direction) {
  this.speed = speed
  this.direction = direction
}

// The swim property here is the "mixin"
const swim = {
  location () {
    console.log(`Heading ${this.direction} at ${this.speed}`)
  }
}

// 許多 mixin 文章的解說，會直接使用 Object.assign 來達成同樣的目的
Object.assign(Alligator.prototype, swim)

const aaa = new Alligator('10 mph', 'East')

aaa.location()
// Heading East at 10 mph

const bbb = new Alligator('20 mph', 'North')

bbb.location()
// Heading North at 20 mph

console.log(aaa.location === bbb.location)
// true
```

### 10. The Decorator Pattern
- [GoF Design Patterns](https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F_(%E8%AE%A1%E7%AE%97%E6%9C%BA)) 中的一種
- 此模式，可以更靈活地替某類別增加功能或動態地修改行為、而不用真的撰寫子類別、繼承等

```javascript=
function iMac () {
  this.cost = function () {
    return 51100
  }
  this.screenSize = function () {
    return 21.5
  }
}

// Decorator 1
function LinePointDiscount (imac) {
  const v = imac.cost()
  imac.cost = function () {
    return v - 5110
  }
}

// Decorator 2
function SellAirPods (imac) {
  const v = imac.cost()
  imac.cost = function () {
    return v - 3600
  }
}

// Decorator 3
function SellKeyboardMouse (imac) {
  const v = imac.cost()
  imac.cost = function () {
    return v - 4000
  }
}

const imac = new iMac()
console.log('Original cost:', imac.cost())
// Original cost: 51100

LinePointDiscount(imac)
SellAirPods(imac)
SellKeyboardMouse(imac)

console.log('Final cost:', imac.cost())
// Final cost: 38390
```