PL EN

Wzorzec projektowy Adapter

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