Kilka miesięcy temu spod krzywej ręki mojej wydostał się post o “klasach i metodach częściowych” (“Partial classes & partial methods – explained”). Wniosek z niego można było wysnuć jeden: trzymaj się z dala od tych mechanizmów, jeśli nie generujesz kodu. Muszę jednak przyznać, że ostatnio natknąłem się na kolejny scenariusz, gdzie klasy częściowe są… przydatne.
Weźmy za przykład standardową aplikację www. Zdarza się, że strony przekazują pomiędzy sobą parametry w query stringu. W takich przypadkach wygodnie jest mieć jedno centralne miejsce, w którym przechowujemy wszystkie wykorzystywane klucze. Może ono wyglądać tak:
1: public static class QueryStringKeys 2: { 3: public const string UserId = "userId"; 4: public const string PostId = "postId"; 5: public const string SortDirection = "sorting"; 6: }
Łatwo domyślić się, że gdy liczba takich kluczy urośnie, to klasa stanie się bezsensownym zgromadziskiem niepowiązanych ze sobą napisów. Najsensowniej jest chyba podzielić klucze na logiczne grupy i zebrać w dedykowanych klasach: klucz reprezentujący id użytkownika i datę jego urodzenia wstawimy do Users, wszystko co związane z blogami: do Blogs itd (celowo nie używam określeń “moduł” czy “aggregate root”, żeby nie mieszać pojęć). Jak zatem nasz prosty przykład wyglądałby po takiej zmianie?:
1: public static class QueryStringKeys 2: { 3: public const string SortDirection = "sorting"; 4: 5: public static class Users 6: { 7: public const string UserId = "userId"; 8: public const string BirthDate = "birth"; 9: } 10: 11: public static class Blogs 12: { 13: public const string PostId = "postId"; 14: } 15: }
Okej, mamy to wszystko pogrupowane, ale… czytelność kodu wcale się nie poprawiła – wręcz odwrotnie! I tutaj do gry wchodzą partial classes… Najwygodniejszym sposobem, jaki udało mi się wymyślić, jest utworzenie głównej klasy QueryStringKeys zawierającej klucze wspólne dla wszystkich grup oraz ewentualnie metody pomocnicze. Każdą klasę zagnieżdżoną (wiem, łamię kolejną zasadę – “nie używaj typów zagnieżdżonych” – ale robię to świadomie i… tak jest bardzo wygodnie) wrzucamy do osobnego pliku:
[QueryStringKeys.cs]1: public static partial class QueryStringKeys 2: { 3: public const string Id = "id"; 4: public const string SortDirection = "sorting"; 5: 6: public static string Create(string key, string value) 7: { 8: return key + "=" + value; 9: } 10: }
1: public static partial class QueryStringKeys 2: { 3: public static class Users 4: { 5: public const string UserId = "userId"; 6: public const string BirthDate = "birth"; 7: } 8: }
1: public static partial class QueryStringKeys 2: { 3: public static class Blogs 4: { 5: public const string PostId = "postId"; 6: } 7: }
Mało tego – żeby nie zaśmiecać SolutionExplorer (eksploratora solucji?:) ) zastosujemy jeszcze jeden trick, o którym kiedyś dawno temu pisałem (“Zwijanie” plików w Visual Studio). W VS wygląda to tak:
Wpadł ktoś może na lepsze rozwiązanie tej kwestii?
A dlaczego na klasie stosujesz static? w tym momencie chyba sealed bedzie bardziej odpowiedni.
Ja osobiscie bym stworzyl tak:
plik A.cs (klasa A)
Folder QueryStrings
plik B.cs (dziedziczacy po A)
… itd
Gutek
Static – żeby podkreślić brak przechowywania jakiegokolwiek stanu tej klasy, czyli uniemożliwić tworzenie jej instancji.
Static też jest sealed.
Jeszcze nigdy nie natknąłem się na scenariusz, w którym użycie samego modyfikatora ‘sealed’ byłoby w pełni uzasadnione:). Kiedys ten temat był poruszany na forum CG: https://www.codeguru.pl/forum-posts-11533-3.aspx .
pomysl z hierarchicznymi staticami parukrotnie (raptem 2-3 razy) stosowalem wczesniej i sprawdza sie oczywiscie jak uzywamy intellisense (a kto nie stosuje?)
procent: (o uzyciu sealed) ja ze swoejgo doswiadczenia rozroznilbym 2 sytuacje: pisanie komponentow (czy kawalkow dla inych developerow, frameworkow itp) oraz pisanie wlasciwe aplikacji. W pierwszym przypadku rzeczywiscie gleboo trzba sie zastanowic nad uzyciem sealed. W pisaniu wlasciwym aplikacji sealed jest jak najbardziej wskazane – poprostu pisac sealed mamy pewne ograniczenia na mysli i jesli za jakis czas uzyjemy konstrukcji lamiacej sealed to kompilator nas o tym poinformuje :-) (z podobnych powodow uzywam jak najwiecej readonly na polach klasy :-) )
BTW: Nie uzycie sealed ma jedna powazna konsekwencje: trzeba poswiecic dodatkowy czas na przemyslenie sceariuszy wszelakiego dziedziczenia. Nie sealowanie jest trudniejsze ;-)
@%
ja robie tak:
public sealed class A{ private A(){} }
jezeli nie chce miec instancji klasy dozwolonej.
zas co do samego slowa sealed – jak najbardziej jest to potrzebne. jak tworze jakies API komus to nie chce niektorych rzeczy udostepniac z mozliwoscia dziedziczenia. i teraz mamy na przyklad klase typu PracownikEtatowy, ktory IMHO juz w danym X kontescie nie powinien byc dziedziczony. wiec dajemy sealed.
Osobiscie prawie w ogole nie uzywam statycznych klas – ale to juz jest kwestia dyskusji na temat obiektowego programowania.
Ostatnio moj znajomy sie mnie spytal:
jezeli masz Samochod (klasa) A (instancja), to czy Samochod powinien udostepnic metode zmien kolor (statyczna metoda) czy A (instancja). Czy osoba (klasa) powinna miec metode zmien plec czy instancja osoby?
ogolnie chodzi o to jaki jest sens pisania static methods…. :)
ale nie wazne ;)
brawo :) spadam na fajka ;)
Gutek
gutek:
To zalezy od stopnia skomplikowania zmien kolor. To zalezy czy chcesz wprowadzac nowe “pojecie” do domeny “zmieniacz koloru samochodu”/”budowacz samochodu”.
Wprowadzenie statycznej metody (elementu) bardzo czesto to obcinanie zakresu modelu domeny…
Z punktu wlasnie czysto akademickiego OO to faktycznie statyczne metody klasy nie sa za czesto widziane.
moge tylko napisac: “ale nie wazne ;)”