Piszę program, który wymaga pracy ze współrzędnymi biegunowymi i kartezjańskimi.
Czy ma sens tworzenie dwóch różnych struktur dla każdego rodzaju punktów, jeden z X
i Y
członkowie, a drugi z R
i Theta
członkowie.
Lub jest go zbyt dużo i lepiej jest mieć tylko jedną z struct first
i second
jako członków.
To, co piszę, jest proste i niewiele się zmieni. Ale jestem ciekaw, co jest lepsze z punktu widzenia projektowania.
Myślę, że pierwsza opcja jest lepsza. Wydaje się bardziej czytelny i skorzystam z kontroli typu.
Odpowiedzi:
Widziałem oba rozwiązania, więc zdecydowanie zależy to od kontekstu.
Aby zwiększyć czytelność, posiadanie wielu struktur, jak sugerujesz, jest bardzo skuteczne. Jednak w niektórych środowiskach chcesz wykonywać typowe operacje na tych strukturach i znajdujesz powielanie kodu, na przykład operacje wektorowe na macierzach *. Może to być frustrujące, gdy dana operacja nie jest dostępna w twoim smaku wektora, ponieważ nikt jej tam nie przenosił.
Skrajnym rozwiązaniem (które ostatecznie daliśmy) jest posiadanie klasy bazowej opartej na szablonie CRTP z funkcjami get <0> () get <1> () i get <2> (), aby uzyskać elementy w sposób ogólny. Funkcje te są następnie zdefiniowane w strukturze kartezjańskiej lub polarnej, która wywodzi się z tej klasy bazowej. Rozwiązuje wszystkie problemy, ale ma dość głupią cenę: konieczność uczenia się metaprogramowania szablonów. Jeśli jednak metaprogramowanie szablonów jest już uczciwą grą dla twojego projektu, może być dobre.
źródło
Tak, to ma sens.
Wartość struktury nie polega tylko na tym, że hermetyzuje ona dane pod przydatną nazwą. Wartość polega na tym, że kodyfikuje twoje intencje, dzięki czemu kompilator może pomóc ci sprawdzić, czy pewnego dnia ich nie naruszysz (np. Pomylisz zestaw współrzędnych biegunowych z zestawem kartezjańskich).
Ludzie źle pamiętają takie dręczące szczegóły, ale potrafią tworzyć odważne, pomysłowe plany. Komputery są dobre w myleniu szczegółów, a złe w kreatywnych planach. Dlatego zawsze dobrym pomysłem jest przeniesienie tak drobnych szczegółów do komputera, abyś mógł swobodnie pracować nad wielkim planem.
źródło
Tak, chociaż zarówno kartezjański, jak i biegunowy są (na ich miejscu) niezwykle sensownymi schematami reprezentacji współrzędnych, najlepiej, aby nigdy nie były mieszane (jeśli masz punkt kartezjański {1,1}, jest to zupełnie inny punkt niż biegun {1,1 }).
W zależności od potrzeb, może on również być warta realizacji współrzędnych interfejs, za pomocą metod, takich jak
X()
,Y()
,Displacement()
iAngle()
(ewentualnieRadius()
iTheta()
, w zależności od).źródło
Ostatecznie celem programowania jest przełączanie bitów tranzystora w celu wykonania użytecznej pracy. Ale myślenie na tak niskim poziomie doprowadziłoby do niemożliwego do opanowania szaleństwa, dlatego istnieją języki programowania wyższego poziomu, które pomogą ci ukryć złożoność.
Jeśli utworzysz tylko jedną strukturę z nazwanymi członkami
first
isecond
, wówczas nazwy te nic nie znaczą; traktowałbyś je zasadniczo jako adresy pamięci. To przeczy celowi języka programowania wysokiego poziomu.Co więcej, tylko dlatego, że wszystkie są reprezentowalne jako
double
nie oznacza, że można ich używać zamiennie. Na przykład θ jest kątem bezwymiarowym, podczas gdy y ma jednostki długości. Ponieważ typy nie są logicznie zastępowalne, powinny być dwiema niekompatybilnymi strukturami.Jeśli naprawdę chcesz zagrać w sztuczki związane z ponownym użyciem pamięci - a prawie na pewno nie powinieneś - możesz użyć
union
typu C, aby wyrazić swoją intencję.źródło
Po pierwsze, obaj wyraźnie, zgodnie z całkowicie dźwiękową odpowiedzią @ Kilian-foth.
Chciałbym jednak dodać:
Zapytaj: Czy naprawdę masz operacje, które są typowe dla obu, gdy są traktowane jako pary
double
? Zauważ, że to nie to samo, co powiedzenie, że masz operacje, które dotyczą obu na ich własnych warunkach. Na przykład „fabuła (Coord)” dba o to, czyCoord
jest to biegun, czy kartezjański. Z drugiej strony utrwalenie pliku po prostu traktuje dane takie, jakie są. Jeśli naprawdę nie mają operacje generycznych, należy rozważyć albo definiując klasę bazową lub definiowania konwerter dostd::pair<double, double>
Ortuple
lub cokolwiek masz w swoim języku.Ponadto, jednym podejściem może być traktowanie jednego typu współrzędnych jako bardziej fundamentalnego, a drugiego jedynie jako obsługi interakcji użytkownika lub zewnętrznej.
Więc może zapewnić wszystkie podstawowe operacje są kodowane
Cartesian
, a następnie zapewnić wsparcie dla konwersjiPolar
doCartesian
. Pozwala to uniknąć wdrażania różnych wersji wielu operacji.źródło
Możliwym rozwiązaniem, w zależności od języka i jeśli wiesz, że obie klasy będą miały podobne metody i operacje, byłoby jednokrotne zdefiniowanie klasy i użycie aliasów typów w celu jawnego nazwania typów inaczej.
Ma to również tę zaletę, że dopóki klasy są dokładnie takie same, możesz zachować tylko jedną, ale gdy tylko zajdzie potrzeba ich zmiany, nie trzeba modyfikować kodu za ich pomocą, ponieważ typy były już używane oczywiście.
Inną opcją, zależną ponownie od użycia klas (jeśli potrzebujesz polimorfizmu itp.), Jest użycie dziedziczenia publicznego dla obu nowych typów, aby miały one ten sam interfejs publiczny co typ wspólny, który reprezentują. Pozwala to również na oddzielną ewolucję typów.
źródło
Uważam, że posiadanie takich samych nazw członków jest w tym przypadku złym pomysłem, ponieważ sprawia, że kod jest bardziej podatny na błędy.
Wyobraź sobie scenariusz: masz kilka kartezjańskich punktów: pntA i pntB. Następnie z jakiegoś powodu decydujesz, że powinny być lepiej reprezentowane we współrzędnych biegunowych, i zmieniasz deklarację i konstruktor.
Teraz, jeśli wszystkie twoje operacje były tylko wywołaniami metod, takimi jak:
więc wszystko w porządku. Ale co, jeśli użyjesz członków wyraźnie? Porównać
W pierwszym przypadku kod nie zostanie skompilowany. Natychmiast zobaczysz błąd i będziesz mógł go naprawić. Ale jeśli masz takie same nazwy członków, błąd będzie tylko na poziomie logicznym, znacznie trudniejszy do wykrycia.
Jeśli piszesz w języku nie zorientowanym obiektowo, jeszcze łatwiej jest przekazać niewłaściwą strukturę do funkcji. Co powstrzyma Cię przed napisaniem następującego kodu?
Z drugiej strony różne typy danych pozwalają znaleźć błąd podczas kompilacji.
źródło