Wzorzec ten należy do wzorców czynnościowych, czyli opisujących sposób przepływu danych w złożonych aplikacjach.
Dodatkowo na potrzeby klasy Nowosci utworzona została klasa przechowująca informacje o użytkownikach:
PHP:
class Uzytkownik {
private $strImie = \'\';
private $strNazwisko = \'\';
private $strEMail = \'\';
public function __construct($strImie, $strNazwisko, $strEMail) {
$this->strImie = $strImie;
$this->strNazwiko = $strNazwisko;
$this->strEMail = $strEMail;
}
public function getImie() {
return $this->strImie;
}
public function getNazwisko() {
return $this->strNazwisko;
}
public function getEMail() {
return $this->strEMail;
}
public function setImie($strImie) {
$this->strImie = $strImie;
}
public function setNazwisko($strNazwisko) {
$this->strNazwisko = $strNazwisko;
}
public function setEMail($strEMail) {
$this->strEMail = $strEMail;
}
}
Java:
public class Uzytkownik {
private String imie;
private String nazwisko;
private String eMail;
public Uzytkownik(String imie, String nazwisko, String eMail) {
this.imie = imie;
this.nazwisko = nazwisko;
this.eMail = eMail;
}
public String getImie() {
return imie;
}
public String getNazwisko() {
return nazwisko;
}
public String getEMail() {
return eMail;
}
public void setImie(String imie) {
this.imie = imie;
}
public void setNazwisko(String nazwisko) {
this.nazwisko = nazwisko;
}
public void setEMail(String eMail) {
this.eMail = eMail;
}
}
Jednym ze sposobów sporządzenia skryptu,
który wywoływałby odpowiednie metody obiektów powyższych klas, jest napisanie prostego skryptu, który zadba o stworzenie poszczególnych obiektów i wykonanie zaplanowanych operacji:
PHP:
class Zadania {
public static function wykonaj() {
$objRSS = new RSS();
$objRSS->generujPlikXML();
$objAtom = new Atom();
$objAtom->generujPlikXML();
$objNBP = new NBP();
$objNBP->aktualizuj();
$objNowosci = new Nowosci();
$objNowosci->wyslijNewsletter($objNowosci->pobierzOsobyZapisane());
}
}
Zadania::wykonaj();
Java:
public class Test1 {
public static void main(String[] argc) {
RSS rss = new RSS();
rss.generujPlikXML();
Atom atom = new Atom();
atom.generujPlikXML();
NBP nbp = new NBP();
nbp.aktualizuj();
Nowosci nowosci = new Nowosci();
nowosci.wyslijNewsletter(nowosci.pobierzOsobyZapisane());
}
}
Rozwiązania to ma jedną wadę – wraz z dodawaniem
kolejnych zadań, które będą wykonywane w tym samym czasie, konieczna będzie modyfikacja
kodu. Lecz wcale nie to jest najgorsze. Część z tych zadań użytkownik będzie prawdopodobnie mógł wykonać także w innych miejscach serwisu (np. z poziomu panelu administracyjnego). Tam też ich kod zostanie zdublowany.
Rozwiązaniem tego (jak i innych – nieco więcej o nich piszemy dalej) problemu jest zastosowanie wzorca projektowego Polecenie.
Użycie wzorca polecenie polega na dokonaniu enkapsulacji do osobnych obiektów czynności, które mają być wykonane, wraz z ich parametrami, koniecznymi do wykonania polecenia. Dodatkowo dla wygody obsługi ustalamy wspólny interfejs dla wszystkich poleceń:
PHP:
interface Polecenie {
public function wykonaj();
}
Java:
public interface Polecenie {
public void wykonaj();
}
Jednym z poleceń może być wygenerowanie nowych plików XML dla kanałów RSS oraz Atom:
PHP:
class GenerowanieKanalow implements Polecenie
{
public function wykonaj() {
$objRSS = new RSS();
$objRSS->generujPlikXML();
$objAtom = new Atom();
$objAtom->generujPlikXML();
}
}
Java:
public class GenerowanieKanalow implements Polecenie {
public void wykonaj() {
RSS rss = new RSS();
rss.generujPlikXML();
Atom atom = new Atom();
atom.generujPlikXML();
}
}
Analogicznie należy utworzyć polecenia aktualizujące
kursy walut oraz rozsyłające newslettera. W sumie dla naszego problemu tworzymy 3 polecenia.
Teraz nasz skrypt wykonujący odpowiednie działania o zaplanowanej porze może mieć postać:
PHP:
class Zadania {
public static function wykonaj() {
$objGK = new GenerowanieKanalow;
$objGK->wykonaj();
$objAKW = new AktualizacjaKursowWalut();
$objAKW->wykonaj();
$objRN = new RozsylanieNewslettera();
$objRN->wykonaj();
}
}
Zadania::wykonaj();
Java:
public class Test2 {
public static void main(String[] argc) {
GenerowanieKanalow gk = new GenerowanieKanalow();
gk.wykonaj();
AktualizacjaKursowWalut akw = new AktualizacjaKursowWalut();
akw.wykonaj();
RozsylanieNewslettera rn = new RozsylanieNewslettera();
rn.wykonaj();
}
}
Może się wydawać, że jest to niewielkie usprawnienie, a nawet krok wstecz – wygląda to tak, jakbyśmy nic nie zyskali, a tylko dodali dodatkowo niepotrzebnie trzy klasy i interfejs.
Rozwiązanie takie ma jednak szereg zalet:
- obiekty poleceń można swobodnie przekazywać pomiędzy obiektami potrafiącymi z poleceń skorzystać – zapewnia to wspólny interfejs,
- obiekty poleceń można wykorzystać do tworzenia
makropoleceń (przykład dalej), - podany kod można jeszcze dodatkowo usprawnić
poprzez konfigurowanie poleceń, które mają zostać wykonane w zewnętrznym pliku.
Zajmijmy się ostatnim punktem, czyli konfigurowaniem
poleceń do wykonania. Konfiguracjętaką można zapisać w pliku XML, .ini, YAML czy dowolnym innym, który będziemy w stanie sparsować
z poziomu wybranego języka programowania. Konfiguracjatakamożemiećpostaćzbliżonąnp.do takiego pliku XML (to tylko przykład, który możesz zastąpić innym, lepiej odpowiadającym twojemu sposobowi konfiguracji):
GenerowanieKanalow
AktualizacjaKursowWalut
RozsylanieNewslettera
Konfiguracje w tej postaci można następnie przeanalizować i na jej podstawie utworzyć obiekty odpowiednich klas, umieszczając je w kolekcji (np. w tablicy dla PHP bądź liście ArrayList w Javie). W przykładach niżej pominąłem parsowanie XML-
a, gdyż nie to jest tematem tego artykułu, przechodząc
automatycznie do momentu, w którym mamy już kolekcję zawierającą polecenia do wykonania:
PHP:
class Zadania {
public static function wykonaj($arrPolecenia)
{
foreach ($arrPolecenia as $objPolecenie)
{
$objPolecenie->wykonaj();
}
}
}
/*
* Poniższa instrukcja powinna zostać
* zastąpiona odczytaniem konfiguracji
* i stworzeniem tablicy na podstawie
* jej zawartości
*/
$arrPolecenia = array(
new GenerowanieKanalow(),
new AktualizacjaKursowWalut(),
new RozsylanieNewslettera()
);
Zadania::wykonaj($arrPolecenia);
Java:
public class Test3 {
public static void main(String[] argc) {
/*
* Poniższa instrukcja powinna zostać
* zastąpiona odczytaniem konfiguracji
* i stworzeniem tablicy (bądź kolekcji)
* na podstawie jej zawartości
*/
Polecenie[] polecenia = {
new GenerowanieKanalow(),
new AktualizacjaKursowWalut(),
new RozsylanieNewslettera()
};
for (Polecenie p: polecenia) {
p.wykonaj();
}
}
}
Prawda, że zaczyna to wyglądać ładne i przejrzyście?
Jak wspomniałem wcześniej, za pomocą wzorca
Polecenie można tworzyć makra. Oto przykład polecenia służącego do budowania makropoleceń:
PHP:
class MakroPolecenie implements Polecenie {
private $arrPolecenia = array();
public function __construct($arrPolecenia = null) {
if ($arrPolecenia !== null) {
$this->arrPolecenia = $arrPolecenia;
}
}
public function dodajPolecenie(Polecenie $objPolecenie) {
$this->arrPolecenia[] = $objPolecenie;
}
public function wykonaj() {
foreach ($this->arrPolecenia as $objPolecenie)
{
$objPolecenie->wykonaj();
}
}
}
Java:
public class MakroPolecenie implements Polecenie {
private List polecenia;
public MakroPolecenie() {
polecenia = new ArrayList();
}
public MakroPolecenie(List polecenia) {
this.polecenia = polecenia;
}
public void dodajPolecenie(Polecenie polecenie) {
polecenia.add(polecenie);
}
public void wykonaj() {
for (Polecenie p: polecenia) {
p.wykonaj();
}
}
}
A oto sposób użycia makropolecenia:
PHP:
class Zadania {
public static function wykonaj($arrPolecenia)
{
foreach ($arrPolecenia as $objPolecenie)
{
$objPolecenie->wykonaj();
}
}
}
$objMakro = new MakroPolecenie(array(new AktualizacjaKursowWalut(), new RozsylanieNewslettera()));
$objMakro->dodajPolecenie(new GenerowanieKanalow());
$arrPolecenia = array($objMakro);
Zadania::wykonaj($arrPolecenia);
Java:
public class Test4 {
public static void main(String[] argc) {
ArrayList mp = new ArrayList();
mp.add(new AktualizacjaKursowWalut());
mp.add(new RozsylanieNewslettera());
MakroPolecenie makro = new MakroPolecenie(mp);
Polecenie[] polecenia = {
makro, new GenerowanieKanalow()
};
for (Polecenie p: polecenia) {
p.wykonaj();
}
}
}
Korzystając z poleceń, można nawet w pewnych
sytuacjach (tam, gdzie da się taką operację zaimplementować) dodać możliwość wycofywania zmian dokonanych przez te polecenia. Rozwiązanie problemu wycofywania zmian pozostawię ci już jednak, Drogi Czytelniku, jako zadanie domowe.
Nadeszła pora, aby zapoznać się z kolejnym wzorcem projektowym. Tym razem będzie nim Polecenie (ang. Command). Wzorzec ten należy do wzorców czynnościowych (dla przypomnienia,
wzorce czynnościowe opisują sposób przepływu
danych w złożonych aplikacjach). Tak jak to było w przypadku wszystkich poznanych do tej pory wzorców, tak i w tym wypadku nazwa Polecenie doskonale
oddaje ideę jego działania (co zapewne sam stwierdzisz po przeczytaniu niniejszego artykułu).
Program komputerowy jest zbiorem instrukcji sterowanych przez użytkowników bądź przez pewne
zdarzenia. Niestety najczęściej instrukcje te są obiektami posiadającymi zupełnie odmienne interfejsy,
w związku z czym w niektórych przypadkach zarządzanie nimi staje się problematyczne.
Przeanalizujmy przykład występujący często w aplikacjach internetowych – cykliczne wykonywanie
pewnych zadań. Problem ten rozwiązuje się, podpinając program (skrypt) pod uniksowego Crona, zaś w przypadku webowych aplikacji napisanych w Javie, posłużyć się można Quartzem (http://www.opensymphony.com/quartz/). Jak jednak rozwiązać sam problem dodawania zadań wykonywanych cyklicznie?
Załóżmy, że w pewnych odcinkach czasu mamy jednocześnie dokonać wygenerowania plików XML dla RSS-a i Atoma, wysłać e-maile z informacjami o nowościach do osób, które zapisały się na news-letter oraz pobrać aktualne kursy walut z serwisu NBP (np. na potrzeby przeliczenia cen towarów w sklepie internetowym). Dla ułatwienia wszystkie te operacje wykonywane są w tym samym czasie, np. raz na dzień. A oto interfejsy klas wykonujących
odpowiednie zadania:
Interfejs wspólny dla klas generujących kanały RSS oraz Atom:
PHP:
interface Kanaly {
public function generujPlikXML();
}
Java:
public interface Kanaly {
public void generujPlikXML();
}
Interfejs klasy wysyłającej e-maile z newsletterem
prezentuje się następująco:
PHP:
interface Newsletter {
public function zapisz($objUzytkownik);
public function wypisz($objUzytkownik);
public function pobierzOsobyZapisane();
public function wyslijNewsletter($arrAdresyEMail);
}
Java:
public interface Newsletter {
public void zapisz(Uzytkownik u);
public void wypisz(Uzytkownik u);
public List pobierzOsobyZapisane();
public void wyslijNewsletter(ListadresyEMail);
}
I na koniec interfejs klasy pobierającej aktualne kursy walut z serwisu NBP:
PHP:
interface KursyWalut {
public function aktualizuj();
public function pobierzKurs($strWaluta);
}
Java:
public interface KursyWalut {
public void aktualizuj();
public double pobierzKurs(String waluta);
}
Same implementacje klas wykonujących odpowiednie
zadania pozostawimy puste. Zadbamy jedynie o wyświetlanie odpowiednich informacji, tak abyś mógł łatwo prześledzić proces wykonywania
programu:
PHP:
class Atom implements Kanaly {
public function generujPlikXML() {
echo \"Generowanie pliku Atom\n\";
}
}
class RSS implements Kanaly {
public function generujPlikXML() {
echo \"Generowanie pliku RSS\n\";
}
}
class NBP implements KursyWalut {
public function aktualizuj() {
echo \"Aktualizacja kursów walut z portalu NBP\n\";
}
public function pobierzKurs($strWaluta) {
echo \"Sprawdzanie kursu waluty $strWaluta\n\";
return 1.00;
}
}
class Nowosci implements Newsletter {
public function zapisz($objUzytkownik) {
echo \"Dodawanie uzytkownika {$objUzytkownik->getImie()}\n\";
}
public function wypisz($objUzytkownik) {
echo \"Usuwanie uzytkownika {$objUzytkownik->getImie()}\n\";
}
public function pobierzOsobyZapisane() {
echo \"Pobieranie listy osob zapisanych do newslettera\n\";
return array(new Uzytkownik(\'Jan\', \'Kowalski\', \'jan@kowalski.pl\'), new Uzytkownik(\'Anna\', \'Kowalska\', \'anna@kowalska.pl\'));
}
public function wyslijNewsletter($arrAdresyEMail) {
echo \"Wysylanie newslettera do:\n\";
foreach ($arrAdresyEMail as $objUzytkownik) {
echo \" - {$objUzytkownik->getImie()} {$objUzytkownik->getNazwisko()} ({$objUzytkownik->getEMail()})\n\";
}
}
}
Java:
public class Atom implements Kanaly {
public void generujPlikXML() {
System.out.println(\"Generowanie pliku Atom\");
}
}
public class RSS implements Kanaly {
public void generujPlikXML() {
System.out.println(\"Generowanie pliku RSS\");
}
}
class NBP implements KursyWalut {
public void aktualizuj() {
System.out.println(\"Aktualizacja kursów walut z portalu NBP\n\");
}
public double pobierzKurs(String waluta) {
System.out.println(\"Sprawdzanie kursu waluty \"+waluta);
return 1.00;
}
}
import java.util.ArrayList;
import java.util.List;
public class Nowosci implements Newsletter {
public void zapisz(Uzytkownik u) {
System.out.println(\"Dodawanie uzytkownika \"+u.getImie());
}
public void wypisz(Uzytkownik u) {
System.out.println(\"Usuwanie uzytkownika \"+u.getImie());
}
public List pobierzOsobyZapisane() {
System.out.println(\"Pobieranie listy osob zapisanych do newslettera\");
ArrayList uzytkownicy = new ArrayList();
uzytkownicy.add(new Uzytkownik(\"Jan\", \"Kowalski\", \"jan@kowalski.pl\"));
uzytkownicy.add(new Uzytkownik(\"Anna\", \"Kowalska\", \"anna@kowalska.pl\"));
return uzytkownicy;
}
public void wyslijNewsletter(ListadresyEMail) {
System.out.println(\"Wysylanie newslettera do:\");
for (Uzytkownik u: adresyEMail) {
System.out.println(u.getImie()+\" \"+u.getNazwisko()+\" (\"+u.getEMail()+\")\");
}
}
}