W tym artykule zaprezentujemy prosty wzorzec projektowy z rodziny wzorców strukturalnych. Przyda się on w sytuacji, gdy zostaniesz z partiami kodu korzystającego ze starych i nierozwijanych już bibliotek.
Wyobraź sobie sytuacje, gdy masz kod
korzystający z pewnej biblioteki wspomagającej
rozsyłanie poczty elektronicznej.
Wszystko działa świetnie, a biblioteki tej z powodzeniem
używasz w kilku swoich aplikacjach.
Jednak w pewnym momencie prace nad biblioteką
zostają porzucone. Nie pozostaje nic innego, jak
przyjrzeć się rozwiązaniom alternatywnym i wybrać
jedno z nich. To nie problem w przypadku nowych
aplikacji. Co jednak ze starymi projektami?
I tu z pomocą przychodzi Adapter.
Przykładowa aplikacja
Jak zawsze zaczniemy od prostego kodu, na którego
przykładzie zaprezentujemy działanie adaptera.
Oprzemy się tu na pseudobibliotece służącej do
rozsyłania poczty elektronicznej. Oto interfejs klasy
Email:
interface IEmail {
public function setTemat($strTemat);
public function
setNadawca($strNadawca);
public function setTresc($strTresc);
public function setFormat($strFormat);
public function wyslij($strDo);
}
class Email implements IEmail {
private $strTemat;
private $strNadawca;
private $strTresc;
private $strFormat; //\'HTML\', \'TXT\'
public function setTemat($strTemat) {
$this->strTemat = $strTemat;
}
public function setNadawca($strNadawca) {
$this->strNadawca = $strNadawca;
}
public function setTresc($strTresc) {
$this->strTresc = $strTresc;
}
public function setFormat($strFormat) {
$this->strFormat = $strFormat;
}
public function wyslij($strDo) {
//tutaj kod wysyłający e-mail
}
}
public interface IEmail {
public void setTemat(String temat);
public void setNadawca(String nadawca);
public void setTresc(String tresc);
public void setFormat(String format);
public void wyslij(String adresDo);
}
public class Email implements IEmail {
private String temat;
private String nadawca;
private String tresc;
private String format; //\"HTML\", \"TXT\"
public void setTemat(String temat) {
this.temat = temat;
}
public void setNadawca(String nadawca)
{
this.nadawca = nadawca;
}
public void setTresc(String tresc) {
this.tresc = tresc;
}
public void setFormat(String format) {
this.format = format;
}
public void wyslij(String adresDo) {
//kod wysyłający e-mail
}
}
Ponadto mamy klasę (nasza miniaplikacja, którą
będziemy chcieli zmusić do korzystania z nowej
biblioteki), stosującą klasę Email:
class Newsletter {
private $objEmail;
public function __construct(IEmail$objEmail) {
$this->objEmail = $objEmail;
}
public function setNadawca($strNadawca)
{
$this->objEmail->setNadawca($strNadawca);
}
public function wyslij($arrAdresy, $strTemat, $strTresc) {
$this->objEmail->setTemat($strTemat);
$this->objEmail->setTresc($strTresc);
$this->objEmail->setFormat(‘HTML\');
foreach ($arrAdresy as $strAdres) {
$this->objEmail->wyslij($strAdres);
}
}
}
import java.util.ArrayList;
public class Newsletter {
private IEmail email;
public Newsletter(IEmail email) {
this.email = email;
}
public void setNadawca(String nadawca) {
email.setNadawca(nadawca);
}
public void wyslij(ArrayList
adresy, String temat, String tresc) {
email.setTemat(temat);
email.setTresc(tresc);
email.setFormat(\"HTML\");
for (String adres: adresy) {
email.wyslij(adres);
}
}
}
Naszym problemem jest zmiana interfejsu klasy
Emial. Nowa klasa wysyłająca pocztę elektroniczną
ma następującą postać:
class NowyEmail {
public function wyslij($strTemat,$strTresc, $strDo, $strOd) {
//wysyłanie e-maila
}
}
public class NowyEmail {
public void wyslij(String temat, String tresc, String adresDo, String adresOd) {
//wysyłanie e-maila
}
}
Jak widzisz, interfejs uległ diametralnej zmianie.
Teraz wszystkie dane należy podać bezpośrednio
w parametrach wywołania metody wyslij(),
nie zaś za pomocą setterów. Dodatkowo nie da
się ustalić formatu treści e-maila – zawsze jest on
wysyłany jako HTML.
Na pierwszy rzut oka wydaje się, że nie
obejdzie się bez modyfikacji klasy Newsletter.
A jednak rozwiązanie problemu jest bardzo
proste. Jak niemal zawsze w przypadku wzorców
projektowych, opieramy się na interfejsach (stąd
jak zapewne zauważyłeś klasa Email implementuje
interfejs IEmail – interfejsy są bardzo pomocne,
gdy zachodzi potrzeba zmodyfikowania kodu
i zastosowania wzorców projektowych).
Rozwiązanie całego problemu sprowadza się do stworzenia
klasy adaptera, implementującego interfejs IEmail,
a do wysyłania poczty używającego nowej klasy.
Jest to więc swojego rodzaju obudowanie naszej
nowej biblioteki interfejsem starej biblioteki:
class EmailAdapter implements IEmail {
private $objNowyEmail;
private $strTemat;
private $strNadawca;
private $strTresc;
private $strFormat; //\'HTML\', \'TXT\'
public function __construct() {
$this->objNowyEmail = newNowyEmail();
}
public function setTemat($strTemat) {
$this->strTemat = $strTemat;
}
public function setNadawca($strNadawca){
$this->strNadawca = $strNadawca;
}
public function setTresc($strTresc) {
$this->strTresc = $strTresc;
}
public function setFormat($strFormat) {
//zostawiamy jedynie dla zgodności z interfejsem, gdyż nasza nowa klasa
//nie umożliwia wyboru formatu e-maila
}
public function wyslij($strDo) {
$this->objNowyEmail->wyslij($this->strTemat, $this->strTresc, $strDo, $this->strNadawca);
}
}
public class EmailAdapter implements
IEmail {
private NowyEmail email;
private String temat;
private String nadawca;
private String tresc;
private String format; //\"HTML\", \"TXT\"
public EmailAdapter() {
email = new NowyEmail();
}
public void setTemat(String temat) {
this.temat = temat;
}
public void setNadawca(String nadawca){
this.nadawca = nadawca;
}
public void setTresc(String tresc) {
this.tresc = tresc;
}
public void setFormat(String format) {
//zostawiamy jedynie dla zgodności z interfejsem, gdyż nasza nowa klasa
//nie umożliwia wyboru formatu e-maila
}
public void wyslij(String adresDo) {
email.wyslij(temat, tresc, adresDo,nadawca);
}
}
I to już wszytko! Jedna prosta klasa opakowująca
nową bibliotekę w stary interfejs. Zapewne
widzisz już, jak to działa.
Klasa Newsletter wysyła żądanie do adaptera,
myśląc, że wysyła je do naszej starej biblioteki
(Newsletter zna jedynie wspólny interfejs starej biblioteki
i naszego adaptera, a więc nie wie, że coś
się zmieniło). Następnie adapter tłumaczy żądanie
przychodzące na wywołania zrozumiałe dla nowej
biblioteki. W efekcie tego e-maile są wysyłane
za pomocą nowej biblioteki bez zmian w starym
kodzie!
Adapter w pigułce
Jak zwykle, zanim użyjemy biblioteki, wyodrębniamy
z niej interfejs i to nim się posługujemy.
O ile w przypadku własnych bibliotek
można zadbać samemu o to, aby korzystały
one z interfejsów, o tyle w przypadku bibliotek
zewnętrznych różnie niestety bywa – ale
w ramach zadania domowego zrób to, co
wyżej, z wykorzystaniem dziedziczenia, bez
interfejsów – w tym przypadku też się da!
Tworzymy klasę adaptera opakowującą nową
bibliotekę w stary interfejs – musimy tutaj
zmapować nowe wywołania, tak aby dało się
je obsługiwać za pomocą starego interfejsu.
Testujemy nową klasę, używając jej w starym
kodzie i nie ryzykując popełnienia błędu
w trakcie jego modyfikacji!