Design patterns
Adapter
Szukaj…
Wzorzec adaptera (PHP)
Przykład ze świata rzeczywistego z wykorzystaniem eksperymentu naukowego, w którym pewne procedury są wykonywane na różnych typach tkanek. Klasa zawiera domyślnie dwie funkcje pobierania tkanki lub rutyny osobno. W późniejszej wersji zaadaptowaliśmy ją za pomocą nowej klasy, aby dodać funkcję, która otrzymuje oba. Oznacza to, że nie edytowaliśmy oryginalnego kodu i dlatego nie ponosimy ryzyka zepsucia naszej istniejącej klasy (i bez ponownego testowania).
class Experiment {
private $routine;
private $tissue;
function __construct($routine_in, $tissue_in) {
$this->routine = $routine_in;
$this->tissue = $tissue_in;
}
function getRoutine() {
return $this->routine;
}
function getTissue() {
return $this->tissue;
}
}
class ExperimentAdapter {
private $experiment;
function __construct(Experiment $experiment_in) {
$this->experiment = $experiment_in;
}
function getRoutineAndTissue() {
return $this->experiment->getTissue().' ('. $this->experiment->getRoutine().')';
}
}
Adapter (Java)
Załóżmy, że w twojej obecnej bazie kodu istnieje interfejs MyLogger
:
interface MyLogger {
void logMessage(String message);
void logException(Throwable exception);
}
Powiedzmy, że utworzyłeś kilka konkretnych implementacji takich, jak MyFileLogger
i MyConsoleLogger
.
Zdecydowałeś, że chcesz użyć platformy do kontrolowania łączności Bluetooth Twojej aplikacji. Ta struktura zawiera BluetoothManager
z następującym konstruktorem:
class BluetoothManager {
private FrameworkLogger logger;
public BluetoothManager(FrameworkLogger logger) {
this.logger = logger;
}
}
BluetoothManager
akceptuje także rejestrator, co jest świetne! Oczekuje jednak, że logger, którego interfejs został zdefiniowany przez platformę, użył przeciążenia metody zamiast nazwać swoje funkcje inaczej:
interface FrameworkLogger {
void log(String message);
void log(Throwable exception);
}
Masz już kilka implementacji MyLogger
, których chciałbyś użyć ponownie, ale nie pasują one do interfejsu FrameworkLogger
. Tutaj pojawia się wzór projektowy adaptera:
class FrameworkLoggerAdapter implements FrameworkLogger {
private MyLogger logger;
public FrameworkLoggerAdapter(MyLogger logger) {
this.logger = logger;
}
@Override
public void log(String message) {
this.logger.logMessage(message);
}
@Override
public void log(Throwable exception) {
this.logger.logException(exception);
}
}
Poprzez zdefiniowanie klasy adaptera, która implementuje interfejs FrameworkLogger
i akceptuje implementację MyLogger
funkcjonalność może być mapowana między różnymi interfejsami. Teraz można korzystać z BluetoothManager
we wszystkich implementacjach MyLogger
takich jak:
FrameworkLogger fileLogger = new FrameworkLoggerAdapter(new MyFileLogger());
BluetoothManager manager = new BluetoothManager(fileLogger);
FrameworkLogger consoleLogger = new FrameworkLoggerAdapter(new MyConsoleLogger());
BluetoothManager manager2 = new BluetoothManager(consoleLogger);
Przykład Java
Świetny istniejący przykład wzorca adaptera można znaleźć w klasach SWT MouseListener i MouseAdapter .
Interfejs MouseListener wygląda następująco:
public interface MouseListener extends SWTEventListener {
public void mouseDoubleClick(MouseEvent e);
public void mouseDown(MouseEvent e);
public void mouseUp(MouseEvent e);
}
Teraz wyobraź sobie scenariusz, w którym budujesz interfejs użytkownika i dodajesz te nasłuchiwania, ale przez większość czasu nie przejmujesz się niczym innym, niż kiedy jedno kliknięcie (mouseUp). Nie chcesz ciągle tworzyć pustych implementacji:
obj.addMouseListener(new MouseListener() {
@Override
public void mouseDoubleClick(MouseEvent e) {
}
@Override
public void mouseDown(MouseEvent e) {
}
@Override
public void mouseUp(MouseEvent e) {
// Do the things
}
});
Zamiast tego możemy użyć MouseAdapter:
public abstract class MouseAdapter implements MouseListener {
public void mouseDoubleClick(MouseEvent e) { }
public void mouseDown(MouseEvent e) { }
public void mouseUp(MouseEvent e) { }
}
Zapewniając puste, domyślne implementacje, możemy zastąpić tylko te metody, na których nam zależy. Zgodnie z powyższym przykładem:
obj.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
// Do the things
}
});
Adapter (UML i przykładowa sytuacja)
Aby uczynić użycie wzorca adaptera i rodzaj sytuacji, kiedy można go zastosować, można sobie wyobrazić, podając tutaj mały, prosty i bardzo konkretny przykład. Nie będzie tu kodu, tylko UML i opis przykładowej sytuacji i jej problemu. Trzeba przyznać, że treść UML jest napisana jak Java. (Cóż, w tekście podpowiedzi było napisane: „Dobre przykłady to w większości kod”. Myślę, że wzorce projektowe są na tyle abstrakcyjne, że można je również wprowadzić w inny sposób.)
Ogólnie wzorzec adaptera jest odpowiednim rozwiązaniem w sytuacji, gdy masz niekompatybilne interfejsy i żadnego z nich nie można bezpośrednio przepisać.
Wyobraź sobie, że prowadzisz niezłą usługę dostarczania pizzy. Klienci mogą zamawiać online na twojej stronie internetowej, a ty masz mały system korzystający z klasycznej Pizza
do reprezentowania twoich pizz i obliczania rachunków, raportów podatkowych i innych. Cena twoich pizzy jest podawana jako jedna liczba całkowita reprezentująca cenę w centach (wybranej waluty).
Twoja usługa dostawy działa świetnie, ale w pewnym momencie nie możesz samodzielnie poradzić sobie z rosnącą liczbą klientów, ale nadal chcesz się rozwijać. Decydujesz się na dodanie swojej pizzy do menu dużej usługi dostarczania meta online. Oferują wiele różnych posiłków - nie tylko pizze - więc ich system w większym stopniu korzysta z abstrakcji i ma interfejs IMeal
reprezentujący posiłki dostarczane wraz z klasą MoneyAmount
reprezentującą pieniądze.
MoneyAmount
składa się z dwóch liczb całkowitych jako danych wejściowych, jednej dla kwoty (lub jakiejś losowej waluty) przed przecinkiem i jednej dla kwoty w MoneyAmount
od 0 do 99 po przecinku;
Ze względu na fakt, że cena Twojej Pizza
jest jedną liczbą całkowitą reprezentującą całkowitą cenę w IMeal
(> 99), nie jest kompatybilna z IMeal
. W tym momencie pojawia się wzorzec adaptera: jeśli zmiana własnego systemu lub utworzenie nowego wymagałoby zbyt dużego wysiłku i konieczne będzie zaimplementowanie niezgodnego interfejsu, może być konieczne zastosowanie wzoru adaptera.
Istnieją dwa sposoby zastosowania wzorca: adapter klasy i adapter obiektu.
Oba mają wspólną cechę, że adapter ( PizzaAdapter
) działa jako swego rodzaju tłumacz między nowym interfejsem a adapttee ( Pizza
w tym przykładzie). Adapter implementuje nowy interfejs ( IMeal
), a następnie dziedziczy po Pizza
i konwertuje własną cenę z jednej liczby całkowitej na dwie (adapter klasy)
lub ma obiekt typu Pizza
jako atrybut i konwertuje wartości tego (adapter obiektu).
Stosując wzorzec adaptera, będziesz w pewien sposób „tłumaczyć” między niekompatybilnymi interfejsami.