NỘI DUNG BÀI VIẾT
Nguyên lý SOLID trong JavaScript hay bất cứ ngôn ngữ lập trình nào khác đều liên quan chặt chẽ với các Design Pattern (Mẫu thiết kế). Điều quan trọng là chúng ta phải biết các mẫu thiết kế vì đó là một chủ đề cực kỳ “hot” trong một buổi phỏng vấn. Nếu bạn biết chúng, bạn sẽ dễ dàng hiểu các mô hình lập trình, mô hình kiến trúc và các tính năng ngôn ngữ phức tạp hơn như Reactive programming, Flux architecture (Redux), Generators trong JavaScript,…
Nguyên lý SOLID là gì?
Nguyên lý SOLID được đúc kết từ kinh nghiệm lập trình của kỹ sư phần mềm nổi tiếng Robert C. Martin. SOLID là sự kết hợp của các từ viết tắt:
- S — Single responsibility principle
- O — Open closed principle
- L — Liskov substitution principle
- I — Interface segregation principle
- D — Dependency Inversion principle
5 nguyên lý SOLID trong JavaScript này sẽ giúp bạn cách viết mã tốt hơn. Mặc dù chúng đến từ lập trình hướng đối tượng. Tôi biết thật là táo bạo khi gọi JavaScript là ngôn ngữ hướng đối tượng. Nhưng chả sao cả, JavaScript cũng chỉ là công cụ để lập trình còn tư duy về thiết kế phần mềm vẫn là ở bạn.
Nào, bắt đầu thôi!
S — Single responsibility principle
Đây có lẽ là một trong những nguyên lý SOLID dễ hiểu nhất và đồng thời cũng là nguyên lý bị hiểu lầm nhiều nhất.
A module should be responsible for only one actor. As a consequence, it has only one reason to change
Mỗi lớp chỉ nên chịu trách nhiệm về một nhiệm vụ cụ thể nào đó mà thôi.
class TodoList {
constructor() {
this.items = []
}
addItem(text) {
this.items.push(text)
}
removeItem(index) {
this.items = items.splice(index, 1)
}
toString() {
return this.items.toString()
}
save(filename) {
fs.writeFileSync(filename, this.toString())
}
load(filename) {
// Some implementation
}
}
Code language: JavaScript (javascript)
Mặc dù ngay từ cái nhìn đầu tiên, lớp TodoList này có vẻ ổn, nhưng nó lại vi phạm nguyên lý trên. Có nhiều thứ được viết trong lớp này. Tôi đã viết thêm một lớp nữa để quản lý việc thực thi với cơ sở dữ liệu.
class TodoList {
constructor() {
this.items = []
}
addItem(text) {
this.items.push(text)
}
removeItem(index) {
this.items = items.splice(index, 1)
}
toString() {
return this.items.toString()
}
}
class DatabaseManager {
saveToFile(data, filename) {
fs.writeFileSync(filename, data.toString())
}
loadFromFile(filename) {
// Some implementation
}
}
Code language: JavaScript (javascript)
Do đó, đoạn mã trở nên dễ mở rộng hơn. Tất nhiên, nó không quá rõ ràng khi chúng ta xem xét các “mẩu” code nhỏ. Khi áp dụng cho một kiến trúc phức tạp, nguyên lý này có nhiều ý nghĩa hơn.
O — Open closed principle
Modules should be open for extension but closed for modification
Khi triển khai các tính năng mới, thay vì sửa đổi code đã tồn tại, chúng ta nên mở rộng/kế thừa.
class Coder {
constructor(fullName, language, hobby, education, workplace, position) {
this.fullName = fullName
this.language = language
this.hobby = hobby
this.education = education
this.workplace = workplace
this.position = position
}
}
class CoderFilter {
filterByName(coders, fullName) {
return coders.filter(coder => coder.fullName === fullName)
}
filterBySize(coders, language) {
return coders.filter(coder => coder.language === language)
}
filterByHobby(coders, hobby) {
return coders.filter(coder => coder.hobby === hobby)
}
}
Code language: JavaScript (javascript)
Vấn đề với CoderFilter
là nếu chúng ta muốn lọc theo bất kỳ thuộc tính mới nào khác, chúng ta phải thay đổi mã của CodeFilter
. Hãy giải quyết vấn đề này bằng cách tạo một hàm filterByProp
.
const filterByProp = (array, propName, value) =>
array.filter(element => element[propName] === value)
Code language: JavaScript (javascript)
L — Liskov substitution principle
Một nguyên lý với cái tên khó hiểu nhất. Nó có nghĩa là gì?
If you have a function, that works for a base type, it should work for a derived type
Các đối tượng của class cha có thể được thay thế bởi các đối tượng của các class con mà không làm thay đổi tính đúng đắn của chương trình.
class Rectangle {
constructor(width, height) {
this._width = width
this._height = height
}
get width() {
return this._width
}
get height() {
return this._height
}
set width(value) {
this._width = value
}
set height(value) {
this._height = value
}
getArea() {
return this._width * this._height
}
}
class Square extends Rectangle {
constructor(size) {
super(size, size)
}
}
const square = new Square(2)
square.width = 3
console.log(square.getArea())
Code language: JavaScript (javascript)
Bạn thử đoán xem những gì sẽ được in ra trong Console. Nếu câu trả lời của bạn là 6 thì bạn đúng rồi đấy. Tất nhiên, câu trả lời mong muốn ở đây là 9. Chúng ta có thể thấy một sự vi phạm cổ điển của nguyên lý thay thế Liskov.
Để khắc phục, bạn có thể xác định Square
theo cách này:
class Square extends Rectangle {
constructor(size) {
super(size, size)
}
set width(value) {
this._width = this._height = value
}
set height(value) {
this._width = this._height = value
}
}
Code language: JavaScript (javascript)
I — Interface segregation principle
Clients should not be forced to depend upon interfaces that they do not use
Thay vì viết một interface cho một mục đích chung chung, chúng ta nên tách thành nhiều interface nhỏ cho các mục đích riêng. Không nên bắt buộc client phải implement các method mà client không cần đến.
Tuy nhiên, trong JavaScript không hề có interface. Có một cách để bắt chước hành vi, nhưng tôi không nghĩ là có nhiều ý nghĩa. Hãy thích nghi tốt hơn nguyên lý này với thế giới JavaScript.
Hãy xác định một lớp abstract Phone
sẽ đóng vai trò của interface trong trường hợp của chúng ta:
class Phone {
constructor() {
if (this.constructor.name === 'Phone')
throw new Error('Phone class is absctract')
}
call(number) {}
takePhoto() {}
connectToWifi() {}
}
Code language: JavaScript (javascript)
Chúng ta có thể sử dụng nó để định nghĩa iPhone không?
class IPhone extends Phone {
call(number) {
// Implementation
}
takePhoto() {
// Implementation
}
connectToWifi() {
// Implementation
}
}
Code language: JavaScript (javascript)
Được rồi, nhưng đối với Nokia 3310 cũ, giao diện này sẽ vi phạm nguyên lý trên:
class Nokia3310 extends Phone {
call(number) {
// Implementation
}
takePhoto() {
// Argh, I don't have a camera
}
connectToWifi() {
// Argh, I don't know what wifi is
}
Code language: JavaScript (javascript)
D — Dependency Inversion principle
High-level modules should not depend on low-level modules
Các module cấp cao không nên phụ thuộc vào các module cấp thấp, cả hai nên phụ thuộc vào abstraction.
class FileSystem {
writeToFile(data) {
// Implementation
}
}
class ExternalDB {
writeToDatabase(data) {
// Implementation
}
}
class LocalPersistance {
push(data) {
// Implementation
}
}
class PersistanceManager {
saveData(db, data) {
if (db instanceof FileSystem) {
db.writeToFile(data)
}
if (db instanceof ExternalDB) {
db.writeToDatabase(data)
}
if (db instanceof LocalPersistance) {
db.push(data)
}
}
}
Code language: JavaScript (javascript)
Trong trường hợp này, mô-đun cấp cao PersistanceManager
phụ thuộc vào các mô-đun cấp thấp, đó là FileSystem
, ExternalDB
và LocalPersistance
.
Để tránh sự cố trong trường hợp đơn giản này, chúng ta có thể nên làm điều gì đó như sau:
class FileSystem {
save(data) {
// Implementation
}
}
class ExternalDB {
save(data) {
// Implementation
}
}
class LocalPersistance {
save(data) {
// Implementation
}
}
class PersistanceManager {
saveData(db, data) {
db.save(data)
}
}
Code language: JavaScript (javascript)
Video chia sẻ: SOLID – Những nguyên lý sống còn
Trong video này, chúng ta cũng sẽ bàn đến mối quan hệ giữa các khái niệm và kỹ thuật như:
- SOLID
- Design Pattern
- Refactoring
- Clean Code
- Automation Test
- …
Tất cả những khái niệm này, tưởng chừng như rời rạc, nhưng thực ra lại có mối quan hệ gắn bó rất mật thiết. Mỗi khái niệm, kỹ thuật đều có mục đích và nhiệm vụ riêng của nó, đóng góp chung vào chất lượng của sản phẩm.
Kết luận
Giá trị của 5 nguyên lý SOLID trong JavaScript là không rõ ràng. Nhưng nếu bạn tự hỏi mình ‘Tôi có vi phạm nguyên lý SOLID không?’ khi bạn thiết kế kiến trúc của phần mềm, tôi hứa rằng chất lượng và khả năng mở rộng mã của bạn sẽ tốt hơn rất nhiều.
Cảm ơn bạn đã theo dõi bài viết!
Các bạn có thể tham khảo các bài viết hay về JavaScript tại đây.
Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.
TỔNG HỢP TÀI LIỆU HỌC LẬP TRÌNH CƠ BẢN CHO NGƯỜI MỚI BẮT ĐẦU
KHOÁ HỌC BOOTCAMP JAVA/JAVASCRIPT/PHP TRỞ THÀNH LẬP TRÌNH VIÊN TRONG 5-6 THÁNG