Supongamos que tiene la siguiente interfaz
public interface IUserRepository
{
User GetByID(int userID);
}
¿Cómo obligaría a los implementadores de esta interfaz a lanzar una excepción si no se encuentra un usuario?
Sospecho que no es posible hacerlo solo con el código, entonces, ¿cómo obligaría a los implementadores a implementar el comportamiento deseado? ¿Ya sea a través de código, documentación, etc.?
En este ejemplo, se espera que la implementación concreta arroje un UserNotFoundException
public class SomeClass
{
private readonly IUserRepository _userRepository;
public SomeClass(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public void DisplayUser()
{
try
{
var user = _userRepository.GetByID(5);
MessageBox.Show("Hello " + user.Name);
}
catch (UserNotFoundException)
{
MessageBox.Show("User not found");
}
}
}
Respuestas:
Esta es una característica del lenguaje que se omitió intencionalmente de C # . En pocas palabras, es completamente posible
IUserRepository.GetByID
que falle por alguna otra razón que no sea que no se encuentre al usuario, por lo que no desea exigir un error específico cuando no puede suceder. Tiene dos opciones si, por cualquier motivo, desea aplicar este comportamiento:User
clase para que arroje la excepción si se inicializa incorrectamente.IUserRepository
esa prueba explícita de este comportamiento.Tenga en cuenta que ninguna de esas opciones es "incluirla en la documentación". Idealmente, debería hacerlo de todos modos, especialmente porque la documentación es donde puede explicar por qué desea aplicar un tipo de error en particular.
fuente
No hay forma de requerir una implementación para lanzar una excepción a través de una interfaz, incluso en lenguajes como Java donde puede declarar que un método podría arrojar una excepción.
Puede haber una manera de asegurar (hasta cierto punto, pero no del todo) que se produce una excepción. Puede crear una implementación abstracta de su interfaz. Luego puede implementar el
GetUser
método como final en la clase abstracta y usar el patrón de estrategia para llamar a otro miembro protegido de la subclase y lanzar una excepción si devuelve algo que no sea un usuario válido (como nulo). Esto todavía puede caerse si, por ejemplo, el otro desarrollador devuelve un tipo de objeto nuloUser
, pero realmente tendrían que trabajar para subvertir la intención aquí. También podrían simplemente reimplementar su interfaz, también mala, por lo que puede considerar reemplazar la interfaz por completo con la clase abstracta.(Se pueden lograr resultados similares usando la delegación en lugar de subclasificar con algo como un decorador de envoltura).
Otra opción podría ser crear un conjunto de pruebas de conformidad que todo el código de implementación debe pasar para ser incluido. La eficacia de esto depende de cuánto control tenga sobre el enlace del otro código al suyo.
También estoy de acuerdo con otros en que la documentación y la comunicación son claras cuando se espera un requisito como este, pero no se puede aplicar por completo en el código.
Ejemplos de código:
Método de subclase:
Método decorador:
Un último punto rápido que quiero señalar que olvidé antes es que al hacer cualquiera de estos, se está vinculando a una implementación más específica, lo que significa que los desarrolladores implementadores obtienen mucha menos libertad.
fuente
Si trabaja junto con los desarrolladores que desea implementar el comportamiento de lanzamiento de excepciones, entonces podría escribir pruebas unitarias (para ellos) para verificar si se produce una excepción si intenta obtener un usuario que no existe.
Por lo tanto, necesita saber qué métodos probar. No estoy familiarizado con C #, pero tal vez podría usar la reflexión para buscar todas las clases que implementan el IUserRepository y probarlas automáticamente, por lo que incluso si un desarrollador agrega otra clase, se probará cuando se ejecuten las pruebas.
Pero eso es solo lo que podría hacer en la práctica si realmente sufre de los desarrolladores que hacen mal las implementaciones con las que tiene que trabajar. En teoría, las interfaces no son para definir cómo se deben implementar las lógicas comerciales.
Espero otras respuestas ya que esta es realmente una pregunta interesante.
fuente
No puedes Eso es lo que pasa con las interfaces: permiten que cualquiera las implemente en cualquier momento. Hay un número infinito de implementaciones potenciales para su interfaz, y no puede forzar a ninguna de ellas a ser correcta. Al elegir una interfaz, ha perdido su derecho de exigir que se use una implementación particular en todo el sistema. Diablos, lo que tienes ni siquiera es una interfaz, es una función. Estás escribiendo código que pasa funciones.
Si necesita seguir esta ruta, simplemente tendrá que documentar claramente la especificación que las implementaciones deben cumplir y confiar en que nadie romperá esa especificación deliberadamente.
La alternativa es tener un tipo de datos abstracto, que se traduce en una clase en lenguajes similares a Java / C #. El problema es que los idiomas principales tienen un soporte débil para los ADT y no se puede cambiar la implementación de la clase sin el sistema de archivos tomfoolery. Pero si está dispuesto a vivir con eso, puede asegurarse de que solo haya una implementación en el sistema. Eso es un paso adelante de las interfaces, donde su código puede romperse en cualquier momento, y cuando lo haga, tendrá que averiguar qué implementación de la interfaz rompió su código y de dónde vino.
EDITAR : Tenga en cuenta que incluso los hacks de IL no le permitirán garantizar la corrección de ciertos aspectos de una implementación. Por ejemplo, se supone implícitamente que
GetByID
debe devolver un valor o lanzar una excepción. Alguien podría escribir una implementación que simplemente entra en un bucle sin fin y no lo hace. No puede probar durante el tiempo de ejecución que el código se repite para siempre; si logra hacer esto, ha resuelto el problema de detención. Es posible que pueda detectar casos triviales (por ejemplo, reconocer awhile (true) {}
) pero no un fragmento de código arbitrario.fuente
No hay forma de forzar al 100% el comportamiento que desea, pero al usar contratos de código se podría decir que las implementaciones no deben devolver un valor nulo, y tal vez que el objeto Usuario haya pasado la identificación (por lo que un objeto con ID de usuario 0 fallaría si se pasa 1 ) Además de eso, documentar lo que espera de las implementaciones también podría ayudar.
fuente
Probablemente sea muy tarde para responder esta pregunta, pero me gustaría compartir mi opinión al respecto. Como todas las respuestas sugieren, no es posible forzar la restricción con la interfaz. Incluso con hacks sería difícil.
Una forma de lograr esto, probablemente específica para .NET es diseñar la interfaz como se muestra a continuación:
La tarea no obligará a lanzar cierto tipo de Excepción, pero tiene la disposición de transmitir la intención de todo el implementador de esta interfaz. Las tareas en .NET tienen cierto patrón de uso, como Task.Result para acceder al resultado, Task.Exception si hay una excepción. Además, esto también lo usará el usuario de forma asíncrona.
fuente
No puede hacer eso con una interfaz, ya que muchos otros ya han respondido.
Pero puede hacerlo con una clase base abstracta:
fuente