
Wzorzec projektowy Adapter
2025/04/08
Wprowadzenie
Wzorce projektowe to rozwiązania popularnych problemów programistycznych. Wyróżniamy następujące grupy wzorców:
- wzorce kreacyjne, które zajmują się tworzeniem obiektów, np. Fabryka, Budowniczy, Singleton.
- wzorce strukturalne, które pomagają grupować obiekty w większe struktury, np. Adapter, Dekorator.
- wzorce behawioralne, które wspomagają definiowanie ścieżek komunikacyjnych między obiektami, np. Obserwator, Strategia.
Adapter jest strukturalnym wzorcem projektowym. Pozwala na współdziałanie ze sobą obiektów o niekompatybilnych interfejsach.
Problem i rozwiązanie
Otrzymaliśmy zadanie polegające na dostosowaniu się do nowego interfejsu dostępowego miejskiej biblioteki. Aktualnie nasz system posiada własny interfejs, który komunikuje się z dotychczasowym interfejsem dostępowym biblioteki. Nadszedł jednak czas zmian i pojawiła się jego nowa wersja.
Na pierwszy rzut oka pomyślelibyśmy, no żaden problem… w sumie wystarczy dostosować interfejs systemu do nowego API biblioteki. Jednak po dłuższym zastanowieniu i przeanalizowaniu za i przeciw, okazuje się że zmiany te mogą nieco naruszyć istniejący kod. A jak wiadomo zmiany w istniejącym kodzie zawsze niosą ryzyko popełnienia błędów.
Z pomocą przychodzi wzorzec Adapter, który jest w stanie przekształcić aktualny interfejs systemu tak, aby mógł współpracować z nowym interfejsem biblioteki bez zmian w istniejącym kodzie. Adapter zachowa się jak osłona, która przysłoni starą implementację i zastąpi nową zawartą w swoim ciele (podmiana starych metod na metody z nowego interfejsu, czyli pośrednictwo lub konwersja danych).
Schemat działania
Diagram UML przedstawiający schemat działania systemu komunikującego się z API biblioteki przed pojawieniem się nowego API, gdzie klasa BookConnector to właśnie system, który przyjmuje jako argument interfejs API biblioteki i obiekt użytkownika.

Diagram UML przedstawiający schemat działania po połączeniu systemu do nowego API za pomocą Adaptera. Wprowadzenie wzorca Adapter rozbudowało nieco schemat przez co złożoność kodu wzrosła (można to zaliczyć do wad tego wzorca). Działanie systemu, czyli klasy BookConnector nie uległo zmianie, ponieważ w dalszym ciągu przyjmuje te same artumenty w konstruktorze. Dzieje się tak, ponieważ klasa APIAdapter implementuje stary interfejs, po to, aby móc połączyć się z systemem po staremu, ale jako argumenty przyjmuje nowy interfejs API i obiekt użytkownika.

Implementacja
Struktura folderów i plików projektu
- adapter
- api
- LibraryAPI.php
- LibraryAPIInterface.php
- LibraryAPIv2.php
- LibraryAPIv2Interface.php
- app
- APIAdapter.php
- BookConnector.php
- User.php
- index.php
- api
Kod źródłowy
Plik LibraryAPIInterface.php
<?php
declare(strict_types=1);
namespace api;
use DateTime;
interface LibraryAPIInterface
{
public function isAvailable(string $bookTitle): bool;
public function dueDate(string $bookTitle, string $pesel): DateTime;
public function reserve(string $bookTitle, string $pesel): bool;
}
Plik LibraryAPI.php
<?php
declare(strict_types=1);
namespace api;
use DateTime;
use api\LibraryAPIInterface;
class LibraryAPI implements LibraryAPIInterface
{
public function isAvailable(string $bookTitle): bool
{
echo "Sprawdzam dostępność: {$bookTitle}<br/>";
return true;
}
public function dueDate(string $bookTitle, string $pesel): DateTime
{
echo "Sprawdzam datę zwrotu dla: {$bookTitle}<br/>";
return new DateTime();
}
public function reserve(string $bookTitle, string $pesel): bool
{
echo "Rezerwuję: {$bookTitle}<br/>";
return true;
}
}
Plik LibraryAPIv2Interface.php
<?php
declare(strict_types=1);
namespace api;
use DateTime;
interface LibraryAPIv2Interface
{
public function numberOfAvailableCopies(string $bookTitle): int;
public function dueDate(string $bookTitle, string $firstName, string $lastName, DateTime $dateOfBirth): DateTime;
public function reserve(string $bookTitle, string $firstName, string $lastName, DateTime $dateOfBirth): bool;
}
Plik LibraryAPIv2.php
<?php
declare(strict_types=1);
namespace api;
use DateTime;
use api\LibraryAPIv2Interface;
class LibraryAPIv2 implements LibraryAPIv2Interface
{
public function numberOfAvailableCopies(string $bookTitle): int
{
echo "Sprawdzam dostępność: {$bookTitle}<br/>";
return 3;
}
public function dueDate(string $bookTitle, string $firstName, string $lastName, DateTime $dateOfBirth): DateTime
{
echo "Sprawdzam datę zwrotu dla: {$bookTitle}<br/>";
return new DateTime();
}
public function reserve(string $bookTitle, string $firstName, string $lastName, DateTime $dateOfBirth): bool
{
echo "Rezerwuję: {$bookTitle}<br/>";
return true;
}
}
Plik User.php
<?php
declare(strict_types=1);
namespace app;
use DateTime;
class User
{
private string $firstName;
private string $lastName;
private DateTime $dateOfBirth;
private string $PESEL;
public function __construct(string $firstName, string $lastName, string $PESEL)
{
$this->firstName = $firstName;
$this->lastName = $lastName;
$this->PESEL = $PESEL;
$this->dateOfBirth = new DateTime();
}
public function getPESEL(): string
{
return $this->PESEL;
}
public function getFirstName(): string
{
return $this->firstName;
}
public function getLastName(): string
{
return $this->lastName;
}
public function getDateOfBirth(): DateTime
{
return $this->dateOfBirth;
}
}
Plik BookConnector.php
<?php
declare(strict_types=1);
namespace app;
use DateTime;
use api\LibraryAPIInterface;
use app\User;
class BookConnector
{
private User $user;
private LibraryAPIInterface $api;
public function __construct(User $user, LibraryAPIInterface $api)
{
$this->user = $user;
$this->api = $api;
}
public function checkAvailability(string $title): bool
{
return $this->api->isAvailable($title);
}
public function reserve(string $title, User $user): bool
{
return $this->api->reserve($title, $user->getPESEL());
}
public function whenToReturn(string $title, User $user): DateTime
{
return $this->api->dueDate($title, $user->getPESEL());
}
}
Plik APIAdapter.php
<?php
declare(strict_types=1);
namespace app;
use DateTime;
use api\{LibraryAPIInterface, LibraryAPIv2Interface};
class APIAdapter implements LibraryAPIInterface
{
private LibraryAPIv2Interface $apiV2;
private User $user;
public function __construct(LibraryAPIv2Interface $apiV2, User $user)
{
$this->apiV2 = $apiV2;
$this->user = $user;
}
public function isAvailable(string $bookTitle): bool
{
return $this->apiV2->numberOfAvailableCopies($bookTitle) > 0;
}
public function dueDate(string $bookTitle, string $pesel): DateTime
{
return $this->apiV2->dueDate($bookTitle, $this->user->getFirstName(), $this->user->getLastName(), $this->user->getDateOfBirth());
}
public function reserve(string $bookTitle, string $pesel): bool
{
return $this->apiV2->reserve($bookTitle, $this->user->getFirstName(), $this->user->getLastName(), $this->user->getDateOfBirth());
}
}
Plik index.php
<?php
declare(strict_types=1);
require_once __DIR__ . "/api/LibraryAPIInterface.php";
require_once __DIR__ . "/api/LibraryAPI.php";
require_once __DIR__ . "/api/LibraryAPIv2Interface.php";
require_once __DIR__ . "/api/LibraryAPIv2.php";
require_once __DIR__ . "/app/User.php";
require_once __DIR__ . "/app/BookConnector.php";
require_once __DIR__ . "/app/APIAdapter.php";
use api\{LibraryAPI, LibraryAPIv2};
use app\{BookConnector, User, APIAdapter};
echo "<br/> -- Stary interfejs Library API -- <br/>";
$api = new LibraryAPI();
$user = new User("Mateusz", "Przybyla", "11111111111");
$connector = new BookConnector($user, $api);
$connector->checkAvailability("Harry Potter i Zakon Feniksa");
echo "<br/> -- Nowy interfejs Library API -- <br/>";
$user = new User("Mateusz", "Przybyla", "11111111111");
$libraryAPIv2 = new LibraryAPIv2();
$apiAdapter = new APIAdapter($libraryAPIv2, $user);
$connector = new BookConnector($user, $apiAdapter);
$connector->checkAvailability("Harry Potter i Zakon Feniksa");
Wynik działania:
-- Stary interfejs Library API --
Sprawdzam dostępność: Harry Potter i Zakon Feniksa
-- Nowy interfejs Library API --
Sprawdzam dostępność: Harry Potter i Zakon Feniksa
Podsumowanie
Na powyższym przykładzie najpierw sprawdziliśmy czy występuje dana książka w bibliotece za pomocą starego API. Następnie to samo zapytanie uczyniliśmy do nowego API, które wykorzystuje klasę Adapter jako łącznik pomiędzy systemem, a nowym interfejsem biblioteki miejskiej. Oczywiście otrzymaliśmy ten sam rezultat.
Dzięki zastosowaniu wzorca spełniamy Zasadę pojedynczej odpowiedzialności (1 zasadę SOLID), ponieważ możemy oddzielić kod konwertujący dane od logiki biznesowej. Równocześnie spełniamy Zasadę otwarte/zamknięte (2 zasada SOLID), ponieważ wprowadziliśmy do programu nową klasę APIAdapter bez wpływu na istniejący kod.