Zakładając język z pewnym nieodłącznym rodzajem bezpieczeństwa (np. Nie JavaScript):
Biorąc pod uwagę metodę, która akceptuje a SuperType
, wiemy, że w większości przypadków, w których możemy ulec pokusie przeprowadzenia testów typu w celu wybrania akcji:
public void DoSomethingTo(SuperType o) {
if (o isa SubTypeA) {
o.doSomethingA()
} else {
o.doSomethingB();
}
}
Zwykle powinniśmy, jeśli nie zawsze, stworzyć jedną, nadającą się do zastąpienia metodę SuperType
i zrobić to:
public void DoSomethingTo(SuperType o) {
o.doSomething();
}
... w którym każdy podtyp otrzymuje własną doSomething()
implementację. Reszta naszej aplikacji może być odpowiednio nieświadoma, czy dana dana SuperType
jest naprawdę SubTypeA
a SubTypeB
.
Wspaniale.
Ale wciąż mamy is a
podobne operacje w większości, jeśli nie we wszystkich, językach bezpiecznych dla typu. A to sugeruje potencjalną potrzebę jawnego testowania typu.
Tak więc, w jakich sytuacjach, jeśli w ogóle, powinny nas lub musi wykonujemy wyraźny badanie typu?
Wybacz mi moją nieobecność lub brak kreatywności. Wiem, że już to zrobiłem; ale to było tak dawno temu, że nie pamiętam, czy to, co zrobiłem, było dobre! W niedawnej pamięci nie sądzę, że spotkałem się z potrzebą testowania typów poza moim kowbojskim JavaScript.
źródło
Odpowiedzi:
„Nigdy” to kanoniczna odpowiedź na „kiedy testowanie typu jest w porządku?” Nie ma sposobu, aby to udowodnić lub obalić; jest częścią systemu przekonań o tym, co czyni „dobry projekt” lub „dobry projekt obiektowy”. To także hokum.
Oczywiście, jeśli masz zintegrowany zestaw klas, a także więcej niż jedną lub dwie funkcje, które wymagają tego rodzaju bezpośredniego testowania typu, prawdopodobnie robisz to źle. To, czego naprawdę potrzebujesz, to metoda, która jest różnie zaimplementowana
SuperType
i jej podtypy. Jest to nieodłączna część programowania obiektowego i istnieje cały powód istnienia klas i dziedziczenia.W tym przypadku jawne testowanie typu jest błędne nie dlatego, że testowanie typu jest z natury błędne, ale dlatego, że język ma już czysty, rozszerzalny, idiomatyczny sposób osiągnięcia dyskryminacji typów, a ty go nie użyłeś. Zamiast tego powróciliście do prymitywnego, kruchego, nierozszerzalnego idiomu.
Rozwiązanie: użyj idiomu. Jak zasugerowałeś, dodaj metodę do każdej z klas, a następnie pozwól, aby standardowe algorytmy dziedziczenia i wyboru metody określiły, który przypadek ma zastosowanie. Lub jeśli nie możesz zmienić typów podstawowych, podklas i dodaj tam swoją metodę.
Tyle o konwencjonalnej mądrości i kilku odpowiedziach. Niektóre przypadki, w których sensowne jest jawne testowanie typu:
To jednorazowe. Jeśli miałeś dużo do czynienia z dyskryminacją typów, możesz rozszerzyć typy lub podklasę. Ale ty nie. Masz tylko jedno lub dwa miejsca, w których potrzebujesz jawnego testowania, więc nie warto wracać i pracować w hierarchii klas, aby dodać funkcje jako metody. Lub nie warto praktycznego wysiłku, aby dodać rodzaj ogólności, testowanie, recenzje projektu, dokumentację lub inne atrybuty klas podstawowych dla tak prostego, ograniczonego użycia. W takim przypadku dodanie funkcji wykonującej bezpośrednie testy jest racjonalne.
Nie możesz dostosowywać klas. Myślisz o podklasach - ale nie możesz. Na przykład wyznaczono wiele klas w Javie
final
. Próbujesz wrzucić „a”,public class ExtendedSubTypeA extends SubTypeA {...}
a kompilator mówi ci bez żadnych wątpliwości, że to, co robisz, nie jest możliwe. Przepraszamy, wdzięk i wyrafinowanie modelu obiektowego! Ktoś zdecydował, że nie możesz rozszerzyć ich typów! Niestety, wiele standardowych bibliotek jestfinal
, a tworzenie klasfinal
jest powszechną wskazówką projektową. Końcowe uruchomienie funkcji jest dla ciebie dostępne.BTW, nie ogranicza się to do języków wpisywanych statycznie. Dynamiczny język Python ma wiele klas podstawowych, których pod osłonami zaimplementowanymi w C nie da się tak naprawdę modyfikować. Podobnie jak Java, obejmuje większość standardowych typów.
Twój kod jest zewnętrzny. Tworzysz klasy i obiekty pochodzące z szeregu serwerów baz danych, silników oprogramowania pośredniego i innych baz kodu, których nie możesz kontrolować ani dostosowywać. Twój kod jest tylko niewielkim konsumentem obiektów generowanych gdzie indziej. Nawet jeśli możesz podklasę
SuperType
, nie będziesz w stanie uzyskać tych bibliotek, od których zależy generowanie obiektów w podklasach. Podadzą ci instancje typów, które znają, a nie twoje warianty. Nie zawsze tak jest ... czasami są budowane z myślą o elastyczności i dynamicznie tworzą instancje klas, które je karmisz. Lub zapewniają mechanizm rejestrowania podklas, które mają budować ich fabryki. Parsery XML wydają się szczególnie dobre w zapewnianiu takich punktów wejścia; patrz np lub lxml w Pythonie . Ale większość baz kodu nie zapewnia takich rozszerzeń. Oddają ci klasy, z którymi zostały zbudowane i o których wiedzą. Generalnie nie ma sensu umieszczać ich wyników w wynikach niestandardowych tylko po to, aby można było użyć selektora typu czysto obiektowego. Jeśli zamierzasz dokonać dyskryminacji typu, będziesz musiał to zrobić stosunkowo brutalnie. Twój kod testowania typu wygląda wtedy całkiem dobrze.Leki ogólne / wielokrotne dla biednych osób. Chcesz zaakceptować wiele różnych typów kodu i masz wrażenie, że posiadanie szeregu metod specyficznych dla typu nie jest taktowne.
public void add(Object x)
Wydaje się logiczne, ale nie tablicąaddByte
,addShort
,addInt
,addLong
,addFloat
,addDouble
,addBoolean
,addChar
, iaddString
wariantów (by wymienić tylko kilka). Dzięki funkcjom lub metodom, które wymagają wysokiego typu, a następnie określają, co robić według rodzaju - nie zdobędą nagrody Czystości podczas corocznego sympozjum projektowego Booch-Liskov, ale rezygnują z Węgierskie nazewnictwo zapewni prostsze API. W pewnym sensie twójis-a
lubis-instance-of
testowanie to symulacja generyczna lub wielozadaniowa w kontekście językowym, który natywnie tego nie obsługuje.Wbudowana obsługa języka zarówno dla ogólnych, jak i pisanych kaczych zmniejsza potrzebę sprawdzania typów, zwiększając prawdopodobieństwo „robienia czegoś wdzięcznego i odpowiedniego”. Wielokrotny wysyłka wybór / interfejs widziany w językach takich jak Julia i przejść podobnie zastąpić bezpośredniego badania typu z wbudowanymi mechanizmami selekcji opartej typu „co robić”. Ale nie wszystkie języki je obsługują. Jawa, na przykład, jest zasadniczo pojedyncza wysyłka, a jej idiomy nie są zbyt przyjazne do pisania na klawiaturze.
Ale nawet przy wszystkich tych funkcjach dyskryminacji typów - dziedziczeniu, rodzajowych, pisaniu kaczych i wielokrotnym wysyłaniu - czasem wygodnie jest mieć jedną, skonsolidowaną procedurę, która sprawia, że robisz coś w oparciu o typ obiektu jasne i natychmiastowe. W metaprogramowaniu stwierdziłem, że jest to w zasadzie nieuniknione. To, czy powrót do zapytań typu bezpośredniego stanowi „pragmatyzm w działaniu” czy „brudne kodowanie”, będzie zależeć od filozofii i przekonań projektowych.
źródło
(1.0).Equals(1.0f)
uzyskujemy nieprzyzwoite zachowania, takie jak w .NET: zwracanie wartości true [argument promuje dodouble
], ale(1.0f).Equals(1.0)
dawanie wartości false [argument promuje doobject
]; w JavieMath.round(123456789*1.0)
daje 123456789, aleMath.round(123456789*1.0)
123456792 [argument promujefloat
raczej niżdouble
].Math.round
wyglądają identycznie jak ja. Co za różnica?Math.round(123456789)
[wskazywać na to, co może się stać, jeśli ktoś przepisze,Math.round(thing.getPosition() * COUNTS_PER_MIL)
aby zwrócić nieskalowaną wartość pozycji, nie zdając sobie sprawy, żegetPosition
zwracaint
lublong
.]Główną sytuacją, jakiej kiedykolwiek potrzebowałem, była porównywanie dwóch obiektów, na przykład w
equals(other)
metodzie, która może wymagać różnych algorytmów w zależności od dokładnego typuother
. Nawet wtedy jest to dość rzadkie.Inną sytuacją, w której pojawiłem się, znowu bardzo rzadko, jest po deserializacji lub analizie składni, gdzie czasami potrzebujesz jej, aby bezpiecznie rzucić na bardziej konkretny typ.
Czasami potrzebujesz po prostu włamania do obejścia kodu, którego nie kontrolujesz. Jest to jedna z tych rzeczy, których tak naprawdę nie chcesz regularnie używać, ale cieszymy się, że jest tam, kiedy naprawdę jej potrzebujesz.
źródło
BaseClass base = deserialize(input)
, ponieważ nie znasz jeszcze typu, a następnieif (base instanceof Derived) derived = (Derived)base
zapisujesz go jako dokładny typ pochodny.Standardowy (ale miejmy nadzieję rzadki) przypadek wygląda następująco: w poniższej sytuacji
funkcje
DoSomethingA
lubDoSomethingB
nie mogą być łatwo zaimplementowane jako funkcje składowe drzewa dziedziczeniaSuperType
/SubTypeA
/SubTypeB
. Na przykład jeśliDoSomethingXXX
do tej biblioteki oznaczałoby wprowadzenie zabronionej zależności.Uwaga: często zdarzają się sytuacje, w których można obejść ten problem (na przykład, tworząc opakowanie lub adapter dla
SubTypeA
iSubTypeB
, lub próbującDoSomething
całkowicie zaimplementować w zakresie podstawowych operacjiSuperType
), ale czasami te rozwiązania nie są warte kłopotu lub sprawiają, że rzeczy bardziej skomplikowane i mniej rozszerzalne niż wykonywanie jawnego testu typu.Przykład z mojej wczorajszej pracy: Miałem sytuację, w której zamierzałem zrównoleglić przetwarzanie listy obiektów (typu
SuperType
, z dokładnie dwoma różnymi podtypami, gdzie jest bardzo mało prawdopodobne, że będzie ich więcej). Niezrównana wersja zawierała dwie pętle: jedną pętlę dla obiektów podtypu A, wywołującąDoSomethingA
, i drugą pętlę dla obiektów podtypu B, wywołującąDoSomethingB
.Metody „DoSomethingA” i „DoSomethingB” są zarówno czasochłonnymi obliczeniami, wykorzystującymi informacje kontekstowe, które nie są dostępne w zakresie podtypów A i B. (więc nie ma sensu implementować ich jako funkcji składowych podtypów). Z punktu widzenia nowej „równoległej pętli” znacznie ułatwia to, radząc sobie z nimi jednolicie, więc zaimplementowałem funkcję podobną do
DoSomethingTo
powyższej. Jednak spojrzenie na implementacje „DoSomethingA” i „DoSomethingB” pokazuje, że działają one bardzo odmiennie wewnętrznie. Więc próba wdrożenia ogólnego „DoSomething” poprzez rozszerzenieSuperType
o wiele abstrakcyjnych metod tak naprawdę nie zadziałała lub oznaczałaby całkowite przeprojektowanie.źródło
SuperType
i to podklasy?NSJSONSerialization
W Obj-C), ale nie chcesz po prostu ufać, że odpowiedź zawiera oczekiwany typ, więc przed użyciem go sprawdź (np.if ([theResponse isKindOfClass:[NSArray class]])...
) .Jak nazywa to wujek Bob:
W jednym ze swoich odcinków Clean Coder podał przykład wywołania funkcji używanej do zwracania
Employee
s.Manager
jest podtypemEmployee
. Załóżmy, że mamy usługę aplikacji, która akceptujeManager
identyfikator i wzywa go do biura :) FunkcjagetEmployeeById()
zwraca supertypEmployee
, ale chcę sprawdzić, czy w tym przypadku użycia został zwrócony menedżer.Na przykład:
Tutaj sprawdzam, czy pracownik zwrócony przez zapytanie jest w rzeczywistości menedżerem (tzn. Oczekuję, że będzie menedżerem, a jeśli nie, to szybko zawiedzie).
Nie najlepszy przykład, ale w końcu to wujek Bob.
Aktualizacja
Zaktualizowałem przykład tak bardzo, jak pamiętam z pamięci.
źródło
Manager
implementacjasummon()
nie rzuca wyjątku w tym przykładzie?CEO
może wezwaćManager
s.Employee
, należy tylko dbać, że robi coś, co zachowuje się jakEmployee
. Jeśli różne podklasyEmployee
mają różne uprawnienia, obowiązki itp., Co sprawia, że testowanie typu jest lepszą opcją niż prawdziwy system uprawnień?Nigdy.
is
klauzulę lub (w niektórych językach lub w zależności od scenariusz), ponieważ nie można rozszerzyć typu bez modyfikacji elementów wewnętrznych funkcjiis
sprawdzającej.is
czeki są mocnym znakiem, że naruszasz Zasadę Zastępstwa Liskowa . Wszystko, co działa,SuperType
powinno być całkowicie nieświadome, jakie mogą być podtypy.To powiedziawszy,
is
czeki mogą być mniej złe niż inne alternatywy. Umieszczenie wszystkich powszechnych funkcji w klasie bazowej jest ciężkie i często prowadzi do gorszych problemów. Używanie pojedynczej klasy, która ma flagę lub wyliczenie dla tego, jakiego „typu” instancja jest… jest gorsze niż okropne, ponieważ teraz rozprzestrzeniasz obchodzenie systemu typów na wszystkich konsumentów.Krótko mówiąc, zawsze powinieneś uważać sprawdzanie typu za silny zapach kodu. Ale tak jak w przypadku wszystkich wytycznych, będą chwile, w których będziesz zmuszony wybrać, które naruszenie wytycznych jest najmniej obraźliwe.
źródło
instanceof
wycieka szczegóły implementacji i przerywa abstrakcję.IEnumerable<T>
nie obiecuje, że istnieje „ostatni” element. Jeśli twoja metoda potrzebuje takiego elementu, powinna wymagać typu gwarantującego jego istnienie. Podtypy tego typu mogą następnie zapewnić wydajne implementacje metody „Last”.Jeśli masz dużą bazę kodu (ponad 100 000 wierszy kodu) i zbliżasz się do wysyłki lub pracujesz w oddziale, który później będzie musiał zostać scalony, a zatem zmiana sposobu radzenia sobie z nim wiąże się z dużym kosztem / ryzykiem.
Czasami masz wtedy opcję dużego refraktora systemu lub jakiegoś prostego zlokalizowanego „testowania typu”. Stwarza to dług techniczny, który powinien zostać spłacony jak najszybciej, ale często tak nie jest.
(Nie można podać przykładu, ponieważ każdy kod, który jest wystarczająco mały, aby go użyć jako przykład, jest również wystarczająco mały, aby lepszy projekt był wyraźnie widoczny.)
Innymi słowy, gdy celem jest wypłata wynagrodzenia, a nie „podniesienie głosów” za czystość projektu.
Innym częstym przypadkiem jest kod interfejsu użytkownika, gdy na przykład wyświetlasz inny interfejs użytkownika dla niektórych typów pracowników, ale oczywiście nie chcesz, aby koncepcje interfejsu użytkownika uciekały do wszystkich klas „domeny”.
Możesz użyć „testowania typu”, aby zdecydować, która wersja interfejsu użytkownika ma zostać wyświetlona, lub mieć jakąś fantazyjną tabelę odnośników, która konwertuje z „klas domen” do „klas interfejsu użytkownika”. Tabela przeglądowa jest tylko sposobem na ukrycie „testowania typu” w jednym miejscu.
(Kod aktualizujący bazę danych może mieć takie same problemy jak kod interfejsu użytkownika, jednak zwykle masz tylko jeden zestaw kodu aktualizującego bazę danych, ale możesz mieć wiele różnych ekranów, które muszą dostosować się do typu wyświetlanego obiektu.)
źródło
Implementacja LINQ wykorzystuje wiele sprawdzania typu dla możliwych optymalizacji wydajności, a następnie powrót do IEnumerable.
Najbardziej oczywistym przykładem jest prawdopodobnie metoda ElementAt (niewielki fragment źródła .NET 4.5):
Ale w klasie Enumerable jest wiele miejsc, w których stosuje się podobny wzorzec.
Być może optymalizacja pod kątem wydajności dla często używanego podtypu jest poprawnym zastosowaniem. Nie jestem pewien, jak można to lepiej zaprojektować.
źródło
IEnumerable<T>
wiele metod takich jak teList<T>
, wraz zFeatures
właściwością wskazującą, które metody mogą działać dobrze, powoli lub wcale, a także różne założenia, które konsument może bezpiecznie poczynić na temat kolekcji (np. czy jej rozmiar i / lub istniejąca zawartość jest gwarantowana, że nigdy się nie zmieni [typ może obsługiwać,Add
jednocześnie gwarantując, że istniejąca zawartość będzie niezmienna]).Istnieje przykład, który często pojawia się w rozwoju gier, szczególnie w wykrywaniu kolizji, z którym trudno sobie poradzić bez użycia jakiejś formy testowania typu.
Załóżmy, że wszystkie obiekty gry pochodzą ze wspólnej klasy podstawowej
GameObject
. Każdy obiekt ma sztywny korpus kolizji kształtCollisionShape
, który może zapewnić wspólny interfejs (co pozycji zapytania, orientacji itd), lecz rzeczywiste kształty kolizji wszystkie będą konkretne podklasy, takie jakSphere
,Box
,ConvexHull
itp przechowywanie informacji specyficznych dla danego typu geometryczny (zobacz tutaj prawdziwy przykład)Teraz, aby przetestować kolizje, muszę napisać funkcję dla każdej pary typów kształtów kolizji:
które zawierają określoną matematykę potrzebną do wykonania przecięcia tych dwóch typów geometrycznych.
Przy każdym „tyknięciu” mojej pętli gry muszę sprawdzać, czy pary obiektów nie kolizją. Ale mam tylko dostęp do
GameObject
si odpowiadających imCollisionShape
. Oczywiście muszę znać konkretne typy, aby wiedzieć, do której funkcji wykrywania kolizji należy zadzwonić. Nawet podwójna wysyłka (która logicznie nie różni się od sprawdzania typu) może tutaj pomóc *.W praktyce w tej sytuacji silniki fizyki, które widziałem (Bullet i Havok), polegają na testowaniu typu takiej czy innej formy.
Nie twierdzę, że jest to dobre rozwiązanie, po prostu może być najlepszym z niewielkiej liczby możliwych rozwiązań tego problemu
* Technicznie jest to możliwe użycie podwójnego wysyłkę w straszliwej i skomplikowany sposób, który wymagałby n (n + 1) / 2 kombinacje (gdzie n oznacza liczbę typów kształtu, który nie ma) i będzie zaciemniać tylko to, czego naprawdę robi których jednocześnie odkrywa rodzaje tych dwóch kształtów, więc nie uważam tego za realistyczne rozwiązanie.
źródło
Czasami nie chcesz dodawać wspólnej metody do wszystkich klas, ponieważ tak naprawdę to nie jest odpowiedzialny za wykonanie tego konkretnego zadania.
Na przykład chcesz narysować niektóre elementy, ale nie chcesz dodawać do nich kodu rysunku (co ma sens). W językach nieobsługujących wielokrotnej wysyłki możesz otrzymać następujący kod:
Staje się to problematyczne, gdy ten kod pojawia się w kilku miejscach i trzeba go wszędzie modyfikować podczas dodawania nowego typu jednostki. W takim przypadku można tego uniknąć, stosując wzorzec użytkownika, ale czasem lepiej jest po prostu zachować prostotę i nie nadużytkować. Są to sytuacje, w których testowanie typu jest prawidłowe.
źródło
Używam tylko w połączeniu z refleksją. Ale nawet wtedy sprawdzanie dynamiczne jest przeważnie nie zakodowane na stałe w określonej klasie (lub tylko zakodowane na stałe w specjalnych klasach, takich jak
String
lubList
).Przez dynamiczne sprawdzanie mam na myśli:
i nie zakodowane na stałe
źródło
Testowanie typu i odlewanie typu to dwie ściśle ze sobą powiązane koncepcje. Tak ściśle powiązane, że jestem przekonany, że nigdy nie powinieneś przeprowadzać testu typu, chyba że masz zamiar wpisać rzut obiektu na podstawie wyniku.
Gdy myślisz o idealnym projektowaniu obiektowym, nigdy nie powinno się przeprowadzać testów typu (i rzutowania) . Ale mam nadzieję, że do tej pory zorientowałeś się, że programowanie obiektowe nie jest idealne. Czasami, szczególnie w przypadku kodu niższego poziomu, kod nie może pozostać wierny ideałowi. Tak jest w przypadku ArrayLists w Javie; ponieważ nie wiedzą w czasie wykonywania, jaka klasa jest przechowywana w tablicy, tworzą
Object[]
tablice i statycznie rzutują je na poprawny typ.Zwrócono uwagę, że powszechna potrzeba testowania typu (i rzutowania) wynika z
Equals
metody, która w większości języków ma być prostaObject
. Implementacja powinna mieć kilka szczegółowych kontroli, aby sprawdzić, czy dwa obiekty są tego samego typu, co wymaga możliwości przetestowania, jakiego typu są.Badanie typu pojawia się również często w refleksji. Często masz metody, które zwracają
Object[]
lub inną ogólną tablicę i chcesz wyciągnąć wszystkieFoo
obiekty z dowolnego powodu. Jest to całkowicie uzasadnione zastosowanie testowania typu i odlewania.Ogólnie rzecz biorąc, testowanie typu jest złe, gdy niepotrzebnie łączy kod z tym, jak napisano określoną implementację. Może to łatwo prowadzić do konieczności przeprowadzenia konkretnego testu dla każdego typu lub kombinacji typów, na przykład jeśli chcesz znaleźć przecięcie linii, prostokątów i okręgów, a funkcja przecięcia ma inny algorytm dla każdej kombinacji. Twoim celem jest umieszczenie wszelkich szczegółów specyficznych dla jednego rodzaju obiektu w tym samym miejscu co ten obiekt, ponieważ ułatwi to utrzymanie i rozszerzenie twojego kodu.
źródło
ArrayLists
nie wiem, czy klasa jest przechowywana w środowisku wykonawczym, ponieważ Java nie miała generycznych, a kiedy zostały w końcu wprowadzone Oracle wybrało kompatybilność wsteczną z kodem bez generycznych.equals
ma ten sam problem i i tak jest wątpliwą decyzją projektową; porównania równości nie mają sensu dla każdego typu.String x = (String) myListOfStrings.get(0)
Object
tak było to możliwe; generics w Javie zapewnia tylko niejawne rzutowanie bezpieczne dzięki regułom kompilatora.Jest to dopuszczalne w przypadku, gdy musisz podjąć decyzję, która obejmuje dwa typy, a decyzja ta jest enkapsulowana w obiekcie spoza tej hierarchii typów. Załóżmy na przykład, że planujesz, który obiekt zostanie następnie przetworzony na liście obiektów oczekujących na przetworzenie:
Powiedzmy teraz, że nasza logika biznesowa jest dosłownie „wszystkie samochody mają pierwszeństwo przed łodziami i ciężarówkami”. Dodanie
Priority
właściwości do klasy nie pozwala na wyraźne wyrażenie logiki biznesowej, ponieważ skończy się na tym:Problem polega na tym, że aby zrozumieć kolejność priorytetową, musisz spojrzeć na wszystkie podklasy lub innymi słowy, dodałeś sprzężenie do podklas.
Powinieneś oczywiście przekształcić priorytety w stałe i umieścić je w klasie samodzielnie, co pomaga utrzymać wspólne planowanie logiki biznesowej:
Jednak w rzeczywistości algorytm szeregowania może się zmienić w przyszłości i ostatecznie może zależeć od więcej niż tylko typu. Na przykład może powiedzieć „ciężarówki o masie 5000 kg mają szczególny priorytet w stosunku do wszystkich innych pojazdów”. Dlatego algorytm szeregowania należy do własnej klasy i dobrym pomysłem jest sprawdzenie typu, aby ustalić, który z nich powinien być pierwszy:
Jest to najprostszy sposób na wdrożenie logiki biznesowej i jednocześnie najbardziej elastyczny na przyszłe zmiany.
źródło
null
, aString
lub aString[]
. Jeśli 99% obiektów będzie potrzebowało dokładnie jednego łańcucha, kapsułkowanie każdego łańcucha w osobno skonstruowanejString[]
może spowodować znaczne obciążenie pamięci. Obsługa przypadku pojedynczego ciągu przy użyciu bezpośredniego odwołania do aString
będzie wymagać więcej kodu, ale pozwoli zaoszczędzić miejsce na dysku i może przyspieszyć działanie.Testowanie typu jest narzędziem, używaj go mądrze i może być potężnym sojusznikiem. Użyj go źle, a twój kod zacznie wąchać.
W naszym oprogramowaniu otrzymywaliśmy wiadomości przez sieć w odpowiedzi na zapytania. Wszystkie zdserializowane wiadomości miały wspólną klasę podstawową
Message
.Same klasy były bardzo proste, tylko ładunek jako wpisane właściwości C # i procedury ich rozbierania i rozszyfrowywania (w rzeczywistości generowałem większość klas przy użyciu szablonów t4 z opisu XML formatu komunikatu)
Kod mógłby wyglądać następująco:
To prawda, że można argumentować, że architektura komunikatu może być lepiej zaprojektowana, ale została zaprojektowana dawno temu, a nie dla C #, więc taka jest. Tutaj testy typu rozwiązały dla nas prawdziwy problem w niezbyt odrapany sposób.
Warto zauważyć, że C # 7.0 dostaje dopasowanie wzorca (co pod wieloma względami jest testowaniem typu na sterydach), nie może być wszystko źle ...
źródło
Weź ogólny parser JSON. Wynikiem pomyślnej analizy jest tablica, słownik, ciąg, liczba, wartość logiczna lub wartość null. Może to być dowolny z nich. A elementy tablicy lub wartości w słowniku mogą ponownie być dowolnymi z tych typów. Ponieważ dane te pochodzą spoza programu, trzeba zaakceptować żadnego rezultatu (czyli trzeba przyjąć ją bez upaść, ty może odrzucić wynik, który nie jest tym, czego się spodziewać).
źródło