Muy claro. Estoy implementando una interfaz, pero hay una propiedad que es innecesaria para esta clase y, de hecho, no debería usarse. Mi idea inicial era hacer algo como:
int IFoo.Bar
{
get { raise new NotImplementedException(); }
}
Supongo que no hay nada de malo en esto, per se, pero no se siente "bien". ¿Alguien más se ha encontrado con una situación similar antes? Si es así, ¿cómo lo enfocaste?
c#
design
interfaces
implementations
Chris Pratt
fuente
fuente
Respuestas:
Este es un ejemplo clásico de cómo las personas deciden violar el Principio de Subtitulación de Liskov. Lo desaconsejo encarecidamente, pero recomendaría posiblemente una solución diferente:
Si el primero es el caso para usted, simplemente no implemente la interfaz en esa clase. Piense en ello como un enchufe eléctrico donde el orificio de conexión a tierra es innecesario, por lo que en realidad no se conecta a tierra. ¡No conectas nada con tierra y no es gran cosa! Pero tan pronto como use algo que necesite un terreno, podría encontrarse con un fracaso espectacular. Es mejor no perforar un agujero falso en el suelo. Entonces, si su clase no hace realmente lo que pretende la interfaz, no implemente la interfaz.
Aquí hay algunos bits rápidos de wikipedia:
El Principio de sustitución de Liskov puede formularse simplemente como "No fortalezca las precondiciones y no debilite las postcondiciones".
Para la interoperabilidad semántica y la sustituibilidad entre diferentes implementaciones de los mismos contratos, necesita que todos se comprometan con los mismos comportamientos.
El principio de segregación de interfaz habla de la idea de que las interfaces deben separarse en conjuntos cohesivos, de modo que no requiera una interfaz que haga muchas cosas diferentes cuando solo desea una instalación. Piense nuevamente en la interfaz de una toma de corriente, también podría tener un termostato, pero dificultaría la instalación de una toma de corriente y podría hacer que sea más difícil de usar para fines que no sean de calefacción. Al igual que una toma de corriente con un termostato, las interfaces grandes son difíciles de implementar y difíciles de usar.
fuente
Me parece bien, si esta es tu situación.
Sin embargo, me parece que su interfaz (o uso de la misma) se rompe si una clase derivada no implementa realmente todo. Considere dividir esa interfaz.
Descargo de responsabilidad: esto requiere una herencia múltiple para funcionar correctamente, y no tengo idea de si C # lo admite.fuente
Me he encontrado con esta situación. De hecho, como se señaló en otra parte, el BCL tiene tales instancias ... Trataré de proporcionar mejores ejemplos y proporcionar algunas razones:
Cuando tiene una interfaz ya enviada que mantiene por razones de compatibilidad y ...
La interfaz contiene miembros que están obsoletos o están descartados. Por ejemplo
BlockingCollection<T>.ICollection.SyncRoot
(entre otros) mientrasICollection.SyncRoot
no es obsoleto per se, arrojaráNotSupportedException
.La interfaz contiene miembros que están documentados como opcionales y que la implementación puede generar la excepción. Por ejemplo, en MSDN al respecto
IEnumerator.Reset
dice:Por un error en el diseño de la interfaz, debería haber sido más de una interfaz en primer lugar. Es un patrón común en BCL implementar versiones de solo lectura de contenedores con
NotSupportedException
. Lo he hecho yo mismo, es lo que se espera ahora ...ICollection<T>.IsReadOnly
Regresotrue
para que puedas decirles una parte. El diseño correcto habría sido tener una versión legible de la interfaz, y luego la interfaz completa hereda de eso.No hay mejor interfaz para usar. Por ejemplo, tengo una clase que le permite acceder a los elementos por índice, verificar si contiene un elemento y en qué índice tiene algún tamaño, puede copiarlo en una matriz ... parece un trabajo
IList<T>
pero mi clase tiene tiene un tamaño fijo y no admite agregar ni quitar, por lo que funciona más como una matriz que como una lista. Pero no hayIArray<T>
en el BCL .La interfaz pertenece a una API que se transfiere a múltiples plataformas, y en la implementación de una plataforma en particular, algunas partes no son compatibles. Idealmente, habría alguna forma de detectarlo, de modo que el código portátil que usa dicha API pueda decidir llamar o no esas partes ... pero si las llama, es totalmente apropiado obtenerlas
NotSupportedException
. Esto es particularmente cierto, si este es un puerto a una nueva plataforma que no estaba prevista en el diseño original.También considere por qué no es compatible?
A veces
InvalidOperationException
es una mejor opción. Por ejemplo, una forma más de agregar polimorfismo en una clase es tener varias implementaciones de una interfaz interna y su código está eligiendo cuál instanciar dependiendo de los parámetros dados en el constructor de la clase. [Esto es particularmente útil si sabe que el conjunto de opciones es fijo y no desea permitir que se introduzcan clases de terceros por inyección de dependencia.] He hecho esto para hacer una copia de seguridad de ThreadLocal porque la implementación de seguimiento y no seguimiento son demasiado lejos, y ¿qué implicaThreadLocal.Values
la implementación sin seguimiento?InvalidOperationException
aunque no depende del estado del objeto. En este caso, presenté la clase yo mismo, y sabía que este método tenía que implementarse simplemente lanzando una excepción.A veces un valor predeterminado tiene sentido. Por ejemplo, en lo
ICollection<T>.IsReadOnly
mencionado anteriormente, tiene sentido devolver 'verdadero' o 'falso' según el caso. Entonces ... ¿cuál es la semántica deIFoo.Bar
? quizás haya algún valor predeterminado razonable para devolver.Anexo: si tiene el control de la interfaz (y no necesita quedarse con ella por compatibilidad) no debería haber un caso en el que deba lanzar
NotSupportedException
. Sin embargo, es posible que tenga que dividir la interfaz en dos o más interfaces más pequeñas para tener el ajuste adecuado para su caso, lo que puede conducir a la "contaminación" en situaciones extremas.fuente
Sí, lo hicieron los diseñadores de la biblioteca .Net. La clase Diccionario hace lo que estás haciendo. Utiliza una implementación explícita para ocultar efectivamente * algunos de los métodos IDictionary. Es explicó mejor aquí , pero para resumir, con el fin de utilizar Agregar del diccionario, CopyTo, o quitar métodos que toman KeyValuePairs, primero hay que convertir el objeto a una IDictionary.
* No está "ocultando" los métodos en el sentido estricto de la palabra "ocultar" como lo usa Microsoft . Pero no conozco un término mejor en este caso.
fuente
Siempre puede implementar y devolver un valor benigno o un valor predeterminado. Después de todo, es solo una propiedad. Podría devolver 0 (la propiedad predeterminada de int), o cualquier valor que tenga sentido en su implementación (int.MinValue, int.MaxValue, 1, 42, etc.)
Lanzar una excepción parece una mala forma.
fuente