Adapter Obiektów

Pomysł na ten wpis jest taki, że na początek, napiszę testy jednostkowe, które będą palić się na czerwono, w których zdefiniuje problem. Testy zapalę na zielono poprzez implementację wzorca Adapter.

Adapter przekształca interfejs klas na inny, oczekiwany przez klienta. Adapter umożliwia współdziałanie klasom, które z uwagi na niezgodne interfejsy standardowo nie mogą współdziałać ze sobą.

„Wzorce Projektowe”, Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides

Adapter dokonuje konwersji danej klasy do postaci innego interfejsu, zgodnie z oczekiwaniami klienta. Adapter pozwala na współpracę klas, które ze względu na niekompatybilne interfejsy wcześniej nie mogły ze sobą współpracować. {…}. Dzięki temu kod klienta nie musi być modyfikowany za każdym razem, kiedy będzie współpracować z innym interfejsem.

„Head First Design Patterns” Erich Freeman, Elisabeth Freeman, Bert Bates, Kathy Sierra

Adapter Obiektów

Na początek, napiszę test, w którym zdefiniuję problem. Po czym napiszę kod produkcyjny, który zapali ten test na zielono.

public class AdapterTester
{
  private readonly Client client;

  public AdapterTester()
  {
    client = new Client();
  }

  [Fact]
  public void call_request_method_in_target()
  {
    // arrange
    ITarget target = A.Fake();

    // act
    client.CallRequest(target);

    // assert
    A.CallTo(() => target.Request()).MustHaveHappened();
  }
}

Clinet posiada metodę CallRequest, która w argumencie pobiera obiekt spełniający interfejs ITarget. W teście sprawdzamy, czy w wyniku wywołania metody CallRequest została wywołana metoda Request na obiekcie target.

Aby ten test zapalił się na zielono potrzebna jest definicja interfejsu ITarget oraz implementacja klasy Client.

public interface ITarget
{
  void Request();
}

public class Client
{
  public void CallRequest(ITarget target)
  {
    target.Request();
  }
}

Teraz zdefiniuję kolejny test, którego zapalenie na zielono będzie wymagało napisania wzorca Adapter.

public class AdapterTester
{
  // ... pozostały kod jest niezmieniony
  [Fact]
  public void call_specific_request_method_in_adaptee()
  {
    // arrange
    IAdaptee adaptee = A.Fake();
    ITarget adapter = new Adapter(adaptee);

    // act
    client.CallRequest(adapter);

    // assert
    A.CallTo(() => adaptee.SpecificRequest()).MustHaveHappened();
  }
}

W teście tym pojawił się obiekt typu IAdaptee. IAdaptee posiada metodę SpecificRequest, co powoduje, że interfejs IAdaptee jest inny od ITarget. Klient nie będzie umieć współpracować z obiektem typu IAdapteeIAdaptee wymaga adaptacji do współdziałania z metodą CallRequest z klasy Client.

IAdaptee przekazany jest do konstruktora nowej klasy Adapter, która implementuje interfejs ITarget. Wewnątrz klasy Adapter nastąpi przetłumaczenie interfejsu IAdaptee na interfejs ITarget. W ostatniej linijce testu jest asercja mówiąca o tym, że w wyniku przetłumaczenia IAdaptee na ITarget, jeśli klient wywoła metodę Request na obiekcie typu ITarget to w rzeczywistości wywoła się metoda SpecificRequest na obiekcie typu IAdaptee.

Jak widać w teście, klasa Client pozostaje bez zmian — to ważne założenie. Adaptacja nowego interfejsu wykonywana jest bez najmniejszej zmiany klasy, do której ten nowy interfejs adaptujemy.

Test pali się teraz na czerwono. Kod się nie kompiluje. Definiuję teraz ten nowy interfejs, który będę adaptować.

public interface IAdaptee
{
  void SpecificRequest();
}

Teraz definiuję klasę Adapter, która w konstruktorze przyjmuje obiekt typu IAdaptee. Klasa Adapter implementuje interfejs ITarget, czyli ten pierwotny, do którego się adaptujemy.

public class Adapter : ITarget
{
  private readonly IAdaptee adaptee;

  public Adapter(IAdaptee adaptee)
  {
    this.adaptee = adaptee;
  }

  public void Request()
  {
    adaptee.SpecificRequest();
  }
}

Diagram klas wzorca Adapter Obiektów

Wzorzec Adapter Obiektów

2 Comments

  1. Eleganckie wytłumaczenie 🙂 No i super to wygląda przy wykorzystaniu ‚składni’ frameworków fejkujących (FakeItEasy), prościej zadeklarować co ma się wydarzyć się nie da: „adaptee.SpecificRequest()).MustHaveHappened();”

    Polubienie

    Odpowiedz

Dodaj komentarz