¿Cuántas son demasiadas interfaces en una clase? [cerrado]

28

Probablemente lo consideraría un olor a código o incluso un antipatrón tener una clase que implemente 23 interfaces. Si de hecho es un antipatrón, ¿cómo lo llamarías? ¿O simplemente no sigue el principio de responsabilidad única?

Jonas Elfström
fuente
3
Bueno, es cuestionable de cualquier manera. Si la clase tiene 23 métodos que cumplen una sola responsabilidad (no todas las clases necesitan tantos métodos, pero no es imposible, especialmente si la mayoría son envoltorios de conveniencia de tres líneas en torno a otros métodos), simplemente tiene interfaces demasiado finas; )
La clase en cuestión es un tipo de entidad y las interfaces son principalmente a propiedades. Hay 113 de esos y solo cuatro métodos.
Jonas Elfström
66
Pensé que las interfaces se utilizan esencialmente para escribir pato en lenguajes compilados estáticamente. ¿Cuál es el problema? :)
cenizas999
2
solo una pregunta: ¿están todas las 113 propiedades pobladas para cada instancia de esta entidad? ¿O es algún tipo de "entidad común" utilizada en diferentes contextos con solo algunas propiedades llenas?
David
1
Volvamos a la comprensión conceptual de lo que constituye una "clase". Una clase es una cosa única con una responsabilidad y un rol claramente definidos. ¿Puedes definir tu objeto de 23 interfaces de esta manera? ¿Puedes publicar una sola oración (que no sea Joycean) que resuma adecuadamente tu clase? Si es así, entonces 23 interfaces está bien. Si no, entonces es demasiado grande, demasiado complicado.
Stephen Gross el

Respuestas:

36

Familia Real: No hacen nada particularmente loco, pero tienen mil millones de títulos y están relacionados con la mayoría de los demás miembros de la realeza de alguna manera.

Ingeniero mundial
fuente
:-)))))))))))))
Emilio Garavaglia
Me estresaríasomehow
Wolf
23

Declaro que este antipatrón se llamará Jack of All Trades, o quizás Too Many Hats.

Mike Partridge
fuente
2
¿Qué tal la navaja suiza?
FrustratedWithFormsDesigner
Parece que este término ya se usa para interfaces con demasiados métodos :-) Aunque eso también tendría sentido aquí.
Jalayn el
1
@FrustratedWithFormsDesigner Una navaja suiza nunca tendría el mal gusto de tener una interfaz llamada IValue que contiene 30 propiedades de las cuales 20 están precedidas por la nueva palabra clave modificadora.
Jonas Elfström
3
+1 para Demasiados sombreros ... para ocultar adecuadamente la deidad de varias cabezas
Newtopian
1
Una navaja suiza es un modelo de reutilización. es decir, una sola "cuchilla" puede realizar muchos métodos, por lo que probablemente sea una mala analogía.
James Anderson el
17

Si tuviera que darle un nombre, creo que lo llamaría Hydra :

Hidra

En la mitología griega, la Hidra de Lernaean (en griego: Λερναία Ὕδρα) era una antigua bestia de agua ctónica sin nombre con forma de serpiente, con rasgos de reptil, (como su nombre lo demuestra) que poseía muchas cabezas: los poetas mencionan más cabezas que los pintores de jarrones pintura, y por cada corte de cabeza crecían dos más, y un aliento venenoso tan virulento que incluso sus huellas eran mortales.

De particular relevancia es el hecho de que no solo tiene muchas cabezas, sino que sigue creciendo cada vez más y es imposible de matar por eso. Esa ha sido mi experiencia con este tipo de diseños; los desarrolladores siguen interfiriendo cada vez más interfaces hasta que tiene demasiadas para caber en la pantalla, y para entonces está tan arraigado en el diseño y las suposiciones del programa que dividirlo es una perspectiva desesperada (si lo intentas, en realidad a menudo terminan necesitando más interfaces para cerrar la brecha).

Una señal temprana de fatalidad inminente debido a una "Hidra" es la transmisión frecuente de una interfaz a otra, sin mucha comprobación de cordura, y a menudo a un objetivo que no tiene sentido, como en:

public void CreateWidget(IPartLocator locator, int widgetTypeId)
{
    var partsNeeded = locator.GetPartsForWidget(widgetTypeId);
    return ((IAssembler)locator).BuildWidget(partsNeeded);
}

Es obvio cuando se saca de contexto que hay algo sospechoso en este código, pero la probabilidad de que esto suceda aumenta a medida que crecen las "cabezas" de un objeto, porque los desarrolladores saben intuitivamente que siempre están tratando con la misma criatura.

Buena suerte nunca hacer cualquier mantenimiento en un objeto que implementa las interfaces 23. Las posibilidades de que no cause daños colaterales en el proceso son escasas o nulas.

Aaronaught
fuente
16

El Objeto de Dios viene a la mente; Un solo objeto que sabe hacer TODO. Esto proviene de la baja adherencia a los requisitos de "cohesión" de las dos principales metodologías de diseño; si tiene un objeto con 23 interfaces, tiene un objeto que sabe cómo ser 23 cosas diferentes para sus usuarios, y esas 23 cosas diferentes probablemente no están en la línea de una sola tarea o área del sistema.

En SOLID, aunque el creador de este objeto obviamente ha intentado seguir el Principio de segregación de interfaz, ha violado una regla anterior; Principio de responsabilidad única. Hay una razón por la que es "SÓLIDO"; S siempre viene primero cuando se piensa en el diseño, y siguen todas las demás reglas.

En GRASP, el creador de dicha clase ha descuidado la regla de "Alta Cohesión"; GRASP, a diferencia de SOLID, enseña que un objeto no TIENE que tener una sola responsabilidad, pero a lo sumo debe tener dos o tres responsabilidades muy relacionadas.

KeithS
fuente
No estoy seguro de que este sea el antipatrón de God Object, ya que Dios no está limitado por ninguna interfaz. Estar limitado por tantas interfaces podría reducir la omnipotencia de Dios. ;) Tal vez este es un objeto de quimera?
FrustratedWithFormsDesigner
Dios tiene muchas caras y puede ser muchas cosas para muchas personas. Si la funcionalidad de las interfaces combinadas hace que el objeto "todo lo sepa" no está realmente limitando a Dios, ¿verdad?
KeithS el
1
A menos que lo que creó las interfaces las modifique. Entonces Dios podría tener que ser reimplementado para cumplir con los cambios de interfaz.
FrustratedWithFormsDesigner
3
Me duele que pienses que fui yo quien creó la clase.
Jonas Elfström
El uso de "usted" es más plural que usted, refiriéndose a su equipo de desarrollo. ALGUIEN en ese equipo, pasado o presente, creó esta clase. Sin embargo, editaré.
KeithS
8

¡23 es solo un número! En el escenario más probable, es lo suficientemente alto como para alarmar. Sin embargo, si preguntamos, ¿cuál es el mayor número de métodos / interfaces antes de que se pueda llamar una etiqueta como "antipatrón"? ¿Son 5, 10 o 25? Te das cuenta de que ese número realmente no es una respuesta porque si 10 es bueno, 11 también puede serlo, y luego cualquier número entero después de eso.

La verdadera pregunta es sobre la complejidad. Y debemos señalar que el código extenso, el número de métodos o el gran tamaño de la clase por cualquier medida NO son en realidad la definición de complejidad. Sí, un código cada vez más grande (mayor número de métodos) dificulta la lectura y comprensión de un nuevo principiante. También maneja funcionalidades potencialmente variadas, gran número de excepciones y algoritmos bastante evolucionados para varios escenarios. Esto no significa que sea complejo, solo es difícil de digerir.

Por otro lado, el código de tamaño relativamente pequeño que uno puede leer en unas pocas horas dentro y fuera puede ser complejo. Aquí es cuando creo que el código es (innecesariamente) complejo.

Aquí se puede poner toda la sabiduría del diseño orientado a objetos para definir "complejo", pero restringiría aquí para mostrar cuando "tantos métodos" es una indicación de complejidad.

  1. Conocimiento mutuo. (también conocido como acoplamiento) Muchas veces, cuando las cosas se escriben como clases, todos pensamos que es un código orientado a objetos "agradable". Pero la suposición sobre la otra clase esencialmente rompe la encapsulación real necesaria. Cuando tiene métodos que "filtran" detalles profundos sobre el estado interno de los algoritmos, y la aplicación se construye con la suposición clave sobre el estado interno de la clase de servicio.

  2. Demasiadas repeticiones (interrumpiendo) Cuando los métodos que tienen nombres similares pero hacen un trabajo contradictorio, o contradicen nombres con una funcionalidad similar. Muchas veces los códigos evolucionan para admitir interfaces ligeramente diferentes para diferentes aplicaciones.

  3. Demasiados roles Cuando la clase sigue agregando funciones secundarias y se expande más a medida que a la gente le gusta, solo para saber que la clase es ahora una clase dos. Sorprendentemente, todos estos comienzan con genuinoLos requisitos y ninguna otra clase existen para hacer esto. Considere esto, hay una clase Transacción, que le dice detalles de la transacción. Se ve bien hasta ahora, ahora alguien requiere la conversión de formato en el "momento de la transacción" (entre UTC y similares), más tarde, las personas agregan reglas para verificar si ciertas cosas estaban en ciertas fechas para validar las transacciones invalidadas. - No escribiré toda la historia, pero eventualmente, la clase de transacción construye todo el calendario y luego la gente comienza a usar "solo el calendario". Esto es muy complejo (imaginar) ¿por qué crearé una instancia de "clase de transacción" para tener la funcionalidad que me proporcionaría un "calendario"!

  4. (In) consistencia de API Cuando hago book_a_ticket () - ¡Reservo un ticket! Eso es muy simple, independientemente de cuántas comprobaciones y procesos se realicen para que esto suceda. Ahora se vuelve complejo cuando el flujo de tiempo comienza a afectar esto. Por lo general, uno permitiría la "búsqueda" y el posible estado disponible / no disponible, luego, para reducir el retroceso, comience a guardar algunos punteros de contexto dentro del ticket y luego consulte eso para reservar el ticket. La búsqueda no es la única funcionalidad: las cosas empeoran después de muchas de estas "funciones secundarias". ¡En el proceso, el significado de book_a_ticket () implica book_that_ticket ()! y eso puede ser inimaginablemente complejo.

Probablemente hay muchas situaciones de este tipo que vería en un código muy evolucionado y estoy seguro de que muchas personas pueden agregar escenarios, donde "tantos métodos" simplemente no tienen sentido o no hacen lo que obviamente pensarían. Este es el antipatrón.

Mi experiencia personal es que cuando los proyectos que comienzan razonablemente de abajo hacia arriba, muchas cosas para las que debería haber clases genuinas por sí mismas, permanecen enterradas o, lo que es peor, aún se dividen entre diferentes clases y aumenta el acoplamiento. Muy a menudo, lo que habría merecido 10 clases, pero tiene solo 4, es probable que muchas de ellas tengan métodos confusos y una gran cantidad de métodos. Llámalo DIOS y DRAGÓN, esto es lo que es MALO.

PERO te encuentras con clases MUY GRANDES que son perfectamente consistentes, tienen 30 métodos y aún son muy LIMPIOS. Pueden ser buenos.

Dipan Mehta
fuente
La clase en preguntas tiene 23 interfaces y éstas contienen, en total, 113 propiedades públicas y cuatro métodos. Solo algunos de ellos son de solo lectura. C # tiene propiedades implementadas automáticamente, por eso difiero entre métodos y propiedades msdn.microsoft.com/en-us/library/bb384054.aspx
Jonas Elfström
7

Las clases deben tener exactamente el número correcto de interfaces; ni mas ni menos.

Decir "demasiados" sería imposible sin mirar si todas esas interfaces tienen o no un propósito útil en esa clase. Declarar que un objeto implementa una interfaz significa que debe implementar sus métodos si espera que la clase se compile. (Supongo que la clase que está viendo lo hace). Si todo en la interfaz está implementado y esas implementaciones hacen algo relativo a las entrañas de la clase, sería difícil decir que la implementación no debería estar allí. El único caso que se me ocurre es que una interfaz que cumple con esos criterios no es externa: cuando nadie la usa.

Algunos lenguajes permiten que las interfaces se "subclasifiquen" utilizando un mecanismo como la extendspalabra clave de Java , y quien lo escribió puede no haberlo sabido. También podría ser que los 23 estén lo suficientemente lejos como para que agregarlos no tenga sentido.

Blrfl
fuente
Supuse que esta pregunta era bastante agnóstica del lenguaje. El código real está en C #.
Jonas Elfström
No he hecho ningún C #, pero parece admitir una forma similar de herencia de interfaz.
Blrfl
Sí, una interfaz puede "heredar" otras interfaces, etc.
Jonas Elfström
Me parece interesante que muchas discusiones se centren en lo que deberían hacer las clases , en lugar de qué instancias deberían hacer. Un robot con una variedad de sensores de imagen y audio conectados a un chasis común debe modelarse como una entidad que tiene una ubicación y orientación, y que puede ver, escuchar y asociar imágenes y sonidos. Toda la lógica real detrás de esas funciones puede estar en otras clases, pero el robot en sí mismo debe admitir métodos como "ver si hay objetos delante", "escuchar ruidos en el área" o "ver si el objeto adelante parece que es generando los sonidos detectados ".
supercat
Bien puede ser que un robot en particular deba subdividir su funcionalidad en subsistemas de una manera particular, pero en la medida en que el código práctico que usa el robot no tenga que saber o importar cómo se subdivide la funcionalidad dentro de un robot en particular. Si los "ojos" y los "oídos" estuvieran expuestos al mundo exterior como objetos separados, pero los sensores correspondientes estuvieran en un brazo de extensión compartido, el código externo podría pedirles a los "oídos" que escuchen los sonidos a nivel del suelo al mismo tiempo que le pidió a los "ojos" que miraran por encima de un obstáculo.
supercat
1

Parece que tienen un par "getter" / "setter" para cada propiedad, como es normal para un "bean" típico y promovieron todos estos métodos a la interfaz. Entonces, ¿qué hay de llamarlo un " haba tiene ". O la "flatulencia" más rabelaisiana después de los efectos bien conocidos de demasiados frijoles.

James Anderson
fuente
Está en C # y tiene propiedades de implementación automática. Estas 23 interfaces, en total, contienen 113 de tales propiedades.
Jonas Elfström
1

El número de interfaces a menudo crece cuando se usa un objeto genérico en diferentes entornos. En C #, las variantes de IComparable, IEqualityComparer e IComparer permiten ordenar en configuraciones distintas, por lo que puede terminar implementando todas ellas, algunas de ellas tal vez más de una vez, ya que puede implementar las versiones genéricas fuertemente tipadas, así como las versiones no genéricas , además, puede implementar más de uno de los genéricos.

Tomemos un escenario de ejemplo, digamos una tienda web donde puede comprar créditos con los que puede comprar otra cosa (los sitios de fotos de archivo a menudo usan este esquema). Es posible que tenga una clase "Valuta" y una clase "Créditos" que heredan de la misma base. Valuta tiene algunas ingeniosas sobrecargas de operadores y rutinas de comparación que le permiten hacer cálculos sin preocuparse por la propiedad "Moneda" (agregar libras a dólares, por ejemplo). Los créditos son mucho más simples pero tienen algún otro comportamiento distinto. Si desea poder compararlos entre sí, podría terminar implementando IComparable, así como IComparable y las otras variantes de interfaces de comparación en ambos (a pesar de que utilizan una implementación común, ya sea en la clase base o en otro lugar).

Al implementar la serialización, ISerializable, se implementan IDeserializationCallback. Luego implementando pilas de deshacer-rehacer: se agrega IClonable Funcionalidad IsDirty: IObservable, INotifyPropertyChanged. Permitir a los usuarios editar los valores utilizando cadenas: IConvertable ... La lista puede seguir y seguir ...

En los idiomas modernos, vemos una tendencia diferente que ayuda a separar estos aspectos y colocarlos en sus propias clases, fuera de la clase principal. La clase o aspecto externo se asocia con la clase de destino mediante el uso de anotaciones (atributos). A menudo es posible hacer que las clases de aspecto externo sean más o menos genéricas.

El uso de atributos (anotación) está sujeto a reflexión. Un inconveniente es la pérdida de rendimiento menor (inicial). Un inconveniente (a menudo emocional) es que los principios como la encapsulación deben relajarse.

Siempre hay otras soluciones, pero para cada solución ingeniosa hay una compensación o una trampa. Por ejemplo, el uso de una solución ORM puede exigir que todas las propiedades se declaren virtuales. Las soluciones de serialización pueden exigir constructores predeterminados en sus clases. Si está utilizando la inyección de dependencia, puede terminar implementando 23 interfaces en una sola clase.

En mi opinión, 23 interfaces no tienen que ser malas por definición. Puede haber un esquema bien pensado detrás de él, o alguna determinación principal, como evitar el uso de la reflexión o las creencias de encapsulación extrema.

Cada vez que cambia un trabajo o tiene que construir sobre una arquitectura existente. Mi consejo es que primero se familiarice por completo, no intente refactorizar todo demasiado rápido. Escuche al desarrollador original (si todavía está allí) e intente descubrir los pensamientos e ideas detrás de lo que ve. Cuando haga preguntas, no lo haga solo por analizarlo, sino para aprender ... Sí, todos tienen sus propios martillos dorados, pero cuantos más martillos pueda recolectar, más fácil será llevarse bien con sus colegas.

Louis Somers
fuente
0

"Demasiado" es subjetivo: ¿estilo de programación? ¿actuación? conformidad con las normas? precedentes? simplemente sentido de comodidad / confianza?

Siempre que su código se comporte correctamente y no presente problemas de mantenimiento, 23 podría incluso ser la nueva norma. Algún día podría decir: "Puedes hacer un excelente trabajo con 23 interfaces, ver: Jonas Elfström".

Kris
fuente
0

Yo diría que el límite debería ser un número menor que 23, más como 5 o 7,
sin embargo, esto no incluye ningún número de interfaces que esas interfaces hereden o cualquier número de interfaces implementadas por las clases base.

(En total, N + cualquier número de interfaces heredadas, donde N <= 7.)

Si su clase implementa demasiadas interfaces, probablemente sea una clase divina .

Danny Varod
fuente