Pracuję w projekcie, który dotyczy urządzeń fizycznych i byłem zdezorientowany, jak prawidłowo nazwać niektóre klasy w tym projekcie.
Biorąc pod uwagę fakt, że rzeczywiste urządzenia (czujniki i odbiorniki) to jedno, a ich reprezentacja w oprogramowaniu jest inna, myślę o nadaniu nazw niektórym klasom za pomocą wzorca nazwy sufiksu „Info”.
Na przykład, podczas gdy a Sensor
byłby klasą reprezentującą rzeczywisty czujnik (gdy jest on faktycznie podłączony do jakiegoś działającego urządzenia), SensorInfo
byłby użyty do przedstawienia jedynie cech takiego czujnika. Na przykład, po zapisaniu pliku, serializowałbym a SensorInfo
do nagłówka pliku, zamiast serializować a Sensor
, co nie miałoby nawet sensu.
Ale teraz jestem zdezorientowany, ponieważ w cyklu życia obiektów istnieje pole środkowe, w którym nie mogę zdecydować, czy mam użyć jednego lub drugiego, jak uzyskać jeden od drugiego, a nawet czy oba warianty powinny być rzeczywiście zwinięte tylko do jednej klasy.
Ponadto, zbyt powszechna przykładowa Employee
klasa oczywiście jest po prostu reprezentacją prawdziwej osoby, ale EmployeeInfo
o ile wiem , nikt nie sugerowałby, aby nazwać klasę .
Językiem, w którym pracuję, jest .NET, a ten schemat nazewnictwa wydaje się być powszechny w całym frameworku, na przykład w przypadku tych klas:
Directory
iDirectoryInfo
klasy;File
iFileInfo
klasy;ConnectionInfo
klasa (bez odpowiedniejConnection
klasy);DeviceInfo
klasa (bez odpowiedniejDevice
klasy);
Więc moje pytanie brzmi: czy istnieje powszechne uzasadnienie używania tego wzorca nazewnictwa? Czy istnieją przypadki, w których sensowne jest posiadanie par nazw ( Thing
i ThingInfo
) oraz inne przypadki, w których powinna istnieć tylko ThingInfo
klasa lub Thing
klasa, bez jej odpowiednika?
źródło
Info
sufiks odróżniastatic
klasy zawierającej metody narzędzia z jego pełnostanową odpowiednik. Nie jest to „najlepsza praktyka” jako taka; to tylko sposób, w jaki zespół .NET wymyślił rozwiązanie konkretnego problemu. Mogli tak łatwo wymyślićFileUtility
iFile
, aleFile.DoSomething()
iFileInfo.FileName
wydają się czytać lepiej.Foo
, możesz mieć klasę użyteczności, której nie da się utworzyćFoos
. Jeśli chodzi o nazewnictwo, ważna jest spójność w interfejsie API, a najlepiej w interfejsach API na platformie.Employee
dziesiątki znajdują przykłady w Internecie lub w klasycznych książkach, których jeszcze nie widziałemEmployeeInfo
(być może dlatego, że pracownik jest żywą istotą, a nie konstrukcja techniczna, taka jak połączenie lub plik).EmployeeInfo
Zgadzam się jednak, że jeśli klasa miałaby zostać zaproponowana w projekcie, wierzę, że mogłaby się przydać.Odpowiedzi:
Myślę, że „informacje” to mylne określenie. Obiekty mają stan i akcje: „informacje” to po prostu inna nazwa „stanu”, który jest już zapisany w OOP.
Co naprawdę próbujesz tutaj wymodelować? Potrzebujesz obiektu reprezentującego sprzęt w oprogramowaniu, aby inny kod mógł z niego korzystać.
Łatwo to powiedzieć, ale jak się dowiedziałeś, jest w tym coś więcej. „Sprzęt reprezentujący” jest zaskakująco szeroki. Obiekt, który to robi, ma kilka obaw:
Niektóre urządzenia, takie jak czujniki, będą miały mniej problemów niż, powiedzmy, urządzenie wielofunkcyjne drukarka / skaner / faks. Czujnik prawdopodobnie po prostu wytwarza strumień bitów, podczas gdy złożone urządzenie może mieć złożone protokoły i interakcje.
Wracając do konkretnego pytania, istnieje kilka sposobów, aby to zrobić, w zależności od konkretnych wymagań, a także złożoności interakcji sprzętowej.
Oto przykład, w jaki sposób zaprojektowałbym hierarchię klas dla czujnika temperatury:
ITemperatureSource: interfejs, który reprezentuje wszystko, co może generować dane dotyczące temperatury: czujnik, może nawet być opakowaniem pliku lub danymi zakodowanymi na stałe (pomyśl: testy próbne).
Acme4680Sensor: czujnik ACME model 4680 (doskonały do wykrywania, gdy Roadrunner jest w pobliżu). Może to implementować wiele interfejsów: być może ten czujnik wykrywa zarówno temperaturę, jak i wilgotność. Ten obiekt zawiera stan na poziomie programu, taki jak „czy czujnik jest podłączony?” i „jakie było ostatnie czytanie?”
Acme4680SensorComm: służy wyłącznie do komunikacji z urządzeniem fizycznym. Nie utrzymuje stanu. Służy do wysyłania i odbierania wiadomości. Ma metodę C # dla każdego komunikatu rozumianego przez sprzęt.
HardwareManager: służy do pobierania urządzeń. Jest to zasadniczo fabryka buforująca instancje: dla każdego urządzenia sprzętowego powinna istnieć tylko jedna instancja obiektu urządzenia. Musi być wystarczająco inteligentny, aby wiedzieć, że jeśli wątek A zażąda czujnika temperatury ACME, a wątek B zażąda czujnika wilgotności ACME, są one w rzeczywistości tym samym obiektem i powinny zostać zwrócone do obu wątków.
Na najwyższym poziomie będziesz mieć interfejsy dla każdego typu sprzętu. Opisują działania, które Twój kod C # podejmowałby na urządzeniach, używając typów danych C # (nie np. Tablice bajtów, których mógłby użyć surowy sterownik urządzenia).
Na tym samym poziomie masz klasę wyliczenia z jedną instancją dla każdego typu sprzętu. Czujnik temperatury może być jednym typem, czujnik wilgotności innym.
Jeden poziom poniżej to rzeczywiste klasy, które implementują te interfejsy: reprezentują one jedno urządzenie podobne do Acme4680Sensor, który opisałem powyżej. Każda konkretna klasa może implementować wiele interfejsów, jeśli urządzenie może wykonywać wiele funkcji.
Każda klasa urządzeń ma swoją prywatną klasę Comm (komunikacyjną), która obsługuje niskopoziomowe zadanie mówienia do sprzętu.
Poza modułem sprzętowym jedyną widoczną warstwą są interfejsy / wyliczanie oraz HardwareManager. Klasa HardwareManager to abstrakcja fabryczna, która obsługuje tworzenie instancji klas urządzeń, buforowanie instancji ( naprawdę nie chcesz, aby dwie klasy urządzeń rozmawiały z tym samym urządzeniem sprzętowym) itp. Klasa, która potrzebuje określonego typu czujnika, prosi HardwareManager o pobranie urządzenie dla konkretnego wyliczenia, które następnie sprawdza, czy jest już utworzone, jeśli nie, jak je utworzyć i zainicjować itp.
Celem jest oddzielenie logiki biznesowej od logiki sprzętowej niskiego poziomu. Kiedy piszesz kod, który drukuje dane czujnika na ekranie, kod ten nie powinien dbać o to, jaki typ czujnika masz , i tylko wtedy, gdy to oddzielenie jest na miejscu, co koncentruje się na tych interfejsach sprzętowych.
Uwaga: istnieją powiązania między HardwareManager a każdą klasą urządzeń, której nie narysowałem, ponieważ diagram zmieniłby się w zupę strzałkową.
źródło
To może być trochę trudne do znalezienia Jednolita konwencja jednoczącej się tutaj, ponieważ klasy te są rozłożone na wiele nazw, (
ConnectionInfo
wydaje się byćCrystalDecisions
, aDeviceInfo
wSystem.Reporting.WebForms
).Patrząc na te przykłady, wydaje się, że istnieją dwa różne zastosowania sufiksu:
Odróżnianie klasy zapewniającej metody statyczne za pomocą klasy zapewniającej metody instancji. Jest tak w przypadku
System.IO
klas, co podkreślono w ich opisach:Katalog :
DirectoryInfo :
Info
wydaje się tutaj nieco dziwnym wyborem, ale czyni względnie wyraźną różnicę:Directory
klasa może w uzasadniony sposób albo reprezentować konkretny katalog, albo zapewniać ogólne metody pomocnicze związane z katalogiem bez utrzymywania żadnego stanu, podczas gdyDirectoryInfo
naprawdę może być tylko tym pierwszym.Podkreślając, że klasa zawiera tylko informacje i nie zapewnia zachowania, którego można zasadnie oczekiwać po nazwie bez przyrostka .
Myślę, że ostatnia część tego zdania może być fragmentem układanki, która odróżnia, powiedzmy,
ConnectionInfo
odEmployeeInfo
. Gdybym miał wywoływaną klasęConnection
, słusznie oczekiwałbym, że zapewni mi funkcjonalność, jaką ma połączenie - szukałbym metod takich jakvoid Open()
itp. Jednak nikt przy zdrowych zmysłach nie spodziewałby się, żeEmployee
klasa faktycznie rób to, coEmployee
robi prawdziwy , lub szukaj metod takich jakvoid DoPaperwork()
lubbool TryDiscreetlyBrowseFacebook()
.źródło
Ogólnie rzecz biorąc,
Info
obiekt kapsułkuje informacje o stanie obiektu w pewnym momencie . Jeśli poproszę system, aby spojrzał na plik i podał miFileInfo
obiekt powiązany z jego rozmiarem, oczekiwałbym, że ten obiekt zgłosi rozmiar pliku w momencie wysłania żądania (lub ściślej mówiąc, rozmiar plik w pewnym momencie między nawiązaniem połączenia a jego zwrotem). Jeśli rozmiar pliku zmienia się między momentem powrotu żądania a momentemFileInfo
sprawdzenia obiektu, nie spodziewałbym się, że taka zmiana zostanie odzwierciedlona wFileInfo
obiekcie.Zauważ, że to zachowanie byłoby bardzo różne od zachowania
File
obiektu. Jeśli żądanie otwarcia pliku dyskowego w trybie niewyłącznym zwróciFile
obiekt, który maSize
właściwość, oczekiwałbym, że wartość zwrócona w ten sposób zmieni się, gdy zmieni się rozmiar pliku dyskowego, ponieważFile
obiekt nie reprezentuje jedynie stanu plik - reprezentuje sam plik .W wielu przypadkach obiekty dołączane do zasobu muszą zostać wyczyszczone, gdy ich usługi nie są już potrzebne. Ponieważ
*Info
obiekty nie dołączają się do zasobów, nie wymagają czyszczenia. W konsekwencji, w przypadkach, gdyInfo
obiekt spełni wymagania klienta, może być lepiej, aby kod używał jednego, niż użyć obiektu, który reprezentowałby zasób bazowy, ale którego połączenie z tym zasobem musiałoby zostać wyczyszczone.źródło
File
klasa nie może być instancja iFileInfo
obiekty zrobić aktualizację z bazowego systemu plików.FileInfo
po prostu przechowuję statyczne informacje. Czyż nie Ponadto nigdy nie miałem okazji otwierać plików w trybie niewyłącznym, ale powinno to być możliwe i oczekiwałbym, że istnieje metoda, która zgłosi bieżący rozmiar pliku, nawet jeśli obiekt użyty do otwarcia nie jest nazywaneFile
(zwykle po prostu użyćReadAllBytes
,WriteAllBytes
,ReadAllText
,WriteAllText
, itd.).Nie podoba mi się to rozróżnienie. Wszystkie obiekty są „reprezentacjami w oprogramowaniu”. To właśnie oznacza słowo „przedmiot”.
Teraz sensowne może być oddzielenie informacji o urządzeniu peryferyjnym od rzeczywistego kodu, który łączy się z urządzeniem peryferyjnym. Na przykład Sensor ma SensorInfo, który zawiera większość zmiennych instancji, wraz z niektórymi metodami, które nie wymagają sprzętu, podczas gdy klasa Sensor jest odpowiedzialna za faktyczną interakcję z czujnikiem fizycznym. Nie masz,
Sensor
chyba że komputer ma czujnik, ale prawdopodobnie maszSensorInfo
.Problem polega na tym, że tego rodzaju projekty można uogólnić na (prawie) dowolne klasy. Musisz więc uważać. Na przykład oczywiście nie miałbyś
SensorInfoInfo
klasy. A jeśli maszSensor
zmienną, możesz naruszać prawo Demeter, wchodząc w interakcje z jegoSensorInfo
członkiem. To oczywiście nie jest fatalne, ale projektowanie API nie jest przeznaczone tylko dla autorów bibliotek. Jeśli utrzymasz własny interfejs API w czystości i prosty, kod będzie łatwiejszy w utrzymaniu.Zasoby systemu plików, takie jak katalogi, moim zdaniem są bardzo zbliżone do tej krawędzi. Są sytuacje, w których chcesz opisać katalog, który nie jest lokalnie dostępny, to prawda, ale przeciętny programista prawdopodobnie nie znajduje się w żadnej z tych sytuacji. Komplikowanie struktury klas w ten sposób jest moim zdaniem nieprzydatne. Porównaj podejście Pythona w
pathlib
: Istnieje jedna klasa, która „najprawdopodobniej jest potrzebna” oraz różne klasy pomocnicze, które większość programistów może bezpiecznie zignorować. Jeśli jednak naprawdę ich potrzebujesz, zapewniają one w dużej mierze ten sam interfejs, z pominięciem konkretnych metod.źródło
SensorInfo
byłby to rodzaj DTO i / lub też jak „migawka”, a nawet „specyfikacja”, reprezentująca tylko część danych / stanów rzeczywistegoSensor
obiektu że „może nawet tam nie być”.PureWindowsPath
działa trochę jak obiekt Info, ale ma metody wykonywania rzeczy, które nie wymagają systemu Windows (np. Przejęcie podkatalogu, podzielenie rozszerzenia pliku itp.). Jest to bardziej pomocne niż tylko dostarczenie gloryfikowanej struktury.Powiedziałbym, że kontekst / domena ma znaczenie, ponieważ mamy wysoki kod logiki biznesowej i modele niskiego poziomu, komponenty architektury i tak dalej ...
„Informacje”, „Dane”, „Menedżer”, „Obiekt”, „Klasa”, „Model”, „Kontroler” itp. Mogą być śmierdzącymi przyrostkami, szczególnie na niższym poziomie, ponieważ każdy obiekt ma pewne informacje lub dane, więc ta informacja nie jest konieczna.
Nazwy klas domeny biznesowej powinny wyglądać tak, jak mówią o tym wszyscy interesariusze, bez względu na to, czy brzmi to dziwnie, czy nie jest w 100% poprawnym językiem.
Dobre przyrostki dla struktur danych to np. „Lista”, „Mapa” i wzorce podpowiedzi „Dekorator”, „Adapter”, jeśli uważasz, że jest to konieczne.
W twoim scenariuszu z czujnikami nie spodziewałbym
SensorInfo
się, że zapiszę twój czujnik, aleSensorSpec
.Info
Imho jest bardziej pochodną informacją, podobnieFileInfo
jak rozmiar, którego nie zapisujesz lub ścieżka pliku zbudowana ze ścieżki i nazwy pliku itp.Kolejny punkt:
To zawsze przypomina mi myślenie o nazwisku przez kilka sekund, a jeśli go nie znalazłem, używam dziwnych imion i oznaczam je „DO ZROBIENIA”. Zawsze mogę to zmienić później, ponieważ moje IDE zapewnia obsługę refaktoryzacji. To nie jest slogan firmowy, który musi być dobry, ale tylko trochę kodu, który możemy zmieniać za każdym razem, kiedy chcemy. Miej to w pamięci.
źródło
ThingInfo może służyć jako świetny serwer proxy tylko do odczytu dla rzeczy.
patrz http://www.dofactory.com/net/proxy-design-pattern
Proxy: „Podaj surogat lub symbol zastępczy dla innego obiektu, aby kontrolować dostęp do niego”.
Zwykle ThingInfo ma właściwości publiczne bez ustawień. Z tych klas i metod w klasie można bezpiecznie korzystać i nie będą one wprowadzać żadnych zmian w danych kopii zapasowej, obiekcie ani innych obiektach. Nie wystąpią żadne zmiany stanu ani inne skutki uboczne. Mogą być używane do raportowania i usług internetowych lub w dowolnym miejscu, w którym potrzebujesz informacji o obiekcie, ale chcesz ograniczyć dostęp do samego obiektu.
Używaj ThingInfo, gdy tylko jest to możliwe, i ogranicz użycie rzeczywistej Rzeczy do czasów, w których faktycznie potrzebujesz zmienić obiekt Thing. Dzięki temu czytanie i debugowanie jest znacznie szybsze, gdy przyzwyczaisz się do korzystania z tego wzorca.
źródło
Receiver
który odbiera strumienie danych z wieluSensor
s. Chodzi o to, że odbiornik powinien wyodrębnić rzeczywiste czujniki. Problem polega jednak na tym, że potrzebuję informacji o czujniku, aby móc zapisać je w nagłówku pliku. Rozwiązanie: każdyIReceiver
będzie miał listęSensorInfo
. Jeśli wyślę do odbiornika polecenia, które sugerują zmianę stanu czujnika, zmiany te zostaną odzwierciedlone (przez getter) na odpowiednimSensorInfo
.List<SensorInfo>
właściwość tylko do odczytu.Jak dotąd nikt w tym pytaniu nie zdawał sobie sprawy z prawdziwego powodu tej konwencji nazewnictwa.
DirectoryInfo
Nie jest katalogiem. Jest DTO z danymi na temat katalogu. Może istnieć wiele takich przypadków opisujących ten sam katalog. To nie jest byt. Jest to obiekt o wartości wyrzucanej. A nie reprezentuje rzeczywistego katalogu. Możesz również myśleć o tym jak o uchwycie lub kontrolerze katalogu.DirectoryInfo
Porównaj to z klasą o nazwie
Employee
. Może to być obiekt jednostki ORM i jest to pojedynczy obiekt opisujący tego pracownika. Jeśli był to obiekt wartości bez tożsamości, należy go wywołaćEmployeeInfo
.Employee
Rzeczywiście nie reprezentuje rzeczywistą pracownika. Wywołana klasa DTO podobna do wartościEmployeeInfo
wyraźnie nie reprezentowałaby pracownika, ale raczej opisywałaby go lub przechowywała dane na jego temat.W BCL istnieje przykład, w którym istnieją obie klasy: A
ServiceController
to klasa opisująca usługę Windows. Dla każdej usługi może istnieć dowolna liczba takich kontrolerów. AServiceBase
(lub klasa pochodna) jest faktyczną usługą i koncepcyjnie nie ma sensu mieć wielu jej instancji dla każdej odrębnej usługi.źródło
Proxy
wzorca wspomnianego przez @Shmoken, prawda?EmployeeInfo
z usługi internetowej. To nie jest proxy. Kolejny przykład: AnAddressInfo
nawet nie ma rzeczy, którą mógłby proxy, ponieważ adres nie jest bytem. Jest to samodzielna wartość.