Hay una pequeña diferencia entre Java y C # que es relevante aquí. En Java, cada miembro es virtual por defecto. En C #, todos los miembros están sellados de forma predeterminada, excepto los miembros de la interfaz.
Las suposiciones que acompañan a esto influyen en la guía: en Java, todo tipo de público debe considerarse no definitivo, de acuerdo con el Principio de Sustitución de Liskov [1]. Si solo tienes una implementación, nombrarás la clase Parser
; Si descubre que necesita varias implementaciones, simplemente cambiará la clase a una interfaz con el mismo nombre y cambiará el nombre de la implementación concreta a algo descriptivo.
En C #, la suposición principal es que cuando obtienes una clase (el nombre no comienza con I
), esa es la clase que deseas. Tenga en cuenta que esto no es en absoluto un 100% exacto: un contraejemplo típico sería clases como Stream
(que realmente debería haber sido una interfaz, o un par de interfaces), y cada uno tiene sus propias directrices y antecedentes en otros idiomas. . También hay otras excepciones, como el sufijo Base
, ampliamente utilizado para denotar una clase abstracta: al igual que con una interfaz, sabes el tipo se supone que es polimórfico.
También hay una buena característica de usabilidad al dejar el nombre sin prefijo I para la funcionalidad que se relaciona con esa interfaz sin tener que recurrir a hacer de la interfaz una clase abstracta (lo que perjudicaría debido a la falta de herencia múltiple de clase en DO#). Esto fue popularizado por LINQ, que usa IEnumerable<T>
como interfaz, y Enumerable
como repositorio de métodos que se aplican a esa interfaz. Esto no es necesario en Java, donde las interfaces también pueden contener implementaciones de métodos.
En última instancia, el prefijo I
se usa ampliamente en el mundo C # y, por extensión, el mundo .NET (ya que la mayor parte del código .NET está escrito en C #, tiene sentido seguir las directrices de C # para la mayoría de las interfaces públicas). Esto significa que es casi seguro que trabajará con las bibliotecas y el código que sigue a esta notación, y tiene sentido adoptar la tradición para evitar confusiones innecesarias. No es como omitir el prefijo que mejorará su código :)
Supongo que el razonamiento del tío Bob fue algo como esto:
IBanana
es la noción abstracta de banano. Si puede haber alguna clase de implementación que no tenga un nombre mejor que Banana
, la abstracción no tiene ningún significado, y usted debe descartar la interfaz y solo usar una clase. Si hay un nombre mejor (por ejemplo, LongBanana
o AppleBanana
), no hay razón para no usar Banana
como el nombre de la interfaz. Por lo tanto, usar el prefijo I
significa que tiene una abstracción inútil, lo que hace que el código sea más difícil de entender sin ningún beneficio. Y como el OOP estricto tendrá que codificar contra las interfaces, el único lugar donde no vería el prefijo I
en un tipo sería en un constructor: un ruido bastante inútil.
Si aplica esto a su interfaz IParser
de muestra, puede ver claramente que la abstracción está completamente en el territorio "sin significado". O hay algo específico acerca de una implementación concreta de un analizador (por ejemplo, JsonParser
, XmlParser
, ...), o simplemente debes usar una clase. No existe tal cosa como "implementación predeterminada" (aunque en algunos entornos, esto sí tiene sentido, especialmente COM, ya sea una implementación específica o desea una clase abstracta o métodos de extensión para los "valores predeterminados". Sin embargo, en C #, a menos que su base de código ya omita el prefijo I
, consérvelo. Solo haga una nota mental cada vez que vea un código como class Something: ISomething
; significa que alguien no es muy bueno en seguir a YAGNI y construir abstracciones razonables.
[1] - Técnicamente, esto no se menciona específicamente en el artículo de Liskov, pero es uno de los cimientos del documento original de OOP y en mi lectura de Liskov, ella no desafió esto. En una interpretación menos estricta (la que se toma en la mayoría de los lenguajes OOP), esto significa que cualquier código que use un tipo público destinado a la sustitución (es decir, no final / sellado) debe funcionar con cualquier implementación conforme de ese tipo.