Adapter Pattern với javascript

date
thumbnail
slug
adapter-pattern
status
Published
tags
Javascript
Architecture
ReactJS
summary
type
Post

Adaper pattern là gì?

Adapter pattern là một mẫu pattern được sử dụng để cho phép các class có cấu trúc khác nhau hoạt động với nhau. Nó giúp chúng ta giải quyết vấn đề khi cần sử dụng một class hoặc library mà không cần thay đổi cấu trúc của class hoặc library đó. Adapter pattern cũng giúp chúng ta tách riêng các component của system và giúp cho việc bảo trì và mở rộng system dễ dàng hơn. Nó rất quan trọng trong việc giải quyết vấn đề tương thích giữa các class hoặc library khác nhau.

Cách sử dụng Adapter pattern?

Để sử dụng Adapter pattern, chúng ta tạo ra một class Adapter, class này sẽ làm một "most" giữa class cần sử dụng và class muốn sử dụng. Class Adapter sẽ thực hiện các method của class cần sử dụng và chuyển đổi các parameters và kết quả thành đúng với cấu trúc của class muốn sử dụng.
Ví dụ, nếu chúng ta có một class A và class B, và chúng ta muốn sử dụng class B nhưng class A không có các phương thức cần thiết. Chúng ta có thể tạo ra một class Adapter C để làm một "most" giữa class A và class B. Class Adapter C sẽ thực hiện các method của class B và chuyển đổi các parameters và kết quả thành đúng với cấu trúc của class A.
Một ví dụ cụ thể về việc sử dụng Adapter pattern với component button trong React (sử dụng Function Component) là khi bạn cần sử dụng button trong một hệ thống mà các hàm sự kiện của button phải được thực hiện theo cách khác so với cách hiện tại. Ví dụ, giả sử button hiện tại chỉ hỗ trợ sự kiện onClick và bạn cần sử dụng sự kiện onMouseOver. Để làm điều này, bạn có thể tạo một function component "Button Adapter” như sau.
const ButtonAdapter = ({ onMouseOver, onClick }) => {
	const handleMouseOver = () => {
	// code xử lý sự kiện onMouseOver
	};
	const handleClick = () => {// code xử lý sự kiện onClick
	};
	
	return (
		<button onMouseOver={handleMouseOver} onClick={handleClick}>
		Click me
		</button>
	);
};
//Bạn có thể sử dụng component ButtonAdapter như bình thường, nhưng với các sự kiện onMouseOver và onClick được xử lý theo cách mà bạn muốn.
<ButtonAdapter onMouseOver={() => console.log("Mouse over")} onClick={() => console.log("Clicked")}/>
Với ví dụ trên, khi người dùng hover vào button thì sẽ in ra "Mouse over" và khi click vào button sẽ in ra "Clicked”

Khi nào sử dụng Adapter pattern hợp lý và khi nào nên tránh sử dụng nó?

Trong Adapter pattern, chúng ta sử dụng một đối tượng adapter để chuyển đổi giữa interface của một đối tượng và interface mà client mong muốn sử dụng. Điều này cho phép các đối tượng không tương thích với nhau có thể làm việc cùng nhau bằng cách sử dụng adapter để chuyển đổi giữa các interface khác nhau.
Ví dụ, trong trường hợp của component button, nếu bạn cần sử dụng button trong một hệ thống mà các hàm sự kiện của button phải được thực hiện theo cách khác so với cách hiện tại, bạn có thể sử dụng một Adapter để chuyển đổi các hàm sự kiện của button thành những hàm sự kiện mà bạn mong muốn.
Khi nào nên tránh:
  • Khi các đối tượng cần sử dụng đều có chung một interface, thì không cần sử dụng Adapter pattern.
  • Khi sự thay đổi trong các đối tượng cần sử dụng là ít, hoặc không cần thay đổi, thì không cần sử dụng Adapter pattern.
  • Khi việc sử dụng Adapter pattern làm cho code trở nên phức tạp và khó hiểu, thì nên tránh sử dụng nó.

Liệu Adapter pattern có thể thay thế các mẫu thiết kế khác không? Nếu có, thì trong trường hợp nào?

Adapter pattern là một mẫu thiết kế riêng biệt và có mục đích riêng, nó không thay thể hoàn toàn các mẫu khác. Adapter pattern giúp cho các interface không tương thích có thể làm việc cùng nhau bằng cách chuyển đổi giữa các interface. Còn các mẫu thiết kế khác có thể giải quyết các vấn đề khác nhau và có thể được sử dụng trong các trường hợp khác nhau.
Tuy nhiên, Adapter pattern có thể được sử dụng cùng với các mẫu thiết kế khác để giải quyết các vấn đề khác nhau. Ví dụ, nó có thể được sử dụng cùng với Bridge pattern để tách ra một abstraction từ một implementation hoặc cùng với Decorator pattern để thêm các tính năng mới cho một đối tượng.
ví dụ:
import React, { useState } from "react";

// Implementor interface
const TV = {
  on: () => console.log("TV is turning on"),
  off: () => console.log("TV is turning off"),
  tuneChannel: (channel) => console.log(`Tuning to channel ${channel}`),
};

// Concrete Implementor
const OldTV = {
  ...TV,
  on: () => console.log("Click! Old TV is turning on"),
  off: () => console.log("Click! Old TV is turning off"),
  flipSwitch: () => console.log("Flipping switch"),
  changeTube: (channel) => console.log(`Changing tube to channel ${channel}`),
};

// Adapter
const OldTVAdapter = (oldTV) => {
  const tv = {
    ...TV,
    on: () => oldTV.flipSwitch(),
    off: () => oldTV.flipSwitch(),
    tuneChannel: (channel) => oldTV.changeTube(channel),
  };
  return tv;
};

// Refined Abstraction
function AdvancedRemoteControl({ tvImplementation }) {
  const [channel, setChannel] = useState(1);
  return (
    <div>
      <button onClick={() => tvImplementation.on()}>Turn On</button>
      <button onClick={() => tvImplementation.off()}>Turn Off</button>
      <button onClick={() => tvImplementation.tuneChannel(channel)}>
        Tune to Channel {channel}
      </button>
      <input
        type="number"
        value={channel}
        onChange={(e) => setChannel(e.target.value)}
      />
    </div>
  );
}

// Usage
const oldTV = OldTVAdapter(OldTV);

function App() {
  return (
    <div>
      <AdvancedRemoteControl tvImplementation={oldTV} />
    </div>
  );
}

export default App;

© Dinh Vo 2022 - 2026