Powyższe pytanie jest abstrakcyjnym przykładem typowego problemu, który napotykam w starszym kodzie, a ściślej - problemów wynikających z poprzednich prób rozwiązania tego problemu.
Mogę wymyślić co najmniej jedną metodę platformy .NET, która ma rozwiązać ten problem, podobnie jak Enumerable.OfType<T>
metoda. Ale fakt, że ostatecznie kończysz przesłuchiwanie typu obiektu w czasie wykonywania, nie pasuje do mnie.
Poza pytaniem każdego konia „Czy jesteś jednorożcem?” przychodzą mi na myśl następujące podejścia:
- Zgłaszaj wyjątek, gdy podejmowana jest próba uzyskania długości rogu jednorożca (ujawnia funkcjonalność nieodpowiednią dla każdego konia)
- Zwraca wartość domyślną lub magiczną dla długości rogu jednorożca (wymaga domyślnych kontroli przeszywanych przez dowolny kod, który chce zniszczyć statystyki rogu dla grupy koni, które mogą być wszystkie inne niż jednorożce)
- Pozbądź się dziedziczenia i stwórz na koniu oddzielny obiekt, który powie ci, czy koń jest jednorożcem, czy nie (co potencjalnie spycha ten sam problem w dół warstwy)
Mam przeczucie, że najlepiej na to odpowiedzieć „brak odpowiedzi”. Ale jak podchodzisz do tego problemu i jeśli to zależy, jaki jest kontekst Twojej decyzji?
Byłbym także zainteresowany wszelkimi spostrzeżeniami na temat tego, czy problem ten nadal występuje w kodzie funkcjonalnym (a może tylko w językach funkcjonalnych, które obsługują zmienność?)
Oznaczono to jako możliwe powtórzenie następującego pytania: Jak uniknąć downcastingu?
Odpowiedź na to pytanie zakłada, że ktoś posiada narzędzie, za HornMeasurer
pomocą którego należy wykonać wszystkie pomiary klaksonu. Jest to jednak narzucenie bazy kodu, która powstała na podstawie egalitarnej zasady, że każdy powinien mieć swobodę mierzenia rogu konia.
W przypadku braku a HornMeasurer
podejście przyjęte w odpowiedzi odzwierciedla podejście oparte na wyjątkach wymienione powyżej.
W komentarzach pojawiły się także pewne nieporozumienia dotyczące tego, czy konie i jednorożce są koniowatymi, czy też jednorożec jest magicznym podgatunkiem konia. Należy rozważyć obie możliwości - być może jedna jest lepsza od drugiej?
źródło
Odpowiedzi:
Zakładając, że chcesz leczyć
Unicorn
jako specjalny rodzajHorse
, istnieją zasadniczo dwa sposoby, aby go wymodelować. Bardziej tradycyjny sposób to relacja podklasy. Możesz uniknąć sprawdzania typu i downcastingu, po prostu zmieniając kod, aby listy zawsze były osobne w kontekstach, w których ma to znaczenie, i łącz je tylko w kontekstach, w których nigdy nie dbasz oUnicorn
cechy. Innymi słowy, ustawiasz go tak, abyś nigdy nie znalazł się w sytuacji, w której musisz wyciągnąć jednorożce ze stada koni. Z początku wydaje się to trudne, ale jest możliwe w 99,99% przypadków i zwykle sprawia, że kod jest znacznie czystszy.Innym sposobem na wymodelowanie jednorożca jest po prostu podanie wszystkim koniom opcjonalnej długości rogu. Następnie możesz sprawdzić, czy jest to jednorożec, sprawdzając, czy ma długość rogu, i znaleźć średnią długość rogu wszystkich jednorożców według (w Scali):
Ta metoda ma tę zaletę, że jest prostsza, z jedną klasą, ale ma tę wadę, że jest znacznie mniej rozszerzalna i ma w pewnym sensie okrągły sposób sprawdzania „jednorożca”. Sztuczka, jeśli zdecydujesz się na to rozwiązanie, polega na rozpoznaniu, kiedy zaczynasz często go rozszerzać, że musisz przejść do bardziej elastycznej architektury. Tego rodzaju rozwiązanie jest znacznie bardziej popularne w językach funkcjonalnych, w których masz proste i wydajne funkcje, takie jak
flatMap
łatwe odfiltrowywanieNone
elementów.źródło
Horse
maIsUnicorn
właściwość i pewnąUnicornStuff
właściwość z długością klaksonu (podczas skalowania dla jeźdźca / brokatu wspomnianego w pytaniu).Masz prawie wszystkie opcje. Jeśli masz zachowanie zależne od określonego podtypu i jest ono mieszane z innymi typami, Twój kod musi być świadomy tego podtypu; to proste logiczne rozumowanie.
Osobiście po prostu bym poszedł
horses.OfType<Unicorn>().Average(u => u.HornLength)
. Bardzo wyraźnie wyraża intencję kodu, co często jest najważniejsze, ponieważ w końcu ktoś będzie musiał go później utrzymywać.źródło
Unicorn
i tak zawiera tylko s (dla rekordu, który można pominąćreturn
).W .NET nie ma nic złego z:
Używanie ekwiwalentu Linq również jest w porządku:
W oparciu o pytanie, które zadałeś w tytule, to jest kod, którego spodziewałbym się znaleźć. Gdyby pytanie dotyczyło czegoś takiego jak „średnia zwierząt z rogami”, byłoby inaczej:
Zauważ, że podczas korzystania z Linq,
Average
(iMin
iMax
) zgłoszą wyjątek, jeśli wyliczalny jest pusty, a typ T nie ma wartości zerowej. Jest tak, ponieważ średnia jest naprawdę niezdefiniowana (0/0). Tak naprawdę potrzebujesz czegoś takiego:Edytować
Myślę, że to wymaga dodania ... jednym z powodów, dla których takie pytanie nie pasuje do programistów zorientowanych obiektowo, jest założenie, że używamy klas i obiektów do modelowania struktury danych. Oryginalna koncepcja obiektowo zorientowana na Smalltalk polegała na zbudowaniu programu z modułów, które były tworzone jako obiekty i wykonywały dla ciebie usługi, gdy wysłałeś im wiadomość. Fakt, że możemy również używać klas i obiektów do modelowania struktury danych, jest (użytecznym) efektem ubocznym, ale są to dwie różne rzeczy. Nawet nie sądzę, że ten ostatni program powinien być uważany za programowanie obiektowe, ponieważ można zrobić to samo z
struct
, ale po prostu nie byłoby tak ładnie.Jeśli używasz programowania obiektowego do tworzenia usług, które robią dla ciebie rzeczy, to z dobrych powodów na ogół nie odpowiada się na pytanie, czy ta usługa jest w rzeczywistości inną usługą lub konkretną implementacją. Otrzymałeś interfejs (zazwyczaj poprzez wstrzyknięcie zależności) i powinieneś zakodować ten interfejs / umowę.
Z drugiej strony, jeśli (błędnie) używasz pomysłów klasy / obiektu / interfejsu do stworzenia struktury danych lub modelu danych, to osobiście nie widzę problemu z wykorzystaniem tego, co jest w pełni. Jeśli zdefiniowałeś, że jednorożce są podtypem koni i ma to sens w twojej domenie, to koniecznie przeprowadź zapytanie o konie w stadzie, aby znaleźć jednorożce. W końcu w takim przypadku zwykle staramy się stworzyć język specyficzny dla domeny, aby lepiej wyrazić rozwiązania problemów, które musimy rozwiązać. W tym sensie nie ma nic złego w
.OfType<Unicorn>()
itp.Ostatecznie pobranie kolekcji elementów i przefiltrowanie ich według typu jest tak naprawdę tylko programowaniem funkcjonalnym, a nie programowaniem obiektowym. Na szczęście języki takie jak C # są teraz wygodne w obsłudze obu paradygmatów.
źródło
animal
toUnicorn
; po prostu rzuć zamiast używaćas
lub potencjalnie jeszcze lepiej użyj,as
a następnie sprawdź, czy nie ma wartości null.Problem z tym stwierdzeniem polega na tym, że bez względu na to, jakiego mechanizmu używasz, zawsze będziesz przesłuchiwał obiekt, aby stwierdzić, jaki to typ. Może to być RTTI lub może to być unia lub zwykła struktura danych, o którą pytasz
if horn > 0
. Dokładne szczegóły zmieniają się nieznacznie, ale cel jest taki sam - pytasz obiekt o siebie w jakiś sposób, aby sprawdzić, czy powinieneś go dalej przesłuchiwać.Biorąc to pod uwagę, warto w tym celu skorzystać ze wsparcia języka. W .NET, którego byś używał
typeof
.Powodem tego jest nie tylko dobre posługiwanie się językiem. Jeśli masz obiekt, który wygląda jak inny, ale z pewną niewielką zmianą, istnieje szansa, że z czasem znajdziesz więcej różnic. W twoim przykładzie jednorożców / koni możesz powiedzieć, że jest tylko długość rogu ... ale jutro sprawdzisz, czy potencjalny jeździec jest dziewicą, czy też kupa jest błyszcząca. (klasyczny przykład w świecie rzeczywistym to widżety GUI, które wywodzą się ze wspólnej bazy i trzeba inaczej szukać pól wyboru i list boxów. Liczba różnic byłaby zbyt duża, aby po prostu stworzyć pojedynczy super obiekt, który posiadałby wszystkie możliwe kombinacje danych ).
Jeśli sprawdzanie typu obiektu w czasie wykonywania nie działa dobrze, wtedy alternatywą jest podzielenie różnych obiektów od samego początku - zamiast przechowywać jedno stado jednorożców / koni, masz 2 kolekcje - jedną dla koni, drugą dla jednorożców . Może to działać bardzo dobrze, nawet jeśli przechowujesz je w specjalnym kontenerze (np. Multimapa, w której kluczem jest typ obiektu ... ale nawet mimo tego, że przechowujemy je w 2 grupach, natychmiast wracamy do badania typu obiektu !)
Z pewnością podejście oparte na wyjątkach jest błędne. Używanie wyjątków jako normalnego przepływu programu to zapach kodu (jeśli miałeś stado jednorożców i osła z muszelką przyklejoną do głowy, to powiedziałbym, że podejście oparte na wyjątkach jest OK, ale jeśli masz stado jednorożców a konie sprawdzające każdego pod kątem jednorożca nie są nieoczekiwane. Wyjątek stanowią wyjątkowe okoliczności, a nie skomplikowane
if
stwierdzenie). W każdym razie użycie wyjątków dla tego problemu polega po prostu na zapytaniu o typ obiektu w czasie wykonywania, tylko tutaj niewłaściwie używasz funkcji języka do sprawdzania obiektów innych niż jednorożec. Równie dobrze możesz napisać wif horn > 0
i przynajmniej przetwarzają Twoją kolekcję szybko, wyraźnie, używając mniejszej liczby wierszy kodu i unikając problemów pojawiających się po zgłoszeniu innych wyjątków (np. pusta kolekcja lub próba zmierzenia muszli tego osła)źródło
if horn > 0
jest to sposób, w jaki problem ten jest rozwiązany na początku. Następnie problemy, które zwykle pojawiają się, gdy chcesz sprawdzić jeźdźców i brokat, ihorn > 0
są zakopane w całym miejscu w niepowiązanym kodzie (również kod cierpi na tajemnicze błędy z powodu braku kontroli, gdy klakson ma wartość 0). Poza tym podklasowanie konia po fakcie jest zwykle najdroższą propozycją, więc zazwyczaj nie jestem skłonny do tego, jeśli nadal są skupieni na końcu reaktora. Tak więc z pewnością stało się „jak brzydkie są alternatywy”Ponieważ pytanie ma
functional-programming
znacznik, możemy użyć typu sumy, aby odzwierciedlić dwa smaki koni i dopasować wzór, aby jednoznacznie między nimi. Na przykład w F #:W porównaniu z OOP, FP ma tę zaletę, że separuje dane / funkcje, co może uchronić cię przed (nieuzasadnionym?) Poczuciem winy polegającym na naruszeniu poziomu abstrakcji podczas sprowadzania do określonych podtypów z listy obiektów nadtypu.
W przeciwieństwie do rozwiązań OO zaproponowanych w innych odpowiedziach, dopasowywanie wzorców zapewnia również łatwiejszy punkt rozszerzenia, jeśli inny gatunek Rogaty
Equine
pojawi się pewnego dnia.źródło
Krótka forma tej samej odpowiedzi na końcu wymaga przeczytania książki lub artykułu internetowego.
Wzór gościa
Problem dotyczy mieszanki koni i jednorożców. (Naruszenie zasady podstawienia Liskowa jest częstym problemem w starszych bazach kodowych.)
Dodaj metodę do konia i wszystkich podklas
Interfejs gościa koni wygląda mniej więcej tak w java / c #
Aby zmierzyć rogi, piszemy teraz ....
Wzorzec odwiedzających jest krytykowany za utrudnienie refaktoryzacji i wzrostu.
Krótka odpowiedź: użyj wzorca projektu Visitor, aby uzyskać podwójną wysyłkę.
patrz także https://en.wikipedia.org/wiki/Visitor_pattern
zobacz także http://c2.com/cgi/wiki?VisitorPattern w celu omówienia odwiedzających.
patrz także Design Patterns autorstwa Gamma i in.
źródło
Zakładając, że w twojej architekturze jednorożce są podgatunkiem konia i napotykasz miejsca, w których możesz znaleźć kolekcję miejsc, w
Horse
których niektóre z nich mogą byćUnicorn
, osobiście wybrałbym pierwszą metodę (.OfType<Unicorn>()...
), ponieważ jest to najprostszy sposób wyrażenia twojego zamiaru . Dla każdego, kto przyjdzie później (w tym ciebie za 3 miesiące), od razu jest oczywiste, co próbujesz osiągnąć za pomocą tego kodu: wybierz jednorożce spośród koni.Inne wymienione metody wydają się być kolejnym sposobem zadawania pytania „Czy jesteś jednorożcem?”. Na przykład, jeśli używasz metody pomiaru rogów opartej na wyjątkach, możesz mieć kod, który wyglądałby mniej więcej tak:
Tak więc teraz wyjątek staje się wskaźnikiem, że coś nie jest jednorożcem. A teraz nie jest to tak naprawdę wyjątkowa sytuacja, ale jest częścią normalnego przebiegu programu. A użycie wyjątku zamiast
if
wydaje się jeszcze bardziej brudne niż samo sprawdzanie typu.Powiedzmy, że podążasz magiczną drogą sprawdzania rogów koni. Więc teraz twoje klasy wyglądają mniej więcej tak:
Teraz twoja
Horse
klasa musi wiedzieć oUnicorn
klasie i mieć dodatkowe metody radzenia sobie z rzeczami, na których jej nie zależy. Teraz wyobraź sobie, że masz takżePegasus
s iZebra
s, które dziedziczą zHorse
. TerazHorse
potrzebujeFly
sposobu jakMeasureWings
,CountStripes
itd A potemUnicorn
klasa dostaje zbyt tych metod. Teraz wszystkie twoje klasy muszą się o sobie znać i zatrułeś je kilkoma metodami, których nie powinno być po prostu po to, by uniknąć pytania typu „Czy to jednorożec?”A co z dodaniem czegoś do
Horse
s, aby powiedzieć, czy coś jestUnicorn
i poradzi sobie z pomiarem wszystkich rogów? Cóż, teraz musisz sprawdzić istnienie tego obiektu, aby wiedzieć, czy coś jest jednorożcem (co tylko zastępuje jeden czek na inny). To także trochę zabrudza wody w tym, że teraz możesz miećList<Horse> unicorns
to naprawdę zawiera wszystkie jednorożce, ale system typów i debugger nie mogą tego łatwo powiedzieć. „Ale wiem, że to wszystkie jednorożce”, mówicie, „nazwa nawet tak mówi”. Co jeśli coś było źle nazwane? Albo powiedzmy, że napisałeś coś z założeniem, że tak naprawdę będą to wszystkie jednorożce, ale potem zmieniły się wymagania i teraz może mieć również pomieszane pegazy? (Ponieważ nic takiego nigdy się nie zdarza, szczególnie w starszych programach / sarkazmie.) Teraz system pisma z przyjemnością wprowadzi twoje pegasi do twoich jednorożców. Jeśli twoja zmienna została zadeklarowana jakoList<Unicorn>
kompilator (lub środowisko wykonawcze), pasowałaby, gdybyś próbował mieszać pegasi lub konie.Wreszcie, wszystkie te metody są tylko zamiennikiem kontroli systemu typów. Osobiście wolałbym nie wymyślać tu na nowo koła i mam nadzieję, że mój kod działa tak samo dobrze, jak coś wbudowanego i przetestowanego przez tysiące innych programistów tysiące razy.
Ostatecznie kod musi być dla Ciebie zrozumiały . Komputer to rozwiąże, niezależnie od tego, jak go napiszesz. To ty musisz go debugować i mieć powód do tego. Dokonaj wyboru, który ułatwi Ci pracę. Jeśli z jakiegoś powodu jedna z tych innych metod oferuje przewagę, która przewyższa wyraźniejszy kod w kilku miejscach, które by się pojawiły, idź. Ale to zależy od twojej bazy kodu.
źródło
if(horse.IsUnicorn) horse.MeasureHorn();
a wyjątki nie zostałyby wyłapane - albo zostałyby uruchomione, gdy!horse.IsUnicorn
jesteś w kontekście pomiaru jednorożca, albo wewnątrzMeasureHorn
nie-jednorożca. W ten sposób, gdy zostanie zgłoszony wyjątek, nie maskujesz błędów, całkowicie wysadza się w powietrze i jest znakiem, że coś trzeba naprawić. Oczywiście jest to odpowiednie tylko w niektórych scenariuszach, ale jest to implementacja, która nie używa zgłaszania wyjątków w celu ustalenia ścieżki wykonania.Cóż, wygląda na to, że twoja domena semantyczna ma relację IS-A, ale jesteś nieco ostrożny w używaniu podtypów / dziedziczenia do modelowania tego - szczególnie ze względu na odbicie typu środowiska wykonawczego. Myślę jednak, że boisz się niewłaściwej rzeczy - podsieci rzeczywiście wiążą się z niebezpieczeństwami, ale fakt, że pytasz o obiekt w czasie wykonywania, nie stanowi problemu. Zobaczysz o co mi chodzi.
Programowanie obiektowe dość mocno opierało się na pojęciu relacji IS-A, prawdopodobnie opierało się na nim zbyt mocno, prowadząc do dwóch znanych koncepcji krytycznych:
Myślę jednak, że istnieje inny, bardziej oparty na programowaniu funkcjonalnym sposób spojrzenia na relacje IS-A, który być może nie ma takich trudności. Po pierwsze, chcemy modelować konie i jednorożce w naszym programie, więc będziemy mieć typ
Horse
iUnicorn
typ. Jakie są wartości tego typu? Powiedziałbym to:Może to zabrzmieć oczywisto, ale myślę, że jednym ze sposobów, w jaki ludzie wpadają w takie problemy, jak problem z elipsą koła, jest niedostateczne uważanie na te punkty. Każde koło jest elipsą, ale to nie znaczy, że każdy schematyczny opis koła jest automatycznie schematycznym opisem elipsy według innego schematu. Innymi słowy, to, że okrąg jest elipsą, nie oznacza, że a
Circle
jestEllipse
, że tak powiem. Ale to oznacza, że:Circle
(opis schematu okręgu) wEllipse
(inny typ opisu) opisujący te same koła;Ellipse
i, jeśli opisuje okrąg, zwraca odpowiedniąCircle
.Tak więc, pod względem programowania funkcjonalnego, twój
Unicorn
typ wcale nie musi być podtypemHorse
, potrzebujesz tylko takich operacji:I
toUnicorn
musi być właściwą odwrotnościątoHorse
:Haskella
Maybe
Typ jest tym, co inne języki nazywają typem „opcji”. Na przykładOptional<Unicorn>
typ Java 8 jest albo „Unicorn
nic”, albo „niczym”. Zauważ, że dwie z twoich alternatyw - rzucenie wyjątku lub zwrócenie „wartości domyślnej lub magicznej” - są bardzo podobne do typów opcji.Zasadniczo zrekonstruowałem pojęcie relacji IS-A pod względem typów i funkcji, bez użycia podtypów i dziedziczenia. Chciałbym od tego zabrać:
Horse
typ;Horse
potrzeby Typ zakodować wystarczająco dużo informacji, aby określić jednoznacznie czy jakakolwiek wartość opisuje jednorożca;Horse
typu muszą ujawniać te informacje, aby klienci tego typu mogli zaobserwować, czy danyHorse
jednorożec jest dany;Horse
typu będą musieli wykorzystać te ostatnie operacje w czasie wykonywania, aby rozróżnić jednorożce od koni.Jest to więc „zapytaj każdego”
Horse
model czy to jednorożec”. Obawiasz się tego modelu, ale nie sądzę. Jeśli dam ci listęHorse
s, wszystko, co gwarantuje ten typ, to to, że elementy, które opisują na liście, to konie - więc nieuchronnie będziesz musiał zrobić coś w czasie wykonywania, aby powiedzieć, które z nich są jednorożcami. Myślę, że nie da się tego obejść - musisz wdrożyć operacje, które zrobią to za Ciebie.W programowaniu obiektowym znany sposób to:
Horse
typ;Unicorn
jako podtypHorse
;Horse
jestUnicorn
.Ma to dużą słabość, kiedy patrzysz na to z perspektywy „rzecz kontra opis”, którą przedstawiłem powyżej:
Horse
instancję, która opisuje jednorożca, ale nie jestUnicorn
instancją?Wracając do początku, myślę, że to naprawdę przerażająca część wykorzystywania podtypów i downcastów do modelowania tej relacji IS-A - nie fakt, że musisz wykonać kontrolę czasu wykonywania. Nadużywanie trochę typografii, pytanie,
Horse
czy toUnicorn
instancja, nie jest równoznaczne z pytaniem,Horse
czy jest to jednorożec (czy jest toHorse
opis konia, który jest również jednorożcem). Nie, chyba że twój program dołożył wszelkich starań, aby obudować kod, który konstruuje,Horses
tak że za każdym razem, gdy klient próbuje zbudowaćHorse
opisujący jednorożca,Unicorn
tworzona jest instancja klasy. Z mojego doświadczenia wynika, że programiści rzadko robią to ostrożnie.Więc wybrałbym podejście, w którym istnieje jawna, nieprzekraczająca operacja, która konwertuje
Horse
s naUnicorn
s. Może to być metodaHorse
typu:... lub może to być obiekt zewnętrzny („oddzielny obiekt na koniu, który mówi, czy koń jest jednorożcem, czy nie”):
Wybór między nimi zależy od tego, jak zorganizowany jest twój program - w obu przypadkach masz odpowiednik mojej
Horse -> Maybe Unicorn
operacji z góry, po prostu pakujesz go na różne sposoby (co wprawdzie będzie miało falowy wpływ na operacje, którychHorse
potrzebuje ten typ ujawniać swoim klientom).źródło
Pomyślałem, że komentarz OP w innej odpowiedzi wyjaśniał pytanie
Tak sformułowane, myślę, że potrzebujemy więcej informacji. Odpowiedź prawdopodobnie zależy od wielu rzeczy:
herd.averageHornLength()
wydaje się pasować do naszego modelu koncepcyjnego.Ogólnie jednak nie pomyślałbym nawet o dziedziczeniu i podtypach tutaj. Masz listę obiektów. Niektóre z tych obiektów można zidentyfikować jako jednorożce, być może dlatego, że mają
hornLength()
metodę. Filtruj listę na podstawie tej właściwości unikalnej dla jednorożca. Teraz problem został zredukowany do uśredniania długości klaksonu na liście jednorożców.OP, daj mi znać, jeśli nadal będę nieporozumienie ...
źródło
HerdMember
), który inicjujemy z koniem lub jednorożcem (uwalniając konia i jednorożca od potrzeby relacji podtypu ).HerdMember
może następnie wdrożyć dowolnie,isUnicorn()
ale uzna to za stosowne, a następnie sugeruję rozwiązanie filtrujące.Metoda GetUnicorns (), która zwraca IEnumerable, wydaje mi się najbardziej eleganckim, elastycznym i uniwersalnym rozwiązaniem. W ten sposób możesz poradzić sobie z dowolną (kombinacją) cech, które określają, czy koń przejdzie jako jednorożec, a nie tylko typ klasy lub wartość określonej właściwości.
źródło
horses.ofType<Unicorn>...
konstrukcji. PełniącGetUnicorns
funkcję, byłby to jeden liniowiec, ale byłby ponadto odporny na zmiany w relacji koń / jednorożec z perspektywy dzwoniącego.IEnumerable<Horse>
, chociaż twoje kryteria jednorożca są w jednym miejscu, są zamknięte, więc dzwoniący muszą poczynić założenia, dlaczego potrzebują jednorożców (mogę uzyskać zupę z małży, zamawiając dziś zupę dnia, ale to nie oznacza to znaczy, że dostanę to jutro, robiąc to samo). Ponadto musisz udostępnić wartość domyślną rogu naHorse
. JeśliUnicorn
jest to własny typ, musisz utworzyć nowy typ i zachować odwzorowania typów, które mogą wprowadzić narzut.