Menu

  • Trang chủ
  • Trending
  • Gợi ý bạn đọc

Chuyên mục

  • Tin tức
  • Hackintosh
  • Lập trình
  • Software
  • Thủ thuật
  • Chia sẻ
  • GenZA Can Cook
  • GenZA Beauty
  • Cảm nhận cuộc sống

Liên hệ hợp tác

admin@genzakit.com
Genzakit
Không có kết quả phù hợp
Xem tất cả kết quả
  • Đăng nhập
Genzakit
Không có kết quả phù hợp
Xem tất cả kết quả

Nguyên lý SOLID là gì? Nguyên lý SOLID trong Node.js với TypeScript

544
CHIA SẺ
3.1k
LƯỢT XEM
Chia sẻ lên FacebookChia sẻ lên TwitterLưu lại trên Pinterest

Với những bạn lập trình Java thì có lẽ biết rất rõ nguyên lý SOLID là gì rồi. Với Java thì SOLID gần như là quy tắc bất di bất dịch mà mọi lập trình viên phải nắm vững.
Tuy nhiên, với Node.js hay Javascript nói chúng thì lại rất dễ dãi. Bạn viết code kiểu gì cũng được, bạ đâu viết đấy cũng được và tùy thuộc style code của mỗi người. Chính vì điều này mà Node.js/Javascript cực dễ học.
Nhưng vì viết code thoải mái, không có quy tắc sẽ dẫn đến dự án khó maintain, code sẽ rất rối, khó debug… Chính vì vậy, nếu có thể áp dụng được nguyên tắc SOLID cho dự án Node.js thì thật tuyệt.
Bài viết này mình sẽ chia sẻ cách thực hiện nguyên lý SOLID trong Node.Js với sự hỗ trợ của TypeScript.
Nguyên lý SOLID là nguyên lý với 5 nguyên tắc, tương ứng với các chữ cái viết tắt trong tên là:

  • Single Responsibility Principle
  • Open Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Nguyên lý SOLID trong Node.js

Single responsibility principle

Được hiểu như sau:

Một class chịu trách một việc mà thôi

Ví dụ sau mình tạo một class Person bằng TypeScript. Trong đó mình định nghĩa các thuộc tính của một người như Tên, đệm, thông tin liên hệ( email), và các hành động chào hỏi – greet(), xác thực email – validateEmail().

class Person {
    public name : string;
    public surname : string;
    public email : string;
    constructor(name : string, surname : string, email : string){
        this.surname = surname;
        this.name = name;
        if(this.validateEmail(email)) {
          this.email = email;
        } else {
            throw new Error("Invalid email!");
        }
    }
    validateEmail(email : string) {
        var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
        return re.test(email);
    }
    greet() {
        alert("Hi!");
    }
}

Nhìn qua class trên bạn thấy ngay rằng có chi tiết thừa. Đó chính là hàm validateEmail(), vì hành động này không phải là hành vi của một người. Không nên đặt hàm này trong class Person.
Chúng ta có cải tiến đoạn code như sau:

class Email {
    public email : string;
    constructor(email : string){
        if(this.validateEmail(email)) {
          this.email = email;
        }e lse {
            throw new Error("Invalid email!");
        }        
    }
    validateEmail(email : string) {
        var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
        return re.test(email);
    }
}
class Person {
    public name : string;
    public surname : string;
    public email : Email;
    constructor(name : string, surname : string, email : Email){
        this.email = email;
        this.name = name;
        this.surname = surname;
    }
    greet() {
        alert("Hi!");
    }
}

Open/close principle

Có thể hiểu nguyên tắc này như sau:

Chỉ nên mở rộng một class bằng cách kế thừa. Tuyệt đối không mở rộng class bằng cách sửa đổi nó.

Điểm mấu chốt của nguyên tắc này là: Chỉ được THÊM mà không được SỬA.
Đoạn code dưới đây là một ví dụ cho vi phạm nguyên tắc này:

class Rectangle {
    public width: number;
    public height: number;
}
class Circle {
    public radius: number;
}
function getArea(shapes: (Rectangle|Circle)[]) {
    return shapes.reduce(
        (previous, current) => {
            if (current instanceof Rectangle) {
                return current.width * current.height;
            } else if (current instanceof Circle) {
                return current.radius * current.radius * Math.PI;
            } else {
                throw new Error("Unknown shape!")
            }
        }, 0);
}

Đoạn code cho phép chúng ta tính diện tích của hình chữ nhật và hình tròn. Nếu giờ mình muốn mở rộng chương trình, muốn hỗ trợ thêm hình tam giác nữa. Vậy phải làm sao?
Mình sẽ phải SỬA code của hàm getArea(). Mà làm như vậy là vi phạm nguyên tắc rồi.
Để cải thiện, chúng ta sửa lại thành như sau:

interface Shape {
    area(): number;
}
class Rectangle implements Shape {
    public width: number;
    public height: number;
    public area() {
        return this.width * this.height;
    }
}
class Circle implements Shape {
    public radius: number;
    public area() {
        return this.radius * this.radius * Math.PI;
    }
}
function getArea(shapes: Shape[]) {
    return shapes.reduce(
        (previous, current) => previous + current.area(),
        0
    );
}

Với code mới này, để hỗ trợ thêm một hình dạng mới, bạn chỉ cần tạo THÊM một class và implement interface Shape là được, không cần phải sửa code đã có.

Liskov substitution principle

Trong một chương trình, các object của class con có thể thay thế class cha mà không làm thay đổi tính đúng đắn của chương trình

Với nguyên tắc này khuyến khích chúng ta sử dụng tính đa hình trong lập trình hướng đối tượng.
Quay lại đoạn code của ví dụ trước:

function getArea(shapes: Shape[]) {
    return shapes.reduce(
        (previous, current) => previous + current.area(),
        0
    );
}

Chúng ta đã sử dụng interface Shape để đảm bảo chương trình có thể mở rộng mà không cần phải sửa code đã có.
Nguyên tắc thay thế Liskov cho chúng ta biết rằng chúng ta có thể chuyển bất cứ kiểu con nào của Shape( Ví dụ: Rectangle, Cyrcle…) sang hàm getArea() mà không làm thay đổi tính chính xác của chương trình.
Trong các ngôn ngữ lập trình kiểu như TypeScript/Java, thì trình biên dịch sẽ kiểm tra đã implement chính xác interface đó chưa( Nếu implement mà không override đủ method là bị lỗi biên dịch ngay). Nên bạn không phải làm thủ công để đảm bảo chương trình tuân thủ nguyên tắc Liskov.

Interface segregation principle

Nên tách Interface thành nhiều interface nhỏ với những mục đích riêng biệt

Vẫn lấy đoạn code tính diện tích hình chữ nhật và hình tròn. Bây giờ có một vấn đề là: Nếu bạn sử dụng đoạn code cho nhiều mục đích khác nhau thì sao?
Nếu mình muốn tính diện tích xong rồi thì mã hóa thành JSON và trả về cho client. Nếu mình code như sau thì sẽ vi phạm nguyên tắc thứ 4 này.

interface Shape {
    area(): number;
    serialize(): string;
}
class Rectangle implements Shape {
    public width: number;
    public height: number;
    public area() {
        return this.width * this.height;
    }
    public serialize() {
        return JSON.stringify(this);
    }
}
class Circle implements  Shape {
    public radius: number;
    public area() {
        return this.radius * this.radius * Math.PI;
    }
    public serialize() {
        return JSON.stringify(this);
    }
}

Vi phạm bởi vì: Có lúc mình dùng không cần phải mã hóa thành JSON để trả cho client, có lúc mình lại cần JSON. Đoạn code đã mix cả hai mục đích ấy vào cùng một interface.
Để tốt hơn thì nên tác interface ra.

interface RectangleInterface {
    width: number;
    height: number;
}
interface CircleInterface {
    radius: number;
}
interface Shape {
    area(): number;
}
interface Serializable {
    serialize(): string;
}

Khi không cần mã hóa JSON.

class Rectangle implements RectangleInterface, Shape {
    public width: number;
    public height: number;
    public area() {
        return this.width * this.height;
    }
}
class Circle implements CircleInterface, Shape {
    public radius: number;
    public area() {
        return this.radius * this.radius * Math.PI;
    }
}
function getArea(shapes: Shape[]) {
    return shapes.reduce(
        (previous, current) => previous + current.area(),
        0
    );
}

Và khi cần mã hóa thành JSON:

class RectangleDTO implements RectangleInterface, Serializable {
    public width: number;
    public height: number;
    public serialize() {
        return JSON.stringify(this);
    }
}
class CircleDTO implements CircleInterface, Serializable {
    public radius: number;
    public serialize() {
        return JSON.stringify(this);
    }
}

Các bạn thấy code “ngon” hơn chưa?

Dependency inversion principle

Đây là nguyên tắc quan trọng nhất trong nguyên lý SOLID. Nhưng vì nó là chữ D trong cụm từ SOLID nên luôn được giải thích sau cùng.
Nếu chúng ta xem lại các ví dụ ở các nguyên tắc trên, chúng ta thấy rằng interface là một yếu tố cơ bản nhất của mọi nguyên tắc. Và nguyên tắc dependency inversion chính là tổng hợp của 4 nguyên tắc trước.

Kết luận

Việc thực hiện nguyên lý SOLID trong các ngôn ngữ lập trình không hỗ trợ Interface như Javascript ES5, thậm chí ES6 thật là khiêm cưỡng. Nhưng với hỗ trợ của TypeScript thì lại thật tuyệt.
Hy vọng qua bài viết này, các bạn có thể nắm được nguyên lý SOLID là gì, và cách ứng dụng nguyên lý SOLID trong Node.js với TypeScript.

Từ khoá: solidtypescript
Next Post

Cách tối ưu Component trong React Native

Theo dõi
Đăng nhập
Thông báo của
guest
guest
0 Comments
Phản hồi nội tuyến
Xem tất cả bình luận

Có thể bạn quan tâm

Hướng dẫn sử dụng TikTok Analytics sao cho hiệu quả

by Jendy Yến
13 Tháng Chín, 2021
1
2.7k

Trong công cụ phân tích của TikTok có cung cấp một cách để tìm rất nhiều thông tin...

Cách làm chè thái sầu riêng

Cách làm chè thái sầu riêng

by Huệ Minh
8 Tháng Mười, 2021
0
2.6k

Chè thái sầu riêng là món ăn khoái khẩu của rất nhiều người. Tuy nhiên không phải ai...

Thịt kho xơ mít

Thịt kho xơ mít

by Huệ Minh
18 Tháng Chín, 2021
0
2.6k

Là một món ăn dân dã của người miền Trung, trong loạt bài về các món ngon từ...

Hướng dẫn cài dual boot windows và hackintosh trên 2 ổ cứng khác nhau

by Anthony Tran
24 Tháng Ba, 2020
5
4.5k

Sau khi cài đặt macOS bằng Hackintosh thành công, nhiều bạn muốn sử dụng Windows 10 song song,...

  • Giới thiệu
  • Liên hệ
  • Chính sách bảo mật

© 2023 Genzakit

Không có kết quả phù hợp
Xem tất cả kết quả
  • Trending
  • Gợi ý bạn đọc
  • Tin tức
  • Hackintosh
  • Lập trình
  • Software
  • Thủ thuật
  • Chia sẻ
  • GenZA Can Cook
  • GenZA Beauty
  • Vui mỗi ngày
  • Cảm nhận cuộc sống

© 2022 Genzakit - Ngôi nhà giới trẻ

Chào mừng trở lại!

Đăng nhập bằng Facebook
Sign In with Google
OR

Đăng nhập

Quên mật khẩu?

Retrieve your password

Please enter your username or email address to reset your password.

Đăng nhập
wpDiscuz