Wiele klas o tej samej nazwie, ale różnych przestrzeniach nazw?

11

Natknąłem się na jakiś kod (C #, jeśli ma to znaczenie), który ma klasy o tej samej nazwie, ale różniące się przestrzenią nazw. Wszystkie zwykle przedstawiają tę samą logiczną rzecz, ale często są to różne „widoki” tego samego obiektu. Różne przestrzenie nazw są czasami częścią tego samego rozwiązania, a nawet tej samej biblioteki dll.

Mam swoje przemyślenia, ale nie mogłem znaleźć niczego ostatecznego w tej praktyce, co można by uznać za sprytne użycie dostępnych narzędzi lub wzorzec, którego należy unikać.

Pytanie na temat nazewnictwa smerfa dotyczy tego, ale z drugiej strony. Ujednoznacznienie nazw wymagałoby prawdopodobnie arbitralnego i wszechobecnego prefiksu, który to pytanie chciał wyeliminować.

Jakie są wytyczne dotyczące tej praktyki?

Telastyn
źródło
1
W OOP istnieje wiele wyjątków, ale na początku uważam ten zły projekt, zwłaszcza jeśli wszystkie klasy mają ten sam poziom dostępności. Kiedy czytam nazwę klasy, w dowolnym pliku, chcę wiedzieć, w której przestrzeni nazw klasa jest natychmiast zlokalizowana, bez konieczności odwoływania się do dyrektyw dotyczących przestrzeni nazw.
Leopold Asperger
Istnieje uzasadniony przypadek posiadania wielu powiązanych obiektów, które zajmują się tym samym stanem, ale zazwyczaj są one oddzielone funkcją. Być może masz jeden obiekt, który dostosowuje model do widoku. Inny może wykonać obliczenia na obiekcie. Innym może być adapter lub fasada. Więc może masz FooAdapter, FooViewer, FooCalculator, itd., Ale na pewno nie nazwał to samo. Jednak bez dodatkowych informacji na temat konkretnych klas, o których mówisz, trudno jest udzielić odpowiedzi.
@JohnGaughan - rozważ Employeejako przykład. Jest pracownik, który jest dla innych pracowników, jeden dla HR, jeden dla zewnętrznego narażenia. Wszyscy są nazywani „pracownikami” i wszyscy mają różne pomocniki (EmployeeRepository, EmployeeView itp.). Wszystkie są w tej samej bibliotece DLL i chociaż mają wspólne właściwości (nazwa, tytuł), wszystkie różnią się (numer telefonu, wynagrodzenie).
Telastyn
Po Employeekilkukrotnej pracy z systemami, które modelują , brzmi to tak, jakbyś potrzebował modelu z połączeniem wszystkich atrybutów i zawierał atrybut typelub department. W przeciwnym razie niepotrzebne jest powielanie kodu.
5
Czy nie był to jeden z powodów, dla których przestrzenie nazw były na początek? Aby zezwolić na kolizje nazw?
Dan Pichelman

Odpowiedzi:

5

Spróbuję udzielić odpowiedzi przeciwko takiemu użyciu, po obejrzeniu i pracowaniu nad tym również w jednym z moich projektów.

Czytelność kodu

Po pierwsze, należy wziąć pod uwagę, że czytelność kodu jest ważna i ta praktyka jest temu przeciwna. Ktoś czyta fragment kodu i powiedzmy, że ma on funkcję doSomething(Employee e). Nie jest to już możliwe do odczytania, ponieważ ze względu na 10 różnych Employeeklas istniejących w różnych pakietach będziesz musiał najpierw skorzystać z deklaracji importowej, aby dowiedzieć się, jakie są twoje dane wejściowe.

Jest to jednak widok wysokiego poziomu i często mamy pozornie przypadkowe konflikty nazw, o które nikt się nie troszczy ani nawet nie znajduje, ponieważ znaczenie można wywnioskować z pozostałego kodu i pakietu, w którym się znajdujesz. Można by nawet argumentować że lokalnie nie ma problemu, ponieważ oczywiście, jeśli widzisz Employeew hrpakiecie, musisz wiedzieć, że mówimy o widoku HR pracownika.

Wszystko się jednak rozpada, gdy tylko opuścisz te paczki. Kiedy pracujesz w innym module / pakiecie / etc i potrzebujesz pracy z pracownikiem, już tracisz czytelność, jeśli nie w pełni kwalifikujesz jego typ. Ponadto posiadanie 10 różnych Employeeklas oznacza, że ​​automatyczne uzupełnianie IDE nie będzie już działać i będziesz musiał ręcznie wybrać typ pracownika.

Duplikacja kodu

Ze względu na naturę każdej z tych klas, które są ze sobą powiązane, Twój kod może ulec pogorszeniu z powodu dużej ilości powielania. W większości przypadków będziesz mieć coś takiego jak imię i nazwisko pracownika lub numer identyfikacyjny, który każda klasa musi wdrożyć. Chociaż każda klasa dodaje swój własny widok, jeśli nie będą dzielić podstawowych danych pracowników, skończysz z ogromną masą bezużytecznego, ale kosztownego kodu.

Złożoność kodu

Co może być tak złożone, że możesz zapytać? W końcu każda z klas może to zrobić tak prosto, jak chce. Tym, co naprawdę staje się problemem, jest sposób propagowania zmian. W rozsądnie bogatym w funkcje oprogramowaniu możesz zmieniać dane pracowników - i chcesz to odzwierciedlać wszędzie. Powiedz, że jedna dama właśnie wyszła za mąż i musisz zmienić jej nazwę od XnaYz tego powodu. Wystarczająco trudne, aby zrobić to dobrze w każdym miejscu, ale jeszcze trudniejsze, gdy masz wszystkie te odrębne klasy. W zależności od innych wyborów projektowych może to łatwo oznaczać, że każda z klas musi implementować własny rodzaj detektora lub być powiadamianym o zmianach - co w zasadzie przekłada się na współczynnik zastosowany do liczby klas, z którymi masz do czynienia . No i oczywiście więcej duplikacji kodu i mniejszej czytelności kodu. Tego typu czynniki mogą być nie do poznania w analizie złożoności, ale z pewnością są denerwujące, gdy zastosujemy je do rozmiaru bazy kodu.

Komunikacja kodowa

Oprócz powyższych problemów ze złożonością kodu, które są również związane z wyborami projektowymi, zmniejszasz również przejrzystość projektu wyższego poziomu i tracisz zdolność do prawidłowej komunikacji z ekspertami w dziedzinie. Kiedy omawiasz architekturę, projekty lub wymagania, nie możesz już swobodnie tworzyć prostych stwierdzeń, takich jak given an employee, you can do .... Deweloper nie będzie już wiedział, co employeetak naprawdę znaczy. Oczywiście ekspert w dziedzinie będzie o tym wiedział. Wszyscy robimy. raczej. Ale pod względem oprogramowania nie jest już tak łatwo się komunikować.

Jak pozbyć się problemu

Po uświadomieniu sobie tego i jeśli twój zespół zgodzi się, że jest to wystarczająco duży problem do rozwiązania, będziesz musiał znaleźć sposób, aby sobie z nim poradzić. Zazwyczaj nie możesz poprosić swojego menedżera, aby dał całemu zespołowi tydzień wolnego, aby mógł wyrzucić śmieci. Zasadniczo będziesz musiał znaleźć sposób na częściowe wyeliminowanie tych klas, pojedynczo. Najważniejszą częścią tego jest decyzja - z całym zespołem - co Employeenaprawdę jest. Czy nie integrować wszystkich poszczególnych atrybutów do jednego Boga-pracownika. Pomyśl bardziej o kluczowym pracowniku i, co najważniejsze, zdecyduj, gdzie Employeebędzie przebywać ta klasa.

Jeśli robisz recenzje kodu, szczególnie łatwo jest upewnić się, że problem przynajmniej nie narasta, tj. Zatrzymać wszystkich na właściwej drodze, gdy chcą Employeeponownie dodać kolejny . Należy również zadbać o to, aby nowe podsystemy przestrzegały uzgodnionych warunków Employeei nie miały dostępu do starych wersji.

W zależności od języka programowania możesz także oznaczyć klasy, które zostaną ostatecznie wyeliminowane, czymś w rodzaju @Deprecatedpomocy dla zespołu w natychmiastowym uświadomieniu sobie, że pracują z czymś, co trzeba będzie zmienić.

Jeśli chodzi o pozbycie się przestarzałych klas pracowniczych, możesz dla każdego indywidualnego przypadku zdecydować, jak najlepiej je wyeliminować, lub po prostu uzgodnić wspólny wzór. Możesz umieścić nazwę klasy i owinąć ją wokół prawdziwego pracownika, możesz użyć wzorów (przychodzi na myśl dekorator lub adapter), lub, lub.

Krótko mówiąc: ta „praktyka” jest technicznie słuszna, ale duszona jest pełnymi ukrytych kosztów, które pojawią się dopiero w dalszej części drogi. Chociaż możesz nie być w stanie natychmiast pozbyć się problemu, możesz od razu zacząć od ograniczenia jego szkodliwych skutków.

Szczery
źródło
Posiadanie klasy Core wydaje się dobrym rozwiązaniem. Inne klasy mogą z niego rozszerzać. Jeśli chodzi o IDE, automatyczne uzupełnianie jest wystarczająco inteligentne, aby wiedzieć, którą klasę zaproponować, aby najlepiej pasowała do kontekstu. Ogólnie nie rozumiem, dlaczego jest to tak duży problem, szczególnie jeśli baza kodów jest używana wewnętrznie.
Poinformowano
1
Nie jest wystarczająco mądry, gdy jesteś poza jednym kontekstem. Rozważ klasę, która faktycznie potrzebuje obu problematycznych klas - autouzupełnianie w ogóle nie pomoże. I to nawet nie odróżni ich od pozostałych dziesięciu klas. Ale prawdziwy problem polega na tym, że nowy członek zespołu, który nawet nie wie, czym właściwie są wszystkie te domeny pakietów i którą powinien wybrać.
Frank
Jeśli w tym samym kontekście używanych jest wiele klas o tej samej nazwie, jest to trudne. Nie widziałem tej sprawy. Widziałem wiele klas o tej samej nazwie, ale nie są one często używane w tym samym kontekście, jak wspomniałeś.
Poinformowano
@randomA - niestety ten przypadek jest dość powszechny w tej bazie kodu.
Telastyn
Dobre punkty, ale tak naprawdę nie podałeś rozwiązania podstawowego problemu, tylko metodologię po rozwiązaniu podstawowego problemu („czym tak naprawdę jest pracownik”). Jedyne oczywiste „rozwiązanie” („zintegruj wszystkie indywidualne atrybuty w jednym boskim pracowniku”), które słusznie odrzuciłeś. Załóżmy, że masz DB.Employee, Marshalling.Employee, ViewModels.Employee, GUI.Views.Employee itp., Jakie jest twoje rozwiązanie?
Pablo H,
9

Scala Collection Framework jest tego dobrym przykładem. Istnieją kolekcje, które można modyfikować i niezmienne, a także kolekcje szeregowe i równoległe (aw przyszłości mogą być również dystrybuowane). Istnieją więc na przykład cztery Mapcechy:

scala.collection.Map
scala.collection.immutable.Map
scala.collection.mutable.Map
scala.collection.concurrent.Map

plus trzy ParMaps:

scala.collecion.parallel.ParMap
scala.collecion.parallel.immutable.ParMap
scala.collecion.parallel.mutable.ParMap

Jeszcze bardziej interesujące:

scala.collection.immutable.Map            extends scala.collection.Map
scala.collection.mutable.Map              extends scala.collection.Map
scala.collection.concurrent.Map           extends scala.collection.mutable.Map

scala.collecion.parallel.ParMap           extends scala.collection.Map
scala.collecion.parallel.immutable.ParMap extends scala.collecion.parallel.ParMap
scala.collecion.parallel.mutable.ParMap   extends scala.collecion.parallel.ParMap

Zatem ParMaprozszerza ParMaprozszerza Mapi Maprozszerza Maprozszerza Map.

Ale spójrzmy prawdzie w oczy: jak inaczej byś je nazwał? To ma sens. Po to są przestrzenie nazw!

Jörg W Mittag
źródło
3
„Po to są przestrzenie nazw!” => +1
svidgen
Podoba mi się to, ale w świecie C # / .NET, kiedy przeniosłem klasy równoległe do podstrony Parallel, koliduje to z klasą pomocniczą System.Threading.Parallel, której często używam do uzyskania równoległości. Zamiast tego nie chciałem używać przestrzeni nazw „Współbieżne”, ponieważ jest to już używane do oznaczenia „równoczesnego dostępu” do klas kolekcji. Jako kompromis wybrałem podstronę „Parallelized”.
redcalx
2

To naprawdę interesujące pytanie, w którym obie zasadniczo przeciwne odpowiedzi są poprawne. Oto prawdziwy przykład tej dyskusji na temat mojego projektu.

Myślę, że do tej pory istnieją dwa bardzo istotne względy, które sprawiłyby, że chcesz iść w jednym lub drugim kierunku, czerpiąc z dwóch odpowiedzi z @Jorg i @Frank:

  1. Jeśli przewidujesz sytuację, w której więcej niż jedna z klas o tej samej nazwie może wymagać użycia w tym samym kontekście kodu, jest to powód, aby nie używać tej samej nazwy. Oznacza to, że jeśli musisz pracować, hr.Employeeale jednocześnie musisz spojrzeć na uprawnienia przez security.Employee, będzie to naprawdę denerwujące.
  2. I odwrotnie, jeśli wszystkie twoje klasy o tej samej nazwie mają takie same lub bardzo podobne interfejsy API, to dobry przykład, kiedy powinieneś używać tej samej nazwy w różnych przestrzeniach nazw. Przykładem Scala jest właśnie ten, w którym wszystkie Mapklasy implementują ten sam interfejs lub są wzajemnymi podklasami. W tym przypadku dwuznaczność lub „zamieszanie” można z pewnością nazwać dobrą rzeczą, ponieważ użytkownik słusznie może traktować wszystkie implementacje klasy lub interfejsu tak samo… co jest punktem interfejsów i podklas.
S'pht'Kr
źródło