Xơn Space

    My bullshit stories

    Back

    Observer Pattern là gì?

    logo

    Hoàng Sơn

    published at:13/12/2024 at 10:41 AM- view

    thumbail

    1. Định nghĩa

    Với Observer Pattern, chúng ta có thể đăng ký (thêm) một số đối tượng, được gọi là "observer", vào một đối tượng khác, được gọi là "observable" .

    Mỗi khi một event ( sự kiện ) xảy ra, đối tượng "observable" sẽ notify (thông báo) cho tất cả các "observer" của nó!

    2. Cấu trúc

    Một đối tượng observable thường chứa 3 phần quan trọng:

    • observers: một mảng các observer sẽ nhận thông báo mỗi khi một sự kiện cụ thể xảy ra.
    • subscribe(): một phương thức để thêm observer vào danh sách observers.
    • unsubscribe(): một phương thức để xóa observer khỏi danh sách observers.
    • notify(): một phương thức để thông báo cho tất cả các observer mỗi khi một sự kiện cụ thể xảy ra.

    3. Triển Khai

    Đây là flow đơn giản về Observer Pattern mà chúng ta sẽ triển khai. Trong ví dụ này mình sử dụng Javascript ES6 class nhé.

    parsed-mdx-image

    Bây giờ, chúng ta thử tạo một "observable" class nha.

    class Observable {
      constructor() {
        this.observers = [];
      }
    
      subscribe(func) {
        this.observers.push(func);
      }
    
      unsubscribe(func) {
        this.observers = this.observers.filter((observer) => observer !== func);
      }
    
      notify(data) {
        this.observers.forEach((observer) => observer(data));
      }
    }
    

    Ở đây, chúng ta có thể thêm observers vào danh sách các observers bằng phương thức subscribe, xóa các observers bằng phương thức unsubscribe, và thông báo cho tất cả các observers đã đăng ký bằng phương thức notify.

    Tiếp theo, ví dụ chúng ta có một ứng dụng rất đơn giản chỉ bao gồm hai elements: một Button (handleClick) và một Switch (handleToggle).

    export default function App() {
      return (
        <div className="App">
          <Button>Click me!</Button>
          <FormControlLabel control={<Switch />} />
        </div>
      );
    }
    

    Chúng ta muốn theo dõi sự tương tác của người với ứng dụng. Mỗi khi người dùng nhấn vào button hoặc nhấn vào switch, chúng ta muốn ghi log lại cùng với timestamp mà người dụng thực hiện hành động. Bên cạnh việc ghi log, chúng ta cũng muốn tạo một toast xuất hiện mỗi khi một sự kiện xảy ra! (Xem lại diagram ở trên nhe).

    Mỗi khi người dùng gọi hàm handleClick hoặc handleToggle, các hàm này sẽ gọi phương thức notify trên observer. Phương thức notify sẽ thông báo cho tất cả các subscriber với data được truyền từ hàm handleClick hoặc handleToggle.

    Trước tiên, hãy tạo các hàm loggertoastify. Các hàm này sẽ nhận dữ liệu từ phương thức notify.

    import { ToastContainer, toast } from "react-toastify";
    
    function logger(data) {
      console.log(`${Date.now()} ${data}`);
    }
    
    function toastify(data) {
      toast(data);
    }
    
    export default function App() {
      return (
        <div className="App">
          <Button>Click me!</Button>
          <FormControlLabel control={<Switch />} />
          <ToastContainer />
        </div>
      );
    }
    

    Hiện tại, các hàm loggertoastify chưa biết về observable: observable chưa thể thông báo cho chúng! Để biến chúng thành observers, chúng ta phải đăng ký chúng, bằng cách sử dụng phương thức subscribe trên observable.

    observable.subscribe(logger);
    observable.subscribe(toastify);
    

    Mỗi khi một sự kiện xảy ra, các hàm loggertoastify sẽ nhận được thông báo. Bây giờ chúng ta chỉ cần triển khai các hàm thực sự thông báo cho observable: các hàm handleClickhandleToggle. Những hàm này sẽ gọi phương thức notify trên observable và truyền dữ liệu mà các observers sẽ nhận.

    import { ToastContainer, toast } from "react-toastify";
    
    function logger(data) {
      console.log(`${Date.now()} ${data}`);
    }
    
    function toastify(data) {
      toast(data);
    }
    
    observable.subscribe(logger);
    observable.subscribe(toastify);
    
    export default function App() {
    
      function handleClick() {
        observable.notify("User clicked button!");
      }
    
      function handleToggle() {
        observable.notify("User toggled switch!");
      }
    
      return (
        <div className="App">
          <Button>Click me!</Button>
          <FormControlLabel control={<Switch />} />
          <ToastContainer />
        </div>
      );
    }
    

    Như vậy, chúng ta vừa hoàn thành toàn bộ quy trình: các hàm handleClickhandleToggle gọi phương thức notify trên observable với data, sau đó observable sẽ thông báo cho các subscriber: trong trường hợp này là các hàm loggertoastify.

    Mỗi khi người dùng tương tác với bất kỳ element nào (trong ví dụ trên là Button và Switch), cả hai hàm loggertoastify sẽ nhận được thông báo với data mà chúng ta đã truyền vào phương thức notify!

    Đây là full code cho ví dụ trên:

    // Observable.js
    class Observable {
      constructor() {
        this.observers = [];
      }
    
      subscribe(func) {
        this.observers.push(func);
      }
    
      unsubscribe(func) {
        this.observers = this.observers.filter((observer) => observer !== func);
      }
    
      notify(data) {
        this.observers.forEach((observer) => observer(data));
      }
    }
    
    export default new Observable();
    
    // App.jsx
    import React from "react";
    import { Button, Switch, FormControlLabel } from "@material-ui/core";
    import { ToastContainer, toast } from "react-toastify";
    import observable from "./Observable";
    
    import observable from "./Observable";
    
    function handleClick() {
    	observable.notify("User clicked button!");
    }
    
    function handleToggle() {
    	observable.notify("User toggled switch!");
    }
    
    function logger(data) {
    	console.log(`${Date.now()} ${data}`);
    }
    
    function toastify(data) {
    	toast(data, {
    		position: toast.POSITION.BOTTOM_RIGHT,
    		closeButton: false,
    		autoClose: 2000
    	});
    }
    
    observable.subscribe(logger);
    observable.subscribe(toastify);
    
    export default function App() {
    
      return (
        <div className="App">
    	    <Button variant="contained" onClick={handleClick}>
    	        Click me!
    	    </Button>
    	    
    	    <FormControlLabel
    		    control={<Switch name="" onChange={handleToggle} />}
    		    label="Toggle me!"
    		/>
    
          <ToastContainer />
        </div>
      );
    }
    
    • Mặc dù chúng ta có thể sử dụng observer pattern theo nhiều cách, nhưng nó sẽ rất hữu ích khi làm việc với dữ liệu bất đồng bộ, dựa trên sự kiện.

    • Có thể chúng ta muốn các thành phần nhất định nhận thông báo mỗi khi dữ liệu cụ thể đã download xong, hoặc khi người dùng gửi tin nhắn mới vào nhóm chat và tất cả các thành viên khác cần nhận được thông báo.

    4. Case Study

    Một thư viện phổ biến có sử dụng Observer Pattern đó chính là RxJS.

    "ReactiveX combines the Observer pattern with the Iterator pattern and functional programming with collections to fill the need for an ideal way of managing sequences of events. - RxJS"

    ""

    ReactiveX kết hợp mô hình Observer với mô hình Iterator và lập trình hàm với các tập hợp để đáp ứng nhu cầu về một cách lý tưởng để quản lý các chuỗi sự kiện. - RxJS

    Một ví dụ cơ bản về RxJS.

    import React from "react";
    import ReactDOM from "react-dom";
    import { fromEvent, merge } from "rxjs";
    import { sample, mapTo } from "rxjs/operators";
    import "./styles.css";
    
    merge(
    	fromEvent(document, "mousedown").pipe(mapTo(false)),
    	fromEvent(document, "mousemove").pipe(mapTo(true))
    	)
    	.pipe(sample(fromEvent(document, "mouseup")))
    	.subscribe(isDragging => {
    		console.log("Were you dragging?", isDragging);
    	});
    	
    ReactDOM.render(
    	<div className="App">Click or drag anywhere and check the console!</div>,
    	document.getElementById("root")
    );
    

    RxJS có rất nhiều tính năng và ví dụ tích hợp sẵn hoạt động với mô hình observable.

    5. Ưu điểm và nhược điểm

    Các đối tượng observer không bị liên kết chặt chẽ với đối tượng observable và có thể được (kết nối) hoặc (ngắt kết nối) vào bất kỳ lúc nào. Đối tượng observable chịu trách nhiệm theo dõi các sự kiện, trong khi các observer chỉ đơn giản xử lý dữ liệu nhận được. Điều này giúp chúng ta đảm bảo được phân tách các mối quan tâm giữa các observable và cũng như các trách nhiệm vụ, logic riêng lẻ của chúng.

    Thế nhưng, nếu một observer trở nên quá phức tạp, nó có thể gây ra vấn đề hiệu suất khi thông báo cho tất cả các subscriber.

    6. Tổng kết

    Như vậy, chúng ta đã hiểu đại khái về Observer Pattern và cách triển khai đơn giản của pattern này.

    Cảm ơn mọi người đã dành thời gian xem qua bài viết của mình, hi vọng sẽ giúp ích được một tí gì đó.

    Mọi thông tin trong bài viết mình có tham khảo ở đây Nếu mọi người muốn đọc bài viết gốc thì click vào link trên nhé.

    See yaa!

    Design Pattern

    Dev

    Javascript

    logo

    Hoàng Sơn

    Cảm ơn bạn đã dành thời gian đọc qua bài viết trên của mình, nếu có bất kỳ câu hỏi gì, thì cứ nhắn tin cho mình nhé. Hi vọng mình đã giúp ích cho bạn 'một phần nào đấy'.

    There are 0 comments on this post

    Comment

    Your email address will not be published. Required fields are marked * are required.