Tiếp nối [Phần 1] Học nhanh các Javascript Design Pattern thì hôm nay chúng ta sẽ đi đến phần còn lại về các Javascript Design Pattern nhé.

ES6 Module

Trước khi ES6 xuất hiện thì Javascript không có tính năng module hóa, anh em dev phải dựa vào các thứ viện ngoài như Webpack hoặc tự tạo theo Module Pattern mà mình giới thiệu ở phần 1 để module hóa ứng dụng web.

Giờ đây thì ta có thể dùng ES6 module ở trong môi trường trình duyệt thông thường, ở trong Webpack hoặc NodeJs phiên bản 13.2.0 trở lên.

ES6 Module lưu trữ trên từng file, mỗi file là một module. Mọi thứ bên trong module mặc định là private. Hàm, biến và các class được “xuất” ra ngoài bằng từ khóa export. Code bên trong module luôn luôn chạy ở chế độ strict mode.

Export một module

Có 2 cách để export một function hoặc 1 biến:

  • Sử dụng từ khóa export ở trước khai báo function hoặc biến. Ví dụ:

utils.js

export const greeting = 'Hello World'
export function sum(num1, num2) {
  console.log('Sum:', num1, num2)
  return num1 + num2
}
export function subtract(num1, num2) {
  console.log('Subtract:', num1, num2)
  return num1 - num2
}
// Đây là một private function
function privateLog() {
  console.log('Private Function')
}

Thêm từ khóa export vào dưới khai báo biến hoặc function. Ví dụ:

function multiply(num1, num2) {
  console.log('Multiply:', num1, num2)
  return num1 * num2
}
function divide(num1, num2) {
  console.log('Divide:', num1, num2)
  return num1 / num2
}
// Đây là một private function
function privateLog() {
  console.log('Private Function')
}
export { multiply, divide }

Import một module

Tương tự như export một module, có 2 cách để import một module bằng cách sử dụng từ khóa import. Ví dụ:

  • Import nhiều biến/hàm cùng một lần

main.js

// import nhiều phần tử
import { sum, multiply } from './utils.js'
console.log(sum(3, 7))
console.log(multiply(3, 7))
  • Import tất cả ở trong module

main.js

// import tất cả phần tử trong module
import * as utils from './utils.js'
console.log(utils.sum(3, 7))
console.log(utils.multiply(3, 7))

Import và Export cũng có thể dùng bí danh (alias)

Nếu bạn muốn tránh việc xung đột tên, bạn có thể thay đổi tên export cũng như import. Ví dụ:

  • Đổi tên một export

utils.js

function sum(num1, num2) {
  console.log('Sum:', num1, num2)
  return num1 + num2
}
function multiply(num1, num2) {
  console.log('Multiply:', num1, num2)
  return num1 * num2
}
export { sum as add, multiply }
  • Đổi tên một import

main.js

import { add, multiply as mult } from './utils.js'
console.log(add(3, 7))
console.log(mult(3, 7))

Trong file HTML các bạn nhớ link đến  file main.js (file mà import các file khác) nhé

index.html

<script src="main.js" type="module"></script>

Factory pattern

Factory Pattern là pattern sử dụng phương thức factory để tạo object mà không cần chỉ định chính xác class hoặc constructor function.

Factory Pattern được sử dụng để tạo các object mà không “làm lộ” logic khởi tạo. Pattern này có thể được sử dụng khi chúng ta cần tạo một object khác dựa vào một điều kiện được chỉ rõ từ trước. Ví dụ:

class Car {
  constructor(options) {
    this.doors = options.doors || 4
    this.state = options.state || 'brand new'
    this.color = options.color || 'white'
  }
}
class Truck {
  constructor(options) {
    this.doors = options.doors || 4
    this.state = options.state || 'used'
    this.color = options.color || 'black'
  }
}
class VehicleFactory {
  createVehicle(options) {
    if (options.vehicleType === 'car') {
      return new Car(options)
    } else if (options.vehicleType === 'truck') {
      return new Truck(options)
    }
  }
}

Ở đây mình tạo một class CarTruck (với một số giá trị mặc định), 2 class này được sử dụng để tạo object cartruck. Và mình định nghĩa một class VehicleFactory để tạo và return một object mới dựa trên vehicleType nhận được từ object options như một đối số.

const factory = new VehicleFactory()
const car = factory.createVehicle({
  vehicleType: 'car',
  doors: 4,
  color: 'silver',
  state: 'Brand New'
})
const truck = factory.createVehicle({
  vehicleType: 'truck',
  doors: 2,
  color: 'white',
  state: 'used'
})
// Prints Car {doors: 4, state: "Brand New", color: "silver"}
console.log(car)
// Prints Truck {doors: 2, state: "used", color: "white"}
console.log(truck)

Mình đã tạo một object mới là factory từ VehicleFactory class. Sau đó chúng ta có thể tạo một object Car hoặc Truck mới bằng cách gọi factory.createVehicle và truyền một object options vào với thuộc tính vehicleTypecar hoặc truck.

Decorator pattern

Decorator pattern được sử dụng để mở rộng chức năng của một object mà không phải thay đổi class hoặc constructor function hiện có.

Pattern này có thể được sử dụng để thêm tính năng vào một object mà không thay đổi code sâu bên dưới chúng.

Một ví dụ đơn giản của pattern này là:

function Car(name) {
  this.name = name
  // Default values
  this.color = 'White'
}
// Tạo object mới
const tesla = new Car('Tesla Model 3')
// Thêm tính năng mới vào object mà không phải thay đổi constructor function
tesla.setColor = function (color) {
  this.color = color
}
tesla.setPrice = function (price) {
  this.price = price
}
tesla.setColor('black')
tesla.setPrice(49000)
// prints black
console.log(tesla.color)

Một ví dụ thực tế hơn của pattern này là:

Bình thường thì chi phí chi trả cho một chiếc xe sẽ khác nhau dựa vào tính năng mà nó có. Nếu không có decorator pattern, chúng ta sẽ tạo nhiều class khác nhau đại diện cho những loại xe mà tính năng của chúng khác nhau, mỗi cái lại có một phương thức tính giá tiền. Ví dụ:

class Car() {
}
class CarWithAC() {
}
class CarWithAutoTransmission {
}
class CarWithPowerLocks {
}
class CarWithACandPowerLocks {
}

Nhưng với decorator pattern, chúng ta có thể tạo một class cơ bản là Car và thêm phương thức tính toán dựa vào decorator function. Ví dụ:

class Car {
  constructor() {
    // Default Cost
    this.cost = function () {
      return 20000
    }
  }
}
// Decorator function
function carWithAC(car) {
  car.hasAC = true
  const prevCost = car.cost()
  car.cost = function () {
    return prevCost + 500
  }
}
// Decorator function
function carWithAutoTransmission(car) {
  car.hasAutoTransmission = true
  const prevCost = car.cost()
  car.cost = function () {
    return prevCost + 2000
  }
}
// Decorator function
function carWithPowerLocks(car) {
  car.hasPowerLocks = true
  const prevCost = car.cost()
  car.cost = function () {
    return prevCost + 500
  }
}

Đầu tiên, chúng ta tạo class cơ bản là Car cho việc tạo Car object. Sau đó, chúng ta tạo các decorator function cho các tính năng mà chúng ta muốn thêm vào Car object. Tiếp theo chúng ta override cost function để return về giá tiền dựa vào từng đặc điểm của xe.

Quy trình sẽ thực hiện như thế này:

const car = new Car()
console.log(car.cost())
carWithAC(car)
carWithAutoTransmission(car)
carWithPowerLocks(car)

Cuối cùng, chúng ta có thể tính toán giá tiền của chiếc xe như thế này:

// Tính toán chi phí của chiếc xe sau khi thêm n tính năng
console.log(car.cost())

Nếu bạn nào đã từng code React thì sẽ biết High Order Component, nó chính là Decorator Pattern!

Đọc thêm:

Observer pattern

Observer pattern là một design pattern nơi mà một object (thường được gọi là subject) có nhiệm vụ duy trì một danh sách các object dựa vào nó, tự động thông báo đến các object đó nếu có bất kì sự thay đổi nào về trạng thái.

Để dễ hình dung, Observer pattern nó giống như kiểu lắng nghe sự kiện bằng addEventListener khi ta DOM bằng Javascript ý. Ta thêm lắng nghe "scroll" thì khi scroll nó sẽ chạy function mà ta đưa vào.

document.body.addEventListener("scroll", event => {
  console.log(event)
})

Bây giờ mình sẽ mô phỏng một observer pattern nhé.

class Subject {
  constructor() {
    this.observers = []
  }
  // Dùng để đăng ký
  subscribe(func) {
    this.observers.push(func)
  }
  // Dùng để hủy đăng ký
  unsubscribe(func) {
    this.observers = this.observers.filter((subscriber) => subscriber !== func)
  }
  // Gửi 1 thông báo đến mọi
  fire(data) {
    this.observers.forEach((observer) => observer(data))
  }
}

Bạn đang thắc mắc cách sử dụng như thế nào phải không, xem tiếp nhé.

const $gun = new Subject()
const func = (value) => {
  console.log(value)
}
$gun.subscribe(func)
$gun.fire('boom')

Mình đăng ký lắng nghe sự kiện, khi mà chạy phương thức fire thì những object đã đăng ký sẽ nhận được thông báo ngay lập tức. Tức là func function sẽ được chạy.

Nếu muốn không nhận thông báo nữa chỉ cần unsubcribe nó là được.

$gun.unsubscribe(func)

Angular dùng cực nhiều pattern này

Tóm lại

Qua 2 phần, mình và các bạn đã đi tìm hiểu top những design pattern phổ biến nhất trong Javascript, mình biết 2 bài vừa rồi không thể bao quát hết những design pattern nên nếu bạn hứng thú với các design pattern thì có thể đọc thêm ở đây của tác giả Addy Osmani. 

Vậy đó 😛 , hy vọng bài viết của mình mang đến giá trị cho các bạn, nếu ủng hộ mình bạn có thể like fanpage hoặc để lại comment là được rồi :mrgreen:

Tham khảo