C'è una piccola differenza tra Java e C # che è rilevante qui. In Java, ogni membro è virtuale per impostazione predefinita. In C #, ogni membro è sigillato per impostazione predefinita, ad eccezione dei membri dell'interfaccia.
Le ipotesi che vanno con questo influenzano la linea guida - in Java, ogni tipo pubblico dovrebbe essere considerato non-finale, in accordo con il Principio di sostituzione di Liskov [1]. Se hai solo un'implementazione, chiamerai la classe Parser
; se trovi che hai bisogno di più implementazioni, cambi semplicemente la classe in un'interfaccia con lo stesso nome e rinomina l'implementazione concreta in qualcosa di descrittivo.
In C #, l'ipotesi principale è che quando si ottiene una classe (il nome non inizia con I
), quella è la classe che si desidera. Intendiamoci, questo non è affatto accurato al 100% - un tipico contro-esempio potrebbe essere classi come Stream
(che avrebbe dovuto essere un'interfaccia, o un paio di interfacce), e ognuno ha le proprie linee guida e sfondi da altre lingue . Esistono anche altre eccezioni come il suffisso Base
, largamente usato, per denotare una classe astratta, proprio come con un'interfaccia, sapere si suppone che il tipo sia polimorfico.
C'è anche una buona funzionalità di usabilità nel lasciare il nome non prefissato per funzionalità che si riferiscono a quell'interfaccia senza dover ricorrere a rendere l'interfaccia una classe astratta (che farebbe male a causa della mancanza di ereditarietà multipla di classe in C #). Questo è stato reso popolare da LINQ, che utilizza IEnumerable<T>
come interfaccia e Enumerable
come repository di metodi che si applicano a tale interfaccia. Questo non è necessario in Java, dove le interfacce possono contenere anche implementazioni di metodi.
In definitiva, il prefisso I
è ampiamente usato nel mondo C # e, per estensione, nel mondo .NET (dato che gran parte del codice .NET è scritto in C #, ha senso seguire le linee guida C # per la maggior parte di le interfacce pubbliche). Questo significa che quasi certamente lavorerai con le librerie e il codice che segue questa notazione, e ha senso adottare la tradizione per evitare inutili confusioni - non è come omettere il prefisso renderà il tuo codice migliore:)
Suppongo che il ragionamento dello zio Bob fosse qualcosa del genere:
IBanana
è la nozione astratta di banana. Se può esistere una classe di implementazione che non avrebbe un nome migliore di Banana
, l'astrazione è completamente priva di significato e dovresti rilasciare l'interfaccia e usare solo una classe. Se c'è un nome migliore (ad esempio, LongBanana
o AppleBanana
), non c'è motivo per non utilizzare Banana
come nome dell'interfaccia. Pertanto, l'uso del prefisso I
significa che si dispone di un'astrazione inutile, che rende il codice più difficile da comprendere senza alcun beneficio. E dal momento che l'OOP rigoroso avrà sempre il codice contro le interfacce, l'unico posto dove non vedresti il prefisso I
su un tipo sarebbe su un costruttore - un rumore abbastanza inutile.
Se lo applichi all'interfaccia IParser
di esempio, puoi vedere chiaramente che l'astrazione è interamente nel territorio "privo di significato". O c'è qualcosa di specifico su un'implementazione concreta di un parser (ad esempio JsonParser
, XmlParser
, ...), oppure dovresti semplicemente usare una classe. Non esiste una cosa come "implementazione di default" (sebbene in alcuni ambienti, questo ha davvero senso - in particolare, COM), o c'è un'implementazione specifica, oppure vuoi una classe astratta o metodi di estensione per i "valori predefiniti". Tuttavia, in C #, a meno che il tuo codebase non stia già omettendo I
-prefix, tienilo. Basta prendere nota mentalmente ogni volta che vedi codice come class Something: ISomething
- significa che qualcuno non è molto bravo a seguire YAGNI e costruire astrazioni ragionevoli.
[1] - Tecnicamente, questo non è specificamente menzionato nel lavoro di Liskov, ma è uno dei fondamenti dell'originale documento OOP e nella mia lettura di Liskov, lei non ha contestato questo. In un'interpretazione meno restrittiva (quella presa dalla maggior parte delle lingue OOP), ciò significa che qualsiasi codice che utilizza un tipo pubblico che è destinato alla sostituzione (cioè non finale / sigillato) deve funzionare con qualsiasi implementazione conforme di quel tipo.