W aplikacji masz bibliotekę, która z dużym prawdopodobieństwem zostanie zastąpiona? Może masz istotny fragment kodu, który używany jest wielokrotnie i pochodzi z zewnętrznych zależności? Lub nowa biblioteka nijak pasuje do starych wywołań w Twoim kodzie? Na takie problemy odpowiada wzorzec Adapter.
Działanie wzorca Adapter polega na utworzeniu nowego spójnego interfejsu z komponentami systemu, poprawiając czytelność i modułowość kodu.
Załóżmy, że w hipotetycznej aplikacji wykorzystujemy bibliotekę do wyświetlania powiadomień.
// ReactComponent.tsx
import notification from "notification-lib"
export const ReactComponent = () => {
const handleData = () => {
/// some JS magic here ///
notification.show({success:'Data sent'})
}
return (
<div>
<button onClick={handleData}>Send Data</button>
</div>
);
};
W przypadku, gdy takich komponentów mamy wiele to wymiana biblioteki staje się czasochłonna.
Wykorzystując wzorzec Adapter utworzymy klasę, która obejmie bibliotekę dodatkową abstrakcją i utworzy uniwersalny interfejs.
// NotificationAdapter.ts
import notification from "notification-lib";
class NotificationAdapter {
static success(message: string) {
notification.show({ success: message });
}
static error(message: string) {
notification.show({ error: message });
}
static warning(message: string) {
notification.show({ warning: message });
}
}
export default NotificationAdapter;
Teraz możemy użyć Adaptera w komponencie.
// ReactComponent.tsx
import NotificationAdapter from "./NotificationAdapter";
export const ReactComponent = () => {
const handleData = () => {
/// some JS magic here ///
NotificationAdapter.success('Data sent');
}
return (
<div>
<button onClick={handleData}>Send Data</button>
</div>
);
};
Test jednostkowy takiego Adaptera w Vitest.
// NotificationAdapter.test.ts
import notification from "notification-lib";
import NotificationAdapter from "./NotificationAdapter";
import { describe, it, expect, vi, afterEach } from "vitest";
vi.mock("notification-lib", () => ({
show: vi.fn(),
}));
describe("NotificationAdapter", () => {
afterEach(() => {
(notification.show as ReturnType<typeof vi.fn>).mockClear();
});
it("should call notification.show with success message", () => {
NotificationAdapter.success("Data sent");
expect(notification.show).toHaveBeenCalledWith({ success: "Data sent" });
});
it("should call notification.show with error message", () => {
NotificationAdapter.error("An error occurred");
expect(notification.show).toHaveBeenCalledWith({ error: "An error occurred" });
});
it("should call notification.show with warning message", () => {
NotificationAdapter.warning("This is a warning");
expect(notification.show).toHaveBeenCalledWith({ warning: "This is a warning" });
});
});
Zalety:
- Dzięki takiemu rozwiązaniu aktualizację w bibliotece notyfikacji dokonujemy już tylko w Adapterze.
- Mamy wydzielone miejsce, w którym określamy sposób wywoływania powiadomień, odseparowując bibliotekę od logiki aplikacji.
Wady:
- Wprowadzamy dodatkowy poziom abstrakcji, który może być mylący dla nowych członków zespołu.
- Korzystając z Adaptera, często ograniczamy się tylko do najczęściej używanych funkcji, dlatego przy specyficznym użyciu klasy należy rozszerzyć Adapter.
Powrót do artykułów