Prawidłowe programowanie wymaga właściwego zastosowania
wzorców projektowych. Zaczynamy od wprowadzenia pojęcia
wzorców projektowych, przedstawienia historii ich rozwoju i
klasyfikacji oraz omówienia trzech - dość prostych, ale
ważnych wzorców - Factory, Flyweight, Singleton. W dalszym
toku wykładów przedstawione zostaną inne - wybrane - wzorce
projektowe.
Nazwa wzorca | Kategoria | Zastosowanie | Często używane z | Pokrewne do |
---|---|---|---|---|
Abstract Factory | Konstrukcyjne | Uzyskiwanie instancji | Factory Method Prototype Singleton + Facade |
Factory Method Prototype Singleton |
Adapter | Strukturalne | Dostosowanie interfejsu | - | Bridge Decorator Proxy |
Command | Behawioralne | Separowanie akcji |
Composite | Composite Memento Prototype |
Composite | Strukturalne | Strukturalna kompozycja/dekompozycja obiektów-systemów | - | Decorator Iterator Visitor |
Decorator | Strukturalne | łatwe uzupełnianie o dodatkowe właściwości/ funkcjonalność |
- | Object Adapter Composite Strategy |
Facade | Strukturalne | Ułatwienia dostępu | Singleton Abstract Factory | Abstract Factory Mediator |
Flyweight | Strukturalne | Współdzielenie zasobów = ograniczanie wymagań pamięciowych |
- | Singleton State Strategy Shareable |
Iterator | Behawioralne | Nawigacja |
- | Composite Factory Method Memento |
Observer | Behawioralne | Komunikacja, uniezależnienie fragmentów kodu |
- | Mediator Singleton |
Proxy | Strukturalne | Kontrolowanie dostępu |
- | Adapter Decorator |
Singleton | Konstrukcyjne | Kontrolowanie dostępu | - | Abstract Factory Builder Prototype |
State | Behawioralne | Zmiany stanów obiektu |
Flyweight | Flyweight Singleton |
Strategy | Behawioralne | Implementacja algorytmu | - | Flyweight State Template Method |
Template Method | Behawioralne | Implementacja algorytmu |
- | Strategy |
Bridge | Strukturalne | Implementacja | - | Abstract Factory Class Adaptor |
Builder | Konstrukcyjne | Tworzenie struktur | - | Abstract Factory Composite |
Chain of Responsibility | Behawioralne | Organizacja przepływu zadań | - | Composite |
Factory Method | Konstrukcyjne | Tworzenie obiektów | Template Method | Abstract Factory Template Method Prototype |
Mediator | Behawioralne | Interakcja między obiektami |
- | Facade Observer |
Prototype | Konstrukcyjne | Tworzenie obiektów | - | Prototype Composite Decorator |
Visitor | Behawioralne | "Double-dispatching" (zob. następne wykłady) |
Composite |
Composite Visitor |
Interpreter | Behawioralne | Organizacja zadań |
- | Composite Flyweight Iterator Visitor |
Memento | Behawioralne | Zarządzanie obiektami | - | Command Iterator |
interface MsgDisplay { void show(String msg); } // Mamy dwie rózne implementacje class ConsoleDisplay implements MsgDisplay { public void show(String s) { System.out.println(s); } } class DialogDisplay implements MsgDisplay { public void show(String s) { JOptionPane.showMessageDialog(null, s ); } }
MsgDisplay msg = new ConsoleDisplay(); // w wielu miejscach w kodzie! msg.show("Bad"); // wiele zmian, jeśli zmianiamy impl.
class MsgDisplayFactory { public static MsgDisplay getInstance() { // zmiana implementacji TYLKO TU return new DialogDisplay(); } }
MsgDisplay msg = MsgDisplayFactory.getInstance(); // tu nie ma zmian msg.show("Good");Zauważmy, że działanie metod fabrycznych może zależeć od parametrów (albo inaczej ustalanych kontekstów), podawanych w fazie wykonania programu. Klasy klienckie nie troszczą się o konkretny typ wyniku (wiedzą, że zawsze dostaną właściwy), a metody fabryczne zwracają obiekty różnych klas - zależnie od kontekstu.
public static Calendar getInstance() { Calendar cal = createCalendar(TimeZone.getDefaultRef(), Locale.getDefault()); cal.sharedZone = true; return cal; } private static Calendar createCalendar(TimeZone zone, Locale aLocale) { // If the specified locale is a Thai locale, returns a BuddhistCalendar // instance. if ("th".equals(aLocale.getLanguage()) && ("TH".equals(aLocale.getCountry()))) { return new sun.util.BuddhistCalendar(zone, aLocale); } // else create the default calendar return new GregorianCalendar(zone, aLocale); }Źródło: Sun
public class ClassicSingleton { private static ClassicSingleton instance = null; private ClassicSingleton() { // prywatny konstruktor } public static ClassicSingleton getInstance() { if(instance == null) { instance = new ClassicSingleton(); } return instance; } }
class KlasaSingletonu { private static KlasaSingletonu obj; private KlasaSingletonu() { // ... } public static KlasaSingletonu getInstance() { if (obj == null) obj = new KlasaSingletonu(); return obj; } }jeden z wątków może zostać wywłaszczony zaraz po sprawdzania warunku (obj == null), przychodzący na jego miejsce drugi wątek może stworzyć obiekt, a przywrócony pierwszy - "pamiętając", że obiektu nie było - też go stworzy.
public static synchronized KlasaSingletonu getInstance() { if (obj == null) obj = new KlasaSingletonu(); return obj; }Warto zauważyć jednak, że taka synchronizacja jest potrzebna tylko przy pierwszym wywołaniu metody getInstance(). Wszystkie inne wywołania będą miały charakter odczytu, nie muszą zatem być synchronizowane, a ponieważ synchronizacja jest kosztowna - to chcielibyśmy jej uniknąć.
public static KlasaSingletonu getInstance() { if (obj == null) { // w tym if (...) mamy ten sam problem co poprzednio synchronized(KlasaSingletonu.class) { obj = new KlasaSingletonu(); } } return obj; }Aby uniknąć synchronizowanych odwołań powinniśmy zatem stworzyć obiekt przy pierwszym odwołaniu do klasy, co można uzyskać np. tak:
class KlasaSingletonu { private static final KlasaSingletonu obj = new KlasaSingletonu(); private KlasaSingletonu() { // prywatny konstruktor // ... } public static KlasaSingletonu getInstance() { return obj; } }
public class Singleton implements java.io.Serializable { public static Singleton INSTANCE = new Singleton(); private Singleton() { // Exists only to thwart instantiation. } private Object readResolve() { return INSTANCE; } }Źródło: David Geary. op.cit.
final class Dimension { private int width; private int height; public Dimension(int w, int h) { width = w; height = h; } public int getWidth() { return width; } public int getHeight() { return height; } public String toString() { return width + "x" + height; } }Rozmiary pudełek będzie nam dostarczać fabryka rozmiarów pudełek (klasa BoxDimensionFactory). Będzie ona zawierała tablicę najczęściej używanych rozmiarów.
class BoxDimensionFactory { // często występujące szerokości pudełek private int[] widths = { 10, 20, 30, 40, 50, 60, 70 }; // Tablica rzomiarów pudełek - do ponownego użycia (współdzielenia) private Dimension[] d = new Dimension[widths.length]; private int reused; // ile razy ponownie użyto gotowego rozmiaru // Singleton // --- odniesienie do jedynego obiektu fabryki private static BoxDimensionFactory bdf; // --- prywatny konstruktor private BoxDimensionFactory() {} // --- metoda zwracająca fabrykę public static BoxDimensionFactory getInstance() { if (bdf == null) bdf = new BoxDimensionFactory(); // jeżeli obiekt nie istnieje -stwórz return bdf; // zwróć jedyny obiekt klasy } // Metoda fabryczna // zwraca referencję do obiekty klasy Dimension public Dimension makeDimension(int w) { for (int i=0; i < widths.length; i++) if (w == widths[i]) { // jeżeli często występujący rozmiar // jeżeli używany pierwszy raz - utwórz go i zapisz do tablicy if (d[i] == null) d[i] = new Dimension(w, 2*w); else reused++; // jeżeli już był utworzony - zwiększ licznik ponownego użycia return d[i]; // zwróć rozmiar - z tablicy "ponownego użycia" } return new Dimension(w, 2*w); // jeżeli jakiś inny rozmiar - utwórz go i zwróć } // Zwraca liczbę ponownego użycia rozmiarów public int reusedCount() { return reused; } }
class Box { private Dimension dim; private String cont; public Box(Dimension d, String c) { dim = d; cont = c; } public String toString() { return "Pudełko: " + dim + " Zawartość: " + cont; } } class BoxTest { public static void main(String[] args) { // Pobranie fabryki rozmiarów BoxDimensionFactory boxDimFac = BoxDimensionFactory.getInstance(); // na jakie pudełka jest teraz zapotrzebowanie int[] potrzebne = { 10, 10, 10, 20, 30, 45, 20, 20, 20, 20, 10, 20, 50, 65, 50, 50, 60, 100, 50, 50, 50, }; // Kolejne pudełka tworzymy na podstawie rozmiarów // uzyskanych z fabryki rozmiarów // nie wiemy i nie interesuje nas czy rozmiary to nowe obiekty // czy też już używane przez inne pudełka Box box = null; for (int i = 0; i < potrzebne.length; i++) box = new Box(boxDimFac.makeDimension(potrzebne[i]), "Kwiaty"); System.out.println("Ostatnie pudełko"); System.out.println(box); System.out.println("Na " + potrzebne.length + " rozmiarów pudełek\n" + "Utworzono nowych " + (potrzebne.length - boxDimFac.reusedCount()) + "\nPonownie użyto (przy wspóldzieleniu) " + boxDimFac.reusedCount() ); } }
// Klasa "kwiaciarnia" class Florist { public Florist() { // Ustalenie cennika PriceList pl = PriceList.getInstance(); pl.set("róża", 10); pl.set("bez", 12); pl.set("piwonia", 8); } } // Klasa testująca class FloristsTest { // tu definicja metody valueOf(Box pudelko, String kolor) zwracającej // sumaryczną wartość kwiatów o podanym kolorzez, znajdujących się w pudełku public static void main(String[] args) { // Kwiaciarnia samoobsługowa Florist kwiaciarnia = new Florist(); // Przychodzi klient janek. Ma 200 zł Customer janek = new Customer("Janek", 200); // Bierze różne kwiaty: 5 róż, 5 piwonii, 3 frezje, 3 bzy janek.get(new Rose(5)); janek.get(new Peony(5)); janek.get(new Freesia(3)); janek.get(new Lilac(3)); // Pewnie je umieścił na wózku sklepowyem // Zobaczmy co tam ma ShoppingCart wozekJanka = janek.getShoppingCart(); System.out.println("Przed płaceniem\n" + wozekJanka); // Teraz za to zapłaci... janek.pay(); // Czy przypadkiem przy płaceniu nie okazało się, // że w wozku są kwiaty na które nie ustalono jescze cceny? // W takim razie zostałyby usunięte z wózka i Janek nie płaciłby za nie System.out.println("Po zapłaceniu\n" + janek.getShoppingCart()); // Ile Jankowi zostało pieniędzy? System.out.println("Jankowi zostało : " + janek.getCash() + " zł"); // Teraz jakos zapakuje kwiaty (może do pudełka) Box pudelkoJanka = new Box(janek); janek.pack(pudelkoJanka); // Co jest teraz w wózku Janka... // (nie powinno już nic być) System.out.println("Po zapakowaniu do pudełka\n" + janek.getShoppingCart()); // a co w pudełku: System.out.println(pudelkoJanka); // Zobaczmy jaka jest wartość czerwonych kwiatów w pudełku Janka System.out.println("Czerwone kwiaty w pudełku Janka kosztowały: " + valueOf(pudelkoJanka, "czerwony") ); // Teraz przychodzi Stefan // ma tylko 60 zł Customer stefan = new Customer("Stefan", 60); // ąle nabrał kwiatów nieco za dużo jak na tę sumę stefan.get(new Lilac(3)); stefan.get(new Rose(5)); // co ma w wózku System.out.println(stefan.getShoppingCart()); // płaci i pakuje do pudełka stefan.pay(); Box pudelkoStefana = new Box(stefan); stefan.pack(pudelkoStefana); // co ostatecznie udało mu się kupić System.out.println(pudelkoStefana); // ... i ile zostało mu pieniędzy System.out.println("Stefanowi zostało : " + stefan.getCash() + " zł"); } } Program powinien wyprowadzić następujące wyniki: Przed płaceniem Wózek własciciel Janek róża, kolor: czerwony, ilość 5, cena 10.0 piwonia, kolor: czerwony, ilość 5, cena 8.0 frezja, kolor: żółty, ilość 3, cena -1.0 bez, kolor: biały, ilość 3, cena 12.0 Po zapłaceniu Wózek własciciel Janek róża, kolor: czerwony, ilość 5, cena 10.0 piwonia, kolor: czerwony, ilość 5, cena 8.0 bez, kolor: biały, ilość 3, cena 12.0 Jankowi zostało : 74.0 zł Po zapakowaniu do pudełka Wózek własciciel Janek -- pusto Pudełko własciciel Janek bez, kolor: biały, ilość 3, cena 12.0 piwonia, kolor: czerwony, ilość 5, cena 8.0 róża, kolor: czerwony, ilość 5, cena 10.0 Czerwone kwiaty w pudełku Janka kosztowały: 90.0 Wózek własciciel Stefan bez, kolor: biały, ilość 3, cena 12.0 róża, kolor: czerwony, ilość 5, cena 10.0 Pudełko własciciel Stefan róża, kolor: czerwony, ilość 5, cena 10.0 Stefanowi zostało : 10.0 zł
Uwaga: w pokazanym tekście programu występują odwołania do klas: PriceList, Customer, ShoppingCart, Box, Rose, Lilac, Freesia, Peony . Trzeba je odpowiednio zdefiniować, ale oprócz tego nalezy zdefiniować jeszcze co najmniej kilka ważnych klasy (których w programie nie widać) potrzebne do spełnienia wymagań postawionych przed programem.
Trzeba też zdefiniować w klasie FloristsTest metodę valueOf(Box pudelko, String kolor) zwracającą wartość kwiatów o podanym kolorze, znajdujących się w pudełku.
Wymaganie podstawowe:
dodanie do powyższego programu zakupów innych kwiatów (np. orchidei)
ma byc bardzo łatwe.
Potrzeba tylko :