Jest jedna drobna różnica między Javą a C #, która jest tutaj istotna. W Javie domyślnie każdy członek jest wirtualny. W języku C # każdy element jest domyślnie zaplombowany - z wyjątkiem elementów interfejsu.
Założenia, które się z tym wiążą, mają wpływ na wytyczną - w Javie każdy typ publiczny powinien być uważany za niekończący, zgodnie z zasadą substytucji Liskowa [1]. Jeśli masz tylko jedną implementację, nazwiesz klasę Parser
; jeśli uznasz, że potrzebujesz wielu implementacji, po prostu zmienisz klasę na interfejs o tej samej nazwie i zmienisz nazwę konkretnej implementacji na coś opisowego.
W języku C # głównym założeniem jest to, że kiedy dostajesz klasę (nazwa nie zaczyna się od I
), to jest klasa, którą chcesz. Pamiętaj, że nie jest to w przybliżeniu 100% dokładne - typowym kontrprzykładem byłyby takie klasy Stream
(które naprawdę powinien byłby być interfejsem lub kilkoma interfejsami), a każdy ma swoje własne wytyczne i pochodzenie z innych języków. Istnieją również inne wyjątki, takie jak dość powszechnie stosowany Base
przyrostek oznaczający klasę abstrakcyjną - podobnie jak w przypadku interfejsu, wiesz, że typ powinien być polimorficzny.
Istnieje również przyjemna funkcja użyteczności polegająca na pozostawieniu nazwy bez prefiksu I dla funkcjonalności związanej z tym interfejsem bez konieczności uciekania się do tworzenia interfejsu jako klasy abstrakcyjnej (co zaszkodziłoby z powodu braku wielokrotnego dziedziczenia klas w języku C #). Zostało to spopularyzowane przez LINQ, który wykorzystuje IEnumerable<T>
jako interfejs i Enumerable
jako repozytorium metod mających zastosowanie do tego interfejsu. Nie jest to konieczne w Javie, gdzie interfejsy mogą również zawierać implementacje metod.
Ostatecznie I
prefiks jest szeroko stosowany w świecie C #, a przez to w świecie .NET (ponieważ zdecydowanie większość kodu .NET jest napisana w języku C #, warto stosować się do wytycznych C # dla większości publicznych interfejsów). Oznacza to, że prawie na pewno będziesz pracować z bibliotekami i kodem, który jest zgodny z tą notacją, i warto przyjąć tradycję, aby zapobiec niepotrzebnym nieporozumieniom - to nie tak, że pominięcie prefiksu poprawi kod :)
Zakładam, że rozumowanie wuja Boba było takie:
IBanana
jest abstrakcyjnym pojęciem banana. Jeśli może istnieć jakaś klasa implementująca, która nie miałaby lepszej nazwy niż Banana
, abstrakcja jest całkowicie pozbawiona znaczenia i powinieneś porzucić interfejs i po prostu użyć klasy. Jeśli istnieje lepsza nazwa (powiedzmy LongBanana
lub AppleBanana
), nie ma powodu, aby nie używać jej Banana
jako nazwy interfejsu. Dlatego użycie I
przedrostka oznacza, że masz niepotrzebną abstrakcję, co sprawia, że kod jest trudniejszy do zrozumienia bez żadnych korzyści. A ponieważ ścisłe OOP sprawi, że zawsze będziesz kodować przeciwko interfejsom, jedynym miejscem, gdzie nie zobaczysz I
prefiksu na typie, będzie konstruktor - dość bezsensowny hałas.
Jeśli zastosujesz to do przykładowego IParser
interfejsu, możesz wyraźnie zobaczyć, że abstrakcja znajduje się całkowicie na terytorium „bez znaczenia”. Albo jest coś specyficznego o konkretnej implementacji parsera (np JsonParser
, XmlParser
...), czy też po prostu użyć klasy. Nie ma czegoś takiego jak „domyślna implementacja” (chociaż w niektórych środowiskach rzeczywiście ma to sens - zwłaszcza COM), albo istnieje konkretna implementacja, albo potrzebujesz abstrakcyjnej metody klasy lub rozszerzenia dla „domyślnych”. Jednak w języku C #, chyba że twoja baza kodu już pomija I
prefiks -f, zachowaj go. Po prostu zanotuj za każdym razem, gdy zobaczysz kod podobny class Something: ISomething
- oznacza to, że ktoś nie jest zbyt dobry w przestrzeganiu YAGNI i tworzeniu rozsądnych abstrakcji.
[1] - Technicznie rzecz biorąc, nie jest to specjalnie wspomniane w pracy Liskova, ale jest to jedna z podstaw oryginalnej pracy OOP i podczas mojego czytania Liskova nie zakwestionowała tego. W mniej ścisłej interpretacji (tej przyjętej przez większość języków OOP) oznacza to, że każdy kod używający typu publicznego, który jest przeznaczony do podstawienia (tj. Nie-końcowy / zapieczętowany) musi działać z dowolną zgodną implementacją tego typu.