4 JavaScript Design Patterns You Should Know
Mỗi developer luôn cố gắng viết các đoạn code có thể bảo trì, dễ đọc và có thể sử dụng lại được. Cấu trúc của code trở nên quan trọng hơn khi ứng dụng ngày càng lớn. Các mẫu thiết kế (design patterns) đã chứng minh được vai trò quan trọng của mình trong việc giải quyết này. Mặc dù có một loạt ...
Mỗi developer luôn cố gắng viết các đoạn code có thể bảo trì, dễ đọc và có thể sử dụng lại được. Cấu trúc của code trở nên quan trọng hơn khi ứng dụng ngày càng lớn. Các mẫu thiết kế (design patterns) đã chứng minh được vai trò quan trọng của mình trong việc giải quyết này. Mặc dù có một loạt các design patterns có thể được sử dụng, tuy nhiên các Javascript developers thường sử dụng các design patterns sau:
- Module
- Prototype
- Observer
- Singleton
Mỗi pattern có chứa rất nhiều thuộc tính, chúng ta chỉ nhấn mạnh vào những đặc điểm chính:
- Context: Trường hợp / dưới hoàn cảnh nào là mẫu được sử dụng?
- Problem: Chúng ta đang cố gắng giải quyết vấn đề gì?
- Solution: Làm thế nào để giải quyết vấn đề đề xuất?
- Implementation: Triển khai thực hiện như thế nào?
Các module JavaScript là các mẫu thiết kế được sử dụng phổ biến nhất để giữ các mẩu đoạn mã độc lập với các thành phần khác. Điều này cung cấp khớp nối lỏng lẻo để hỗ trợ code có cấu trúc tốt hơn.
Đối với những người quen thuộc với các ngôn ngữ hướng đối tượng, module là các javascript's class. Một trong những lợi ích của lớp học là class - bảo vệ các trạng thái và hành vi từ việc truy cập từ các class khác. Module pattern cho phép truy cập vào các cấp độ truy cập public và private.
Modules should be Immediately-Invoked-Function-Expressions (IIFE) to allow for private scopes – that is, a closure that protect variables and methods (however, it will return an object instead of a function). This is what it looks like:
(function() { // declare private variables and/or functions return { // declare public variables and/or functions } })();
Ví dụ dưới đây thể hiện việc bảo vệ truy cập của class với biến bên trong của nó:
let HTMLChanger = (function() { let contents = 'contents' let changeHTML = function() { let element = document.getElementById('attribute-to-change'); element.innerHTML = contents; } return { callChangeHTML: function() { changeHTML(); console.log(contents); } }; })(); HTMLChanger.callChangeHTML(); // Outputs: 'contents' console.log(HTMLChanger.contents); // undefined
Bất kỳ JavaScript developer nào cũng đã thấy từ khóa prototype. Mẫu thiết kế Prototype dựa vào thừa kế nguyên mẫu JavaScript. prototype được sử dụng chủ yếu để tạo ra các đối tượng trong các tình huống thực hiện nhiều công việc.
Các đối tượng được tạo ra là clone (shallow clones) của đối tượng ban đầu được truyền lại
Sơ đồ UML trên mô tả cách một prototype interface được sử dụng để clone trong trường hợp cụ thể.
Chúng ta hãy nhìn vào một ví dụ cơ bản:
var TeslaModelS = function() { this.numWheels = 4; this.manufacturer = 'Tesla'; this.make = 'Model S'; } TeslaModelS.prototype.go = function() { // Rotate wheels } TeslaModelS.prototype.stop = function() { // Apply brake pads }
Constructor cho phép tạo ra một đối tượng TeslaModelS. Khi tạo một đối tượng TeslaModelS mới, nó sẽ giữ lại các trạng thái khởi tạo trong hàm tạo. Ngoài ra, việc duy trì (maintain) chức năng go và stop là trở lên dễ dàng hơn kể từ khi chúng ta khai báo nó cùng với prototype. Một cách khác để khai báo 2 chức năng go và stop cho kết quả tương tự với đoạn code ở trên:
var TeslaModelS = function() { this.numWheels = 4; this.manufacturer = 'Tesla'; this.make = 'Model S'; } TeslaModelS.prototype = { go: function() { // Rotate wheels }, stop: function() { // Apply brake pads } }
Có nhiều lần khi một phần của ứng dụng thay đổi, các phần khác cần phải được cập nhật. Trong AngularJS, nếu cập nhật đối tượng trong $$cope, một sự kiện có thể được kích hoạt để thông báo cho các thành phần khác. Mẫu observer kết hợp thực hiện - nếu một đối tượng được sửa đổi nó sẽ thông báo tới các đối tượng phụ thuộc khi mà một sự thay đổi đã xảy ra. Một ví dụ khác chính là kiến trúc model-view-controller (MVC). Giao diện cập nhật khi model thay đổi.
As shown in the UML diagram, the necessary objects are the subject, observer, and concrete objects. The subject contains references to the concrete observers to notify for any changes. The Observer object is an abstract class that allows for the concrete observers to implements the notify method. Như được biểu diễn trong sơ đồ UML, các đối tượng cần thiết là subject, observer và các đối tượng cụ thể. Subject có chứa tham chiếu đến các observers cụ thể để thông báo cho bất kỳ thay đổi. Đối tượng Observer là một lớp trừu tượng cho phép các observers cụ thể thực hiện phương thức thông báo.
Dưới đây là 1 ví dụ với AngularJS về observer:
// Controller 1 $scope.$on('nameChanged', function(event, args) { $scope.name = args.name; }); ... // Controller 2 $scope.userNameChanged = function(name) { $scope.$emit('nameChanged', {name: name}); };
Với mô hình observer, điều quan trọng là phải phân biệt đối tượng độc lập hoặc subject.
Mặc dù mô hình observer mang đến nhiều lợi ích, tuy nhiên một trong những nhược điểm của nó đó là sự sụt giảm đáng kể về hiệu suất khi số lượng observer hoặc object tăng lên. Trong AngularJS, chúng ta có thể quan sát các biến, hàm và các đối tượng.
Chúng ta có thể tự tạo Subjects và Observers trong JavaScript.
var Subject = function() { this.observers = []; return { subscribeObserver: function(observer) { this.observers.push(observer); }, unsubscribeObserver: function(observer) { var index = this.observers.indexOf(observer); if(index > -1) { this.observers.splice(index, 1); } }, notifyObserver: function(observer) { var index = this.observers.indexOf(observer); if(index > -1) { this.observers[index].notify(index); } }, notifyAllObservers: function() { for(var i = 0; i < this.observers.length; i++){ this.observers[i].notify(i); }; } }; }; var Observer = function() { return { notify: function(index) { console.log("Observer " + index + " is notified!"); } } } var subject = new Subject(); var observer1 = new Observer(); var observer2 = new Observer(); var observer3 = new Observer(); var observer4 = new Observer(); subject.subscribeObserver(observer1); subject.subscribeObserver(observer2); subject.subscribeObserver(observer3); subject.subscribeObserver(observer4); subject.notifyObserver(observer2); // Observer 2 is notified! subject.notifyAllObservers(); // Observer 1 is notified! // Observer 2 is notified! // Observer 3 is notified! // Observer 4 is notified!
Một Singleton chỉ cho phép cho một khởi tạo đơn, nhưng nhiều phiên bản của cùng một đối tượng. Singleton hạn chế clients tạo ra nhiều đối tượng, sau khi đối tượng đầu tiên được tạo ra, nó sẽ trả về các phiên bản của chính nó.
Tìm kiếm các trường hợp sử dụng Singletons rất khó cho hầu hết những người chưa sử dụng nó trước đó. Một ví dụ là sử dụng một máy in văn phòng. Nếu có mười người trong một văn phòng, và tất cả đều sử dụng một máy in, mười máy tính chia sẻ một máy in. Bằng cách chia sẻ một máy in, họ chia sẻ cùng một nguồn lực. Tức là chúng ta không cần thiết phải mua 10 cái máy in để phục vụ cho 10 người, chỉ cần 1 là đủ
var printer = (function () { var printerInstance; function create () { function print() { // underlying printer mechanics } function turnOn() { // warm up // check for paper } return { // public + private states and behaviors print: print, turnOn: turnOn }; } return { getInstance: function() { if(!printerInstance) { printerInstance = create(); } return printerInstance; } }; function Singleton () { if(!printerInstance) { printerInstance = intialize(); } }; })();
Phương thức create cần được set private vì chúng ta không muốn client truy cập vào nó. Mỗi người có thể tạo một instance của máy in bằng việc sử dụng phương thức getInstance:
let officePrinter = printer.getInstance();