Một trong những nguyên tắc quan trọng nhất của lập trình hướng đối tượng – phân định giao diện bên trong với giao diện bên ngoài.

Đó là thực hành bắt buộc để phát triển bất cứ thứ gì phức tạp hơn một ứng dụng hello word.

Để hiểu điều này, chúng ta hãy tách ra khỏi sự phát triển và hướng mắt vào thế giới thực.

Thông thường, các thiết bị của chúng ta đang sử dụng khá phức tạp. Nhưng phân định giao diện bên trong từ giao diện bên ngoài cho phép sử dụng chúng mà không gặp vấn đề gì.

1. Một ví dụ thực tế

Ví dụ, một máy pha cà phê. Đơn giản từ bên ngoài: một nút bấm, màn hình hiển thị, một vài lỗ nhỏ Và chắc chắn, kết quả – cà phê tuyệt vời! 🙂

Nhưng bên trong, (một hình ảnh từ hướng dẫn sửa chữa)

Rất nhiều chi tiết. Nhưng chúng ta có thể sử dụng nó mà không biết gì.

Máy pha cà phê khá đáng tin cậy phải không? Chúng ta có thể sử dụng một chiếc trong nhiều năm và chỉ khi có sự cố xảy ra – hãy mang nó đi sửa chữa.

Bí mật về độ tin cậy và đơn giản của máy pha cà phê – tất cả các chi tiết đều được điều chỉnh tốt và ẩn bên trong.

Nếu chúng ta tháo vỏ bảo vệ khỏi máy pha cà phê, thì việc sử dụng nó sẽ phức tạp hơn nhiều (ấn vào đâu?) Và nguy hiểm (nó có thể bị điện giật).

Như chúng ta sẽ thấy, trong các đối tượng lập trình giống như máy pha cà phê.

Nhưng để che giấu các chi tiết bên trong, chúng ta sẽ không sử dụng vỏ bọc bảo vệ mà là cú pháp đặc biệt của ngôn ngữ và quy ước.

2. Giao diện bên trong và bên ngoài

Trong lập trình hướng đối tượng, các thuộc tính và phương thức được chia thành hai nhóm:

  • Giao diện bên trong – các phương thức và thuộc tính, chỉ có thể truy cập từ các phương thức khác của lớp, nhưng không phải từ bên ngoài.
  • Giao diện bên ngoài – phương thức và thuộc tính, có thể truy cập từ bên ngoài lớp.

Nếu chúng ta tiếp tục tương tự với máy pha cà phê – thứ ẩn giấu bên trong: ống nồi hơi, bộ phận làm nóng, v.v. – là giao diện bên trong của nó.

Một giao diện bên trong được sử dụng để đối tượng hoạt động, các chi tiết của nó sử dụng lẫn nhau. Ví dụ, một ống nồi hơi được gắn vào bộ phận làm nóng.

Nhưng từ bên ngoài, một chiếc máy pha cà phê được đóng kín bằng vỏ bảo vệ, để không ai có thể tiếp cận được. Thông tin chi tiết được ẩn và không thể truy cập. Chúng ta có thể sử dụng các tính năng của nó thông qua giao diện bên ngoài.

Vì vậy, tất cả những gì chúng ta cần sử dụng một đối tượng là để biết giao diện bên ngoài của nó. Chúng ta có thể hoàn toàn không biết làm thế nào nó hoạt động bên trong, và điều đó thật tuyệt.

Đó là một giới thiệu chung.

Trong JavaScript, có hai loại trường đối tượng (thuộc tính và phương thức):

  • Công khai(Public): có thể truy cập từ bất cứ đâu. Chúng bao gồm các giao diện bên ngoài. Cho đến bây giờ chúng ta chỉ sử dụng các thuộc tính và phương pháp công cộng.
  • Riêng tư(Private): chỉ có thể truy cập từ bên trong lớp. Đây là cho giao diện nội bộ.

Trong nhiều ngôn ngữ khác cũng tồn tại các trường được bảo vệ(protected): chỉ có thể truy cập từ bên trong lớp và những trường mở rộng nó (những trường đó là riêng tư, nhưng cộng với quyền truy cập từ các lớp kế thừa). Chúng cũng hữu ích cho giao diện nội bộ. Chúng theo nghĩa rộng rãi hơn so với tư nhân, bởi vì chúng ta thường muốn lớp kế thừa nó có quyền truy cập vào chúng.

Các trường được bảo vệ không được triển khai trong JavaScript ở cấp độ ngôn ngữ, nhưng trong thực tế, chúng rất thuận tiện.

Bây giờ chúng ta sẽ tạo một máy pha cà phê bằng JavaScript với tất cả các loại thuộc tính này. Một máy pha cà phê có rất nhiều chi tiết, chúng ta sẽ không mô hình chúng để đơn giản (mặc dù chúng ta có thể).

3. protected

Trước tiên hãy tạo một lớp máy pha cà phê đơn giản:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

class CoffeeMachine {
  waterAmount = 0; // the amount of water inside

  constructor(power) {
    this.power = power;
    alert( `Created a coffee-machine, power: ${power}` );
  }

}

// create the coffee machine
let coffeeMachine = new CoffeeMachine(100);

// add water
coffeeMachine.waterAmount = 200;

Ngay bây giờ các thuộc tính waterAmountpowerđược công khai. Chúng ta có thể dễ dàng get / set chúng từ bên ngoài thành bất kỳ giá trị nào.

Hãy thay đổi thuộc tính waterAmount để được bảo vệ và có quyền kiểm soát nhiều hơn đối với nó. Chẳng hạn, chúng ta không muốn ai đặt nó bé hơn 0.

Các thuộc tính được bảo vệ thường được thêm tiền tố vào dấu gạch dưới _.

Điều đó không được thi hành ở cấp độ ngôn ngữ, nhưng có một quy ước nổi tiếng giữa các lập trình viên rằng các thuộc tính và phương thức đó không nên được truy cập từ bên ngoài.

Vì vậy, thuộc tính của chúng tôi sẽ được gọi là _waterAmount:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

class CoffeeMachine {
  _waterAmount = 0;

  set waterAmount(value) {
    if (value < 0) throw new Error("Negative water");
    this._waterAmount = value;
  }

  get waterAmount() {
    return this._waterAmount;
  }

  constructor(power) {
    this._power = power;
  }

}

// create the coffee machine
let coffeeMachine = new CoffeeMachine(100);

// add water
coffeeMachine.waterAmount = -10; // Error: Negative water

Bây giờ quyền truy cập đã được kiểm soát, do đó, việc đặt water dưới 0 sẽ không thành công.

4. Chỉ cho đọc thuộc tính

Đối với thuộc tínhpower, hãy làm cho nó chỉ đọc. Đôi khi xảy ra rằng một thuộc tính phải được đặt tại thời điểm tạo và sau đó không bao giờ được sửa đổi.

Đó chính xác là trường hợp của một máy pha cà phê: power không bao giờ thay đổi.

Để làm như vậy, chúng ta chỉ cần tạo getter chứ không phải setter:

class CoffeeMachine {
  // ...

  constructor(power) {
    this._power = power;
  }

  get power() {
    return this._power;
  }

}

// create the coffee machine
let coffeeMachine = new CoffeeMachine(100);

alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W

coffeeMachine.power = 25; // Error (no setter)

Hàm Getter / setter

Ở đây chúng ta sử dụng cú pháp getter / setter.

Nhưng hầu hết các hàm get.../set... được ưa thích viết như thế này:

class CoffeeMachine {
  _waterAmount = 0;

  setWaterAmount(value) {
    if (value < 0) throw new Error("Negative water");
    this._waterAmount = value;
  }

  getWaterAmount() {
    return this._waterAmount;
  }
}

new CoffeeMachine().setWaterAmount(100);

Điều đó có vẻ lâu hơn một chút, nhưng nó hàm linh hoạt hơn. Họ có thể chấp nhận nhiều đối số (ngay cả khi chúng ta không cần chúng ngay bây giờ).

Mặt khác, cú pháp get / set ngắn hơn, vì vậy cuối cùng không có quy tắc nghiêm ngặt nào, tùy bạn quyết định.

Các trường được bảo vệ cho kế thừa

Nếu chúng ta kế thừa class MegaMachine extends CoffeeMachine, thì không có gì ngăn cản chúng ta truy cập this._waterAmounthoặc this._powertừ các phương thức của lớp mới.

Vì vậy, các trường được bảo vệ khi kế thừa vẫn sử dụng bình thường. Không giống như những quyền riêng tư mà chúng ta sẽ thấy dưới đây.

5. Riêng tư(Private) #waterLimit

Một bổ sung gần đây

Đây là một bổ sung gần đây cho ngôn ngữ. Không được hỗ trợ trong các công cụ JavaScript hoặc được hỗ trợ một phần.

Có một đề xuất JavaScript đã hoàn thành, gần như trong chuẩn, cung cấp hỗ trợ ở cấp độ ngôn ngữ cho các thuộc tính và phương thức riêng tư(private).

Thuộc tính private nên bắt đầu với #. Họ chỉ có thể truy cập từ bên trong lớp.

Ví dụ: đây là một thuộc tính#waterLimit riêng tư và phương thức riêng kiểm tra nước #checkWater:

class CoffeeMachine {
  #waterLimit = 200;

  #checkWater(value) {
    if (value < 0) throw new Error("Negative water");
    if (value > this.#waterLimit) throw new Error("Too much water");
  }

}

let coffeeMachine = new CoffeeMachine();

// can't access privates from outside of the class
coffeeMachine.#checkWater(); // Error
coffeeMachine.#waterLimit = 1000; // Error

Ở cấp độ ngôn ngữ, #là một dấu hiệu đặc biệt cho thấy trường này là riêng tư. Chúng ta không thể truy cập nó từ bên ngoài hoặc từ các lớp kế thừa.

Trường riêng tư không xung đột với những thuộc tính công cộng. Chúng ta có thể có cả trường riêng tư #waterAmountvà công cộng waterAmountcùng một lúc.

Ví dụ: hãy tạo waterAmountmột trình truy cập cho #waterAmount:

class CoffeeMachine {

  #waterAmount = 0;

  get waterAmount() {
    return this.#waterAmount;
  }

  set waterAmount(value) {
    if (value < 0) throw new Error("Negative water");
    this.#waterAmount = value;
  }
}

let machine = new CoffeeMachine();

machine.waterAmount = 100;
alert(machine.#waterAmount); // Error

Không giống như những thuộc tính được bảo vệ, các trường riêng tư được thực thi bởi chính ngôn ngữ. Đó là một điều tốt.

Nhưng nếu chúng ta kế thừa từ CoffeeMachineđó, thì chúng ta sẽ không có quyền truy cập trực tiếp vào #waterAmount. Chúng ta sẽ cần dựa vào waterAmountgetter / setter:

class MegaCoffeeMachine extends CoffeeMachine {
  method() {
    alert( this.#waterAmount ); // Error: can only access from CoffeeMachine
  }
}

Trong nhiều kịch bản giới hạn như vậy là quá nghiêm trọng. Nếu chúng ta mở rộng một CoffeeMachine, chúng ta có thể có lý do chính đáng để truy cập vào bên trong của nó. Đó là lý do tại sao các trường được bảo vệ được sử dụng thường xuyên hơn, mặc dù chúng không được hỗ trợ bởi cú pháp ngôn ngữ.

Các trường riêng tư không có sẵn vì this[name]

Trường riênh tư là đặc biệt.

Như chúng ta biết, thông thường chúng ta có thể truy cập các trường bằng cách sử dụng this[name]:

class User {
  ...
  sayHi() {
    let fieldName = "name";
    alert(`Hello, ${this[fieldName]}`);
  }
}

Với các trường riêng tư là không thể: this['#name']không hoạt động. Đó là một giới hạn cú pháp để đảm bảo sự riêng tư.

6. Tóm lược

Về mặt OOP, việc phân định giao diện bên với giao diện bên ngoài được gọi là tính đóng gói.

Nó mang lại những lợi ích sau: Bảo vệ cho người dùng, cũng như bảo vệ chính đối tượng đó

Hãy tưởng tượng, có một nhóm các developer sử dụng máy pha cà phê. Nó được sản xuất bởi công ty “Best CoffeeMachine” , và hoạt động tốt, nhưng vỏ bảo vệ đã được gỡ bỏ. Vì vậy, giao diện nội bộ được tiếp xúc.

Tất cả các developer đều văn minh – họ sử dụng máy pha cà phê như dự định. Nhưng một trong số họ, David, đã quyết định rằng anh ta là người thông minh nhất, và thực hiện một số điều chỉnh trong bộ phận máy pha cà phê. Thế là hai ngày sau máy cà phê bị hư.

Đó chắc chắn không phải lỗi của David, mà là người đã tháo vỏ bảo vệ và để David thực hiện các thao tác của mình.

Giống nhau trong lập trình. Nếu một người dùng của một lớp có thể thay đổi những thứ không có ý định thay đổi từ bên ngoài – hậu quả là không thể đoán trước.

Hỗ trợ

Tình huống trong lập trình phức tạp hơn so với máy pha cà phê ngoài đời thực, vì chúng ta không chỉ mua nó một lần. Code liên tục trải qua sự phát triển và cải tiến.

Nếu chúng ta phân định nghiêm ngặt giao diện bên trong, thì developer của lớp có thể tự do thay đổi các thuộc tính và phương thức bên trong của nó, ngay cả khi không thông báo cho người dùng.

Nếu bạn là developer của lớp như vậy, thật tuyệt khi biết rằng các phương thức riêng tư có thể được đổi tên một cách an toàn, các tham số của chúng có thể được thay đổi và thậm chí bị xóa, vì không có code bên ngoài nào phụ thuộc vào chúng.

Đối với người dùng, khi một phiên bản mới xuất hiện, nó có thể là một đại tu tổng thể trong nội bộ, nhưng vẫn đơn giản để nâng cấp nếu giao diện bên ngoài giống nhau. Che giấu sự phức tạp

Mọi người ngưỡng mộ sử dụng những thứ đơn giản. Ít nhất là từ bên ngoài. Những gì bên trong là một điều khác biệt.

Lập trình viên không phải là một ngoại lệ.

Thật tiện lợi khi các chi tiết triển khai bị ẩn và giao diện bên ngoài đơn giản, được ghi chép đầy đủ có sẵn.

Để ẩn giao diện nội bộ, chúng ta sử dụng các thuộc tính được bảo vệ hoặc riêng tư:

  • Các trường được bảo vệ bắt đầu với _. Đó là một quy ước nổi tiếng, không được thi hành ở cấp độ ngôn ngữ. Các lập trình viên chỉ nên truy cập vào một trường bắt đầu _từ lớp của nó và các lớp kế thừa từ nó.
  • Trường riêng tư bắt đầu với #. JavaScript đảm bảo chúng ta chỉ có thể truy cập những thứ từ bên trong lớp.

Ngay bây giờ, các trường riêng tư không được hỗ trợ tốt giữa các trình duyệt, nhưng có thể viết được đầy đủ.

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!