2009-03-31 5 views
8

Estoy bebiendo el coolade y amándolo - interfaces, IoC, DI, TDD, etc. etc. Trabajando bastante bien. ¡Pero estoy encontrando que tengo que luchar una tendencia a hacer todo una interfaz! Tengo una fábrica que es una interfaz. Sus métodos devuelven objetos que podrían ser interfaces (podrían facilitar las pruebas). Esos objetos son interfaces DI a los servicios que necesitan. Lo que estoy descubriendo es que mantener las interfaces sincronizadas con las implementaciones se está agregando al trabajo: agregar un método a una clase significa sumarlo a la clase + la interfaz, simulacros, etc.Interfaz Insanity

¿Estoy factorizando las interfaces? fuera demasiado temprano? ¿Existen mejores prácticas para saber cuándo algo debería devolver una interfaz frente a un objeto?

+0

Bien, ahora todos ... ¡cambie de computadora y lea el código de cada uno! –

Respuesta

7

Las interfaces son útiles cuando desea simular una interacción entre un objeto y uno de sus colaboradores.Sin embargo, hay menos valor en una interfaz para un objeto que tiene un estado interno.

Por ejemplo, supongo que tengo un servicio que habla con un repositorio para extraer algún objeto de dominio con el fin de manipularlo de alguna manera.

Existe un valor de diseño definido al extraer una interfaz del repositorio. Mi implementación concreta del repositorio bien puede estar fuertemente vinculada a NHibernate o ActiveRecord. Al vincular mi servicio a la interfaz obtengo una separación limpia de este detalle de implementación. Da la casualidad de que también puedo escribir pruebas de unidades independientes súper rápidas para mi servicio ahora que puedo entregarle un IRepository falso.

Considerando el objeto de dominio que regresó del repositorio y sobre el cual actúa mi servicio, hay menos valor. Cuando escribo la prueba para mi servicio, querré usar un objeto de dominio real y verificar su estado. P.ej. después de la llamada al servicio.AddSomething() Quiero verificar que se haya agregado algo al objeto de dominio. Puedo probar esto mediante una simple inspección del estado del objeto de dominio. Cuando pruebo mi objeto de dominio de forma aislada, no necesito interfaces, ya que solo realizaré operaciones en el objeto y lo examinaré en su estado interno. p.ej. ¿Es válido para mis ovejas comer hierba si está durmiendo?

En el primer caso, estamos interesados ​​en interacción basado en la prueba. Las interfaces ayudan porque queremos interceptar las llamadas que pasan entre el objeto bajo prueba y sus colaboradores con los simulacros. En el segundo caso, nos interesan las pruebas basadas en . Las interfaces no ayudan aquí. Intente ser consciente de si está probando el estado o las interacciones y deje que eso influya en su interfaz o no tome ninguna decisión sobre la interfaz.

Recuerde que (siempre que tenga una copia de Resharper instalada) es extremadamente económico extraer una interfaz más adelante. También es barato eliminar la interfaz y volver a una jerarquía de clases más simple si decides que no necesitas esa interfaz después de todo. Mi consejo sería comenzar sin interfaces y extraerlas a pedido cuando descubras que quieres simular la interacción.

Cuando incorpora IoC en la imagen, entonces tendería a extraer más interfaces, pero trate de controlar cuántas clases inserta en el contenedor de IoC. En general, desea mantener estos restringidos a objetos de servicio en gran parte sin estado.

6

Parece que estás sufriendo un poco de BDUF.

Tómelo con calma con el coolade y déjelo fluir naturalmente.

5

Normalmente encuentro que quiero interfaces para "servicios", mientras que los tipos que son principalmente sobre "datos" pueden ser clases concretas. Por ejemplo, tendría una interfaz Authenticator, pero una clase Contact. Por supuesto, no siempre es claro, pero es una regla empírica inicial.

me siento su dolor, aunque - que es un poco como volver a los días oscuros de .hy archivos .c ...

0

interfaces tienen el propósito de establecer un contrato y son particularmente útiles cuando se quiere cambiar la clase realizando una tarea sobre la marcha. Si no hay necesidad de cambiar las clases, una interfaz podría interferir.

Existen herramientas automáticas para extraer la interfaz, por lo que quizás sea mejor demorar la extracción de la interfaz más adelante en el proceso.

-1

No cree las interfaces primero; no las va a necesitar. No puede adivinar para qué clases necesitará una interfaz de para qué clases no. Por lo tanto, no dedique tiempo a cargar el código con una interfaz inútil ahora.

Pero extráigalos cuando sienta la necesidad de hacerlo, cuando vea la necesidad de una interfaz, en el paso de refactorización.

Those answers pueden ayudar también.

+1

-1 El diseño siempre debe comenzar con las interfaces. –

6

Recuerde que si bien la flexibilidad es un objetivo digno, la flexibilidad adicional con IoC y DI (que en cierta medida son requisitos para TDD) también aumenta la complejidad. El único punto de flexibilidad es hacer los cambios río abajo más rápido, más barato o mejor. Cada punto IoC/DI aumenta la complejidad y, por lo tanto, contribuye a hacer que los cambios en otros lugares sean más difíciles.

Esto es en realidad donde necesita un Big Design Up Front para alguna medida: identifique qué áreas tienen más probabilidades de cambiar (y/o necesita pruebas unitarias exhaustivas), y planifique la flexibilidad allí. Refactor para eliminar la flexibilidad cuando los cambios son poco probables.

Ahora, no estoy diciendo que pueda adivinar dónde se necesitará flexibilidad con cualquier tipo de precisión. Estarás equivocado. Pero es probable que consigas algo bien. Cuando más tarde descubra que no necesita flexibilidad, se puede tener en cuenta en el mantenimiento. Donde lo necesite, se puede tener en cuenta al agregar funciones.

Ahora, las áreas que pueden o no cambiar dependen del problema de su empresa y del entorno de TI. Aquí hay algunas áreas recurrentes.

  1. Siempre lo consideraría interfaces externas, donde se integran con otros sistemas ser altamente mutable.
  2. Cualquiera que sea el código que proporcione un back-end a , la interfaz de usuario deberá admitir cambios en la IU. Sin embargo, planifique los cambios en la funcionalidad principalmente: no se exceda y planifique para diferentes tecnologías de interfaz de usuario (como el soporte de un cliente inteligente y una aplicación web, los patrones de uso serán muy diferentes).
  3. Por otro lado, la codificación de portabilidad a diferentes bases de datos y plataformas es generalmente un desperdicio de tiempo al menos en entornos corporativos. Pregunte y compruebe qué planes pueden existir para reemplazar o tecnologías de actualización dentro de la probable vida útil de su software .
  4. Cambios en el contenido de datos y formatos son un negocio difícil : mientras que los datos serán ocasiones cambiar, la mayoría de los diseños que he visto mango tales cambios mal, y por lo tanto se obtiene concretas clases entidad utilizar directamente.

Pero solo usted puede hacer el juicio de lo que podría o no debería cambiar.

2

Creo que el principio "ágil" más importante es YAGNI ("No lo vas a necesitar"). En otras palabras, no escriba código adicional hasta que realmente lo necesite, ya que si lo escribe con anticipación, los requisitos y restricciones bien pueden haber cambiado cuando (¡si!) Finalmente lo necesita.

Interfaces, inyecciones de dependencia, etc. - todo esto agrega complejidad a su código, por lo que es más difícil de entender y cambiar. Mi regla de oro es mantener las cosas lo más simples posible (pero no más simples) y no agregar complejidad a menos que me genere más que suficiente para compensar la carga que impone.

Así que si realmente está probando y tener un objeto de simulacro sería muy útil, entonces definitivamente defina una interfaz que implementen tanto sus clases simuladas como reales. Pero no cree un grupo de interfaces por motivos puramente hipotéticos que podrían ser útiles en algún momento, o que es un diseño de OO "correcto".

+0

Por favor, exponer en la declaración "todo esto agrega complejidad a su código, por lo que es más difícil de entender y cambiar". Es fundamental hasta el punto en su respuesta, pero no da ninguna razón para * por qué * es verdad. –

0

Depende mucho de lo que proporcione ... si está trabajando en asuntos internos, entonces el consejo de "no los haga hasta que lo necesite" es razonable. Sin embargo, si está haciendo una API que otros desarrolladores deben consumir, cambiar las cosas en las interfaces en una fecha posterior puede ser molesto.

Una buena regla empírica es hacer interfaces de todo lo que necesita ser subclases. Este no es un tipo de cosas como "haz siempre una interfaz en ese caso", aún necesitas pensar en ello.

Por lo tanto, la respuesta corta es (y esto funciona con ambas cosas internas y proporcionar una API) es que si prevé que se necesitará más de una implementación, conviértala en una interfaz.

Somethings que generalmente no serían interfaces serían las clases que solo contienen datos, como por ejemplo una clase de ubicación que trata sobre xey. Las probabilidades de que haya otra implementación de eso son escasas.