![translation](https://cdn.durumis.com/common/trans.png)
To jest post przetłumaczony przez AI.
Wybierz język
Tekst podsumowany przez sztuczną inteligencję durumis
- Opisuje podejście do programowania obiektowego w celu implementacji systemu rezerwacji biletów na film, przedstawiając koncepcje współpracy, obiektów i klas oraz podkreślając strukturę programu zgodną ze strukturą domeny.
- W szczególności przedstawia metody zmniejszania ingerencji z zewnątrz poprzez autonomię obiektów i hermetyzację, a także separację interfejsu od implementacji, zapewniając programistom większą swobodę.
- Ponadto omawia metody tworzenia elastycznego i rozszerzalnego kodu przy użyciu współpracy, dziedziczenia i polimorfizmu w celu obliczania cen zniżkowych, a także podkreśla znaczenie abstrakcji i zalety kompozycji w celu ponownego wykorzystania kodu.
System rezerwacji biletów na film
Omówienie wymagań
- Chcemy zaimplementować system rezerwacji biletów na film online.
- Film reprezentuje podstawowe informacje o filmie.
- Tytuł, czas trwania, informacje o cenie itp.
- Seans reprezentuje rzeczywiste wydarzenie, podczas którego widzowie oglądają film.
- Data, godzina i numer seansu.
- Ludzie rezerwują bilety na film, ale tak naprawdę rezerwują bilety na konkretnyseans.
- Warunki zniżkowe
- Dostępność zniżki cenowej
- Warunki kolejności: wykorzystanie numeru seansu do określenia dostępności zniżki
- Warunki czasowe: wykorzystanie godziny rozpoczęcia seansu do określenia dostępności zniżki
- Polityka zniżkowa
- Określanie stawek zniżkowych
- Polityka zniżki kwotowej: obniżenie ceny biletu o określoną kwotę
- Polityka zniżki procentowej: obniżenie ceny o określony procent
- Możliwe jest przypisanie jednej polityki zniżkowej do filmu lub żadnej, a warunki zniżkowe mogą być łączone.
- Jeśli polityka zniżkowa jest zastosowana, ale warunki nie są spełnione lub polityka zniżkowa nie została zastosowana, cena biletu nie zostanie obniżona.
W kierunku programowania obiektowego
Współpraca, obiekty, klasy
- Programowanie obiektowe to programowanie zorientowane na obiekty.
- Po zdefiniowaniu klasy nie należy zastanawiać się, jakie atrybuty i metody są potrzebne w klasie.
- Należy zdecydować, jakie obiekty mają jakie stany i zachowania.
- Obiekty należy postrzegać nie jako samodzielne byty, ale jako członkowie współpracującej społeczności.
Struktura programu odzwierciedlająca strukturę domeny
- Domena to obszar, w którym użytkownicy korzystają z programu w celu rozwiązania problemu.
- Ogólnie rzecz biorąc, nazwy klas powinny być takie same lub przynajmniej podobne do odpowiadających im pojęć domenowych.
- Relacje między klasami należy jak najbardziej upodobnić do relacji między pojęciami domenowymi, aby ułatwić zrozumienie i przewidywanie struktury programu.
Implementacja klasy
- Klasa dzieli się na część wewnętrzną i zewnętrzną. Aby zaprojektować dobrą klasę, należy zdecydować, która część zostanie ujawniona na zewnątrz, a która zostanie ukryta.
- Atrybuty obiektu są ukrywane jako private, a metody potrzebne do modyfikacji stanu wewnętrznego są udostępniane publicznie.
- Rozdzielenie wewnętrznej i zewnętrznej części klasy zapewnia autonomiczność obiektu, dając programistom swobodę implementacji.
Autonomiczne obiekty
- Obiekty powinny być autonomiczne, tzn. posiadać stan i zachowanie.
- Połączenie danych i funkcji w ramach obiektu nazywa się inkapsulacją.
- Kontrola dostępu ogranicza ingerencję z zewnątrz, dzięki czemu obiekt może samodzielnie decydować o swoich działaniach.
- Zasada rozdzielenia interfejsu i implementacji to kluczowa zasada programowania obiektowego.
- Publiczny interfejs: część dostępna z zewnątrz.
- Implementacja: część dostępna tylko wewnętrznie.
Swoboda programisty
- Role programisty można podzielić na twórców klas i programistów klienckich.
- Twórcy klas dodają nowe typy danych.
- Programiści klienccy korzystają z dodanych typów danych.
- Ukrywanie implementacji
- Twórcy klas mogą udostępniać programistom klienckim tylko niezbędne części, ukrywając wewnętrzną implementację.
- Programiści klienccy muszą znać tylko interfejs, co zmniejsza ilość wiedzy, którą muszą przyswoić.
Współpracująca społeczność obiektów
- Zamiast po prostu deklarować zmienną typu Long do reprezentowania pieniędzy, lepiej jest opakować ją w obiekt, np. Money. Używanie obiektów ułatwia komunikację i pozwala na centralizację operacji obliczeniowych.
- Współpraca to interakcja między obiektami w celu realizacji funkcji systemu.
Krótka opowieść o współpracy
- Jedynym sposobem, w jaki obiekty mogą ze sobą współdziałać, jest wysyłanie i odbieranie wiadomości.
- Własny sposób przetwarzania otrzymanej wiadomości nazywa się metodą.
- Różnicowanie między wiadomościami a metodami jest kluczowe, od tego pojęcia wywodzi się koncepcja polimorfizmu.
Obliczanie ceny biletu ze zniżką
Rozpoczęcie współpracy w celu obliczenia ceny ze zniżką
- Klasa Movie nie zawiera szczegółowej logiki dotyczącej polityki zniżkowej, delegowała ją do interfejsu DiscountPolicy. Używanie dziedziczenia, polimorfizmu i abstrakcji jest niezwykle ważne.
Polityka zniżkowa i warunki zniżkowe
Dziedziczenie i polimorfizm
Zależność w czasie kompilacji i zależność w czasie wykonywania
- Zależności w kodzie i w czasie wykonywania mogą się różnić.
- Im większa różnica między tymi zależnościami, tym trudniej zrozumieć kod, ale tym bardziej elastyczny i rozszerzalny.
Programowanie oparte na różnicach
- Dziedziczenie pozwala na szybkie i łatwe dodawanie nowych klas w oparciu o istniejące klasy, a także na ponowne wykorzystanie implementacji klasy nadrzędnej.
- Tworzenie nowych klas poprzez dodanie tylko tych części, które różnią się od klasy nadrzędnej, nazywa się programowaniem opartym na różnicach.
Dziedziczenie i interfejsy
- Dziedziczenie powoduje, że klasa potomna dziedziczy wszystkie interfejsy klasy nadrzędnej.
- Interfejs definiuje listę wiadomości, które obiekt może zrozumieć.
public class Movie {
public Money calculateMovieFee(Screening screening) {
return fee.minus(discountPolicy.calculateDiscountAmount(screening));
}
- Movie wysyła wiadomość calculateDiscountAmount do DiscountPolicy. Z punktu widzenia Movie nie ma znaczenia, jaki obiekt odpowiada. Ważne jest, aby odpowiedź została pomyślnie przekazana.
- W związku z tym zarówno AmountDiscountPolicy, jak i PercentDiscountPolicy mogą współpracować z Movie w zamian za DiscountPolicy.
- To, że klasa potomna może zastępować klasę nadrzędną, nazywa się konwersją w górę. Wygląda na to, że klasa potomna jest automatycznie konwertowana do typu klasy nadrzędnej.
Polimorfizm
- Polimorfizm to zdolność obiektów do reagowania w różny sposób na tę samą wiadomość, w zależności od ich typu.
- Movie wysyła tę samą wiadomość, ale rzeczywista metoda, która zostanie wykonana, zależy od typu obiektu odbierającego wiadomość.
- Polimorfizm opiera się na możliwości różnicowania zależności w czasie kompilacji i w czasie wykonywania.
- Polimorfizm polega na określeniu metody do wykonania w czasie wykonywania, dlatego nazywany jest również wiązanie opóźnione lub wiązanie dynamiczne.
Interfejsy i polimorfizm
- Jeśli nie chcesz dzielić się implementacją, a jedynie chcesz dzielić się interfejsem, zamiast używać klasy abstrakcyjnej, możesz użyć interfejsu.
Abstrakcja i elastyczność
Siła abstrakcji
- Zalety abstrakcji
- Jeśli spojrzeć tylko na hierarchię abstrakcji, politykę wymagań można opisać na wysokim poziomie.
- Ułatwia to bardziej elastyczne projektowanie.
Elastyczne projektowanie
- Abstrakcja zapobiega łączeniu projektu z konkretnymi sytuacjami, co pozwala na tworzenie bardziej elastycznych projektów.
Kompromis między klasami abstrakcyjnymi a interfejsami
public abstract class DiscountPolicy {
private List conditions;
public DiscountPolicy(DiscountCondition... conditions) {
this.conditions = Arrays.asList(conditions);
}
public Money calculateDiscountAmount(Screening screening) {
for (DiscountCondition condition : conditions) {
if (condition.isSatisfiedBy(screening)) {
return getDiscountAmount(screening);
}
}
return Money.ZERO;
}
abstract protected Money getDiscountAmount(Screening screening);
}
public class NoneDiscountPolicy extends DiscountPolicy {
@Override
protected Money getDiscountAmount(Screening screening) {
return Money.ZERO;
}
- Obecnie NoneDiscountPolicy tak naprawdę nie jest potrzebny, ponieważ calculateDiscountAmount() z DiscountPolicy zwraca 0, jeśli nie ma warunków zniżkowych. Jednak dla użytkownika lepiej jest przekazać jako argument None, ponieważ polityka zniżkowa nie jest obecna.
- Zatem przebudowanie kodu jak poniżej jest bardziej zrozumiałe.
public interface DiscountPolicy {
Money calculdateDiscountAmount(Screening screening);
}
public abstract class DefaultDiscountPolicy implements DiscountPolicy {
private List conditions;
public DefaultDiscountPolicy(DiscountCondition... conditions) {
this.conditions = Arrays.asList(conditions);
}
public Money calculateDiscountAmount(Screening screening) {
for (DiscountCondition condition : conditions) {
if (condition.isSatisfiedBy(screening)) {
return getDiscountAmount(screening);
}
}
return Money.ZERO;
}
abstract protected Money getDiscountAmount(Screening screening);
}
public class NoneDiscountPolicy implements DiscountPolicy {
@Override
public Money calculateDiscountAmount(Screening screening) {
return Money.ZERO;
}
}
public class PercentDiscountPolicy extends DefaultDiscountPolicy {
private double percent;
public PercentDiscountPolicy(double percent, DiscountCondition... conditions) {
super(conditions);
this.percent = percent;
}
@Override
protected Money getDiscountAmount(Screening screening) {
return screening.getMovieFee().times(percent);
}
}
public class AmountDiscountPolicy extends DefaultDiscountPolicy {
private Money discountAmount;
public AmountDiscountPolicy(Money discountAmount, DiscountCondition... conditions) {
super(conditions);
this.discountAmount = discountAmount;
}
@Override
protected Money getDiscountAmount(Screening screening) {
return discountAmount;
}
- W ten sposób DiscountPolicy jest abstrakcyjnym interfejsem, a jego implementacje to NoneDiscountPolicy i DefaultDiscountPolicy, która reprezentuje ogólną politykę zniżkową.
- Można by pomyśleć, że dodanie interfejsu wyłącznie dla NoneDiscountPolicy zwiększa złożoność, a nie przynosi żadnych korzyści.
- Należy pamiętać, że wszystko, co dotyczy implementacji, jest kompromisem. Każdy fragment kodu powinien mieć uzasadnienie.
Ponowne wykorzystanie kodu
- Dziedziczenie można wykorzystać do ponownego wykorzystania kodu.
- Jednak do ponownego wykorzystania kodu lepiej jest użyć kompozycji niż dziedziczenia.
- Obecnie Movie używa kompozycji do ponownego wykorzystania kodu DiscountPolicy.
- Jeśli Movie byłoby klasą nadrzędną, a AmountDiscountMovie i PercentDiscountMovie klasami potomnymi, byłoby to dziedziczenie.
Dziedziczenie
- Wady dziedziczenia
- Narusza inkapsulację.
- Należy dobrze znać wewnętrzną strukturę klasy nadrzędnej.
- Zmiana klasy nadrzędnej z dużym prawdopodobieństwem wymaga zmiany klasy potomnej.
- Tworzy mniej elastyczne projekty.
- Relacja między klasą nadrzędną a klasą potomną jest ustalana w czasie kompilacji.
- Nie można zmienić typu obiektu w czasie wykonywania.
- Kompozycja pozwala jednak na wymianę instancji w czasie wykonywania za pomocą metody changeDiscountPolicy().
- Narusza inkapsulację.
Kompozycja
- Ponowne wykorzystanie kodu tylko poprzez wiadomości zdefiniowane w interfejsie nazywa się kompozycją.
- Pozwala na realizację inkapsulacji i utrzymanie luźnego sprzężenia.
- Aby skorzystać z polimorfizmu i ponownego wykorzystania interfejsu, należy połączyć dziedziczenie i kompozycję.
- Na przykład DiscountPolicy to interfejs, który musi być dziedziczony przez jego implementacje.
Źródła
- Obiekty