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 ne derivano influenzano le linee guida: in Java, ogni tipo pubblico dovrebbe essere considerato non definitivo, in conformità con il principio di sostituzione di Liskov [1]. Se hai una sola implementazione, nominerai la classe Parser
; se trovi che hai bisogno di più implementazioni, cambierai la classe in un'interfaccia con lo stesso nome e rinominerai l'implementazione concreta in qualcosa di descrittivo.
In C #, il presupposto principale è che quando ottieni una classe (il nome non inizia con I
), quella è la classe che desideri. Intendiamoci, questo non è assolutamente accurato al 100%: un tipico esempio di esempio sarebbe rappresentato da classi come Stream
(che in realtà avrebbero dovuto essere un'interfaccia o un paio di interfacce) e ognuno ha le proprie linee guida e sfondi da altre lingue. Ci sono anche altre eccezioni come il Base
suffisso abbastanza ampiamente usato per indicare una classe astratta - proprio come con un'interfaccia, sai che il tipo dovrebbe essere polimorfico.
C'è anche una buona funzionalità di usabilità nel lasciare il nome senza I-prefisso per funzionalità che si riferisce a quell'interfaccia senza dover ricorrere a rendere l'interfaccia una classe astratta (che danneggerebbe 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. Ciò non è necessario in Java, dove anche le interfacce possono contenere implementazioni di metodi.
In definitiva, il I
prefisso è ampiamente usato nel mondo C # e, per estensione, nel mondo .NET (poiché gran parte del codice .NET è scritto in C #, ha senso seguire le linee guida C # per la maggior parte delle interfacce pubbliche). Ciò significa che quasi sicuramente lavorerai con librerie e codice che seguono questa notazione, e ha senso adottare la tradizione per prevenire inutili confusioni - non è come omettere il prefisso renderà il tuo codice migliore :)
Suppongo che il ragionamento di zio Bob fosse qualcosa del genere:
IBanana
è la nozione astratta di banana. Se può esserci una classe di implementazione che non avrebbe un nome migliore di Banana
, l'astrazione è del tutto priva di significato, e dovresti abbandonare l'interfaccia e usare solo una classe. Se esiste un nome migliore (diciamo, LongBanana
o AppleBanana
), non c'è motivo di non usarlo Banana
come nome dell'interfaccia. Pertanto, l'utilizzo del I
prefisso significa che hai un'astrazione inutile, che rende il codice più difficile da capire senza alcun vantaggio. E dal momento che OOP rigoroso ti farà sempre programmare contro le interfacce, l'unico posto in cui non vedresti il I
prefisso su un tipo sarebbe su un costruttore - rumore abbastanza inutile.
Se lo applichi all'interfaccia di esempio IParser
, puoi vedere chiaramente che l'astrazione è interamente nel territorio "insignificante". O c'è qualcosa di specifico su un'implementazione concreta di un parser (ad es JsonParser
. XmlParser
, ...) o dovresti semplicemente usare una classe. Non esiste qualcosa come "implementazione predefinita" (anche se in alcuni ambienti, questo ha davvero senso, in particolare COM), o c'è un'implementazione specifica o si desidera una classe astratta o metodi di estensione per le "impostazioni predefinite". Tuttavia, in C #, a meno che il tuo codebase non ometta già il I
-prefix, tienilo. Prendi solo una nota mentale ogni volta che vedi un codice simile class Something: ISomething
- significa che qualcuno non è molto bravo a seguire YAGNI e costruire astrazioni ragionevoli.
[1] - Tecnicamente, questo non è specificamente menzionato nel documento di Liskov, ma è uno dei fondamenti del documento originale OOP e nella mia lettura di Liskov, non ha contestato questo. In un'interpretazione meno rigorosa (quella adottata dalla maggior parte delle lingue OOP), ciò significa che qualsiasi codice che utilizza un tipo pubblico destinato alla sostituzione (ovvero non definitivo / sigillato) deve funzionare con qualsiasi implementazione conforme di quel tipo.