System rezerwacji biletów na filmy
Przeglądanie wymagań
- Chcemy zaimplementować system rezerwacji biletów na filmy online.
- Film reprezentuje podstawowe informacje o filmie.
- Tytuł, czas trwania, informacje o cenie itp.
- Projekcja reprezentuje rzeczywiste wydarzenie, podczas którego widzowie oglądają film.
- Data projekcji, godzina, numer kolejności itp.
- Ludzie rezerwują bilety na filmy, ale właściwie powinniśmy powiedzieć, że rezerwują bilety na konkretną projekcjęfilmu.
- Warunki zniżek
- Możliwość obniżenia ceny.
- Warunek kolejności: wykorzystanie numeru kolejności projekcji do ustalenia zniżki.
- Warunek czasu: wykorzystanie godziny rozpoczęcia projekcji filmu do ustalenia zniżki.
- Polityka zniżek
- Ustalanie ceny po zniżce.
- Polityka zniżki kwotowej: odliczenie określonej kwoty od ceny biletu.
- Polityka zniżki procentowej: odliczenie określonego procentu od ceny standardowej.
- Dla każdego filmu można przypisać jedną politykę zniżek lub w ogóle nie stosować zniżek, a warunki zniżek można łączyć.
- Jeśli polityka zniżek jest zastosowana, ale warunki zniżek nie są spełnione lub polityka zniżek nie jest zastosowana, to cena biletu nie jest obniżana.
W kierunku programowania obiektowego
Współpraca, obiekty, klasy
- Programowanie obiektowe to programowanie zorientowane na obiekty.
- Nie należy zastanawiać się nad tym, jakie atrybuty i metody są potrzebne w klasie po jej zdefiniowaniu.
- Należy ustalić, jakie obiekty mają jakie stany i zachowania.
- Obiekty należy postrzegać nie jako niezależne jednostki, ale jako członków współpracującej społeczności.
Struktura programu zgodna ze strukturą domeny
- Domena to dziedzina, w której użytkownik korzysta z programu w celu rozwiązania problemu.
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fe7d22a03-4a24-40e9-8068-bd03b9fd816b%2FUntitled.png?table=block&id=3f1931fc-8ef9-470a-8189-3727146638f6&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/438;" width="2000" height="438"></span>
- Zazwyczaj nazwa klasy powinna być taka sama jak odpowiadająca jej koncepcja domeny lub przynajmniej podobna.
- Relacje między klasami powinny być jak najbardziej podobne do relacji między koncepcjami domeny, aby struktura programu była łatwa do zrozumienia i przewidywania.
Implementacja klasy
- Klasa jest podzielona na część wewnętrzną i zewnętrzną. Aby zaprojektować dobrą klasę, należy zdecydować, które części udostępnić na zewnątrz, a które ukryć.
- Atrybuty obiektu są blokowane jako prywatne, a metody potrzebne do zmiany stanu wewnętrznego są otwierane jako publiczne.
- Rozróżnienie między częścią wewnętrzną i zewnętrzną klasy zapewnia autonomię obiektu, co z kolei daje programistom swobodę implementacji.
Autonomiczne obiekty
- Obiekt powinien być autonomicznym obiektem posiadającym stan i zachowania.
- Łączenie danych i funkcji w obrębie obiektu nazywa się inkapsulacją.
- Kontrolowanie dostępu ogranicza ingerencję z zewnątrz, co pozwala obiektowi samodzielnie podejmować decyzje dotyczące własnych działań.
- Zasada separacji interfejsu od implementacji jest jedną z głównych zasad, których należy przestrzegać w programowaniu obiektowym.
- Interfejs publiczny: część dostępna z zewnątrz.
- Implementacja: część dostępna tylko wewnątrz.
Swoboda programisty
- Rola programisty jest podzielona na autora klasy i programistę klienta.
- Autor klasy dodaje nowy typ danych.
- Programista klienta używa typu danych dodanego przez autora klasy.
- Ukrywanie implementacji
- Autor klasy może ukryć wewnętrzną implementację przed programistą klienta, udostępniając tylko niezbędne części.
- Programista klienta musi znać tylko interfejs, co zmniejsza ilość informacji, którą musi znać.
Współpracująca społeczność obiektów
- Zamiast deklarować prostą zmienną typu Long do reprezentowania pieniędzy, lepiej jest zapakować ją w obiekt, np. Money. Użycie obiektu zapewnia lepszą komunikację znaczenia i pozwala na wykonywanie powtarzalnych operacji w jednym miejscu.
- Współpracę między obiektami w celu realizacji konkretnej funkcji systemu nazywamy współpracą.
Krótka historia współpracy
- Jedynym sposobem, w jaki obiekt może wchodzić w interakcję z innymi obiektami, jest wysyłanie lub odbieranie wiadomości.
- Własną metodę obsługi otrzymanej wiadomości nazywamy metodą.
- Rozróżnianie między wiadomością a metodą jest ważne, a stąd bierze się koncepcja polimorfizmu.
Obliczanie ceny po zniżce
Rozpoczęcie współpracy w celu obliczenia ceny po zniżce
- Klasa Movie nie zawiera szczegółowej logiki dotyczącej polityki zniżek i deleguje ją do interfejsu DiscountPolicy. Wykorzystanie dziedziczenia, polimorfizmu i abstrakcji jest niezwykle ważne.
Polityka zniżek i warunki zniżek
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F7b9b27a5-0dac-4ba7-9552-05b7f8fbdecd%2FUntitled.png?table=block&id=3264f189-6e12-4a55-94d2-75b851320c7a&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/535;" width="2000" height="535"></span>
Dziedziczenie i polimorfizm
Zależność w czasie kompilacji i zależność w czasie wykonywania
- Zależność kodu i zależność w czasie wykonywania mogą się różnić.
- Im większa różnica między tymi dwoma rodzajami zależności, tym trudniej zrozumieć kod, ale tym bardziej elastyczny i rozszerzalny staje się kod.
Programowanie oparte na różnicach
- Dziedziczenie pozwala na łatwe i szybkie dodawanie nowych klas w oparciu o istniejące klasy, a także na ponowne wykorzystanie implementacji klasy nadrzędnej.
- Tworzenie nowej klasy poprzez dodanie tylko różnic w stosunku do klasy nadrzędnej nazywamy programowaniem opartym na różnicach.
Dziedziczenie i interfejsy
- Dziedziczenie powoduje, że klasa potomna dziedziczy wszystkie interfejsy udostępnione przez klasę nadrzędną.
- Interfejs definiuje listę wiadomości, które obiekt może zrozumieć.
- Movie wysyła do DiscountPolicy wiadomość calculateDiscountAmount. Dla Movie nie ma znaczenia, jaka klasa instancji odpowie, ważne jest tylko, aby odpowiedź była poprawna.
- Dlatego zarówno AmountDiscountPolicy, jak i PercentDiscountPolicy mogą współpracować z Movie w imieniu DiscountPolicy.
- Takie zastępowanie klasy potomnej przez klasę nadrzędną nazywamy konwersją w górę (upcasting). Wynika to z faktu, że klasa potomna jest automatycznie konwertowana na typ klasy nadrzędnej, co sprawia wrażenie, że znajduje się ona wyżej.
Polimorfizm
- Polimorfizm to zdolność obiektu do reagowania na tę samą wiadomość w różny sposób w zależności od jego typu.
- Movie wysyła tę samą wiadomość, ale metoda, która zostanie faktycznie wykonana, zależy od tego, jaka klasa obiektu odbierającego wiadomość.
- Polimorfizm opiera się na fakcie, że zależność w czasie kompilacji i zależność w czasie wykonywania mogą się różnić.
- Polimorfizm decyduje o metodzie do wykonania w czasie wykonywania, dlatego nazywamy go późnym wiązanie lub dynamicznym wiązaniem.
Interfejsy i polimorfizm
- Jeśli nie chcemy udostępniać implementacji, a chcemy udostępniać tylko interfejs, to zamiast klasy abstrakcyjnej powinniśmy użyć interfejsu.
Abstrakcja i elastyczność
Siła abstrakcji
- Zalety abstrakcji
- Jeśli spojrzymy tylko na hierarchię abstrakcji, możemy opisać politykę wymagań na wysokim poziomie.
- Projektowanie staje się bardziej elastyczne.
Elastyczne projektowanie
- Abstrakcja zapobiega powiązywaniu projektu z konkretną sytuacją, co pozwala na stworzenie elastycznego projektu.
Kompromis między klasą abstrakcyjną a interfejsem
- Obecnie klasa NoneDiscountPolicy jest w rzeczywistości zbędna, ponieważ metoda calculateDiscountAmount() klasy DiscountPolicy zwraca 0, jeśli nie ma żadnych warunków zniżek, więc nie ma problemu. Jednak z punktu widzenia użytkownika należy przekazać do Movie pustą politykę zniżek typu None, dlatego została ona dodana.
- Dlatego klasa NoneDiscountPolicy jest myląca, a po modyfikacji będzie łatwiejsza do zrozumienia.
- W ten sposób DiscountPolicy jest abstrakcyjnym interfejsem, a jego implementacjami są NoneDiscountPolicy (reprezentujący brak polityki zniżek) i DefaultDiscountPolicy (reprezentujący standardową politykę zniżek).
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F712983a9-3937-4265-82fe-66a144c44a0f%2FUntitled.png?table=block&id=f5ca651c-d03c-4a6c-97e9-7443ee5649a0&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/886;" width="2000" height="886"></span>
- Można uznać, że dodawanie interfejsu wyłącznie dla NoneDiscountPolicy jest nieefektywne w porównaniu do wzrostu złożoności.
- Należy pamiętać, że wszystko, co implementujemy, jest przedmiotem kompromisu. Oznacza to, że każdy napisany przez nas kod musi mieć uzasadnienie.
Ponowne użycie kodu
- Dziedziczenie może być używane do ponownego użycia kodu.
- Jednak do ponownego użycia kodu lepiej jest używać kompozycji niż dziedziczenia.
- W obecnym kodzie sposób, w jaki Movie ponownie używa kodu DiscountPolicy, to właśnie kompozycja.
- Jeśli Movie byłoby klasą nadrzędną, a AmountDiscountMovie i PercentDiscountMovie klasami pochodnymi, to byłoby to dziedziczenie.
Dziedziczenie
- Wady dziedziczenia
- Narusza inkapsulację.
- Należy dobrze znać wewnętrzną strukturę klasy nadrzędnej.
- Zmiana klasy nadrzędnej prawdopodobnie spowoduje konieczność zmiany klasy potomnej.
- Utrudnia elastyczność projektu.
- Relacja między klasą nadrzędną a potomną jest ustalana w czasie kompilacji.
- Nie można zmienić typu obiektu w czasie wykonywania.
- Jednak w przypadku kompozycji można zmienić instancję w czasie wykonywania za pomocą metody takiej jak changeDiscountPolicy().
- Narusza inkapsulację.
Kompozycja
- Sposób ponownego użycia kodu wyłącznie za pomocą wiadomości zdefiniowanych w interfejsie nazywamy kompozycją.
- Pozwala na implementację inkapsulacji i utrzymanie luźnego sprzężenia.
- W przypadku polimorfizmu i ponownego użycia interfejsu należy łączyć dziedziczenie i kompozycję.
- Na przykład interfejs DiscountPolicy musi używać dziedziczenia do implementacji jego podklas.
Źródło
- Obiekty
Komentarze0