¿Por qué no todos los métodos son virtuales o por qué cada clase no tiene al menos una interfaz?

8

Esta es una pregunta más filosófica, que aborda la plataforma .NET, pero tal vez sea útil también para otros idiomas. Estoy haciendo muchas pruebas unitarias y especialmente cuando uso componentes de terceros con los que a menudo me cuesta. En .NET hay una gran demanda de diseño de componentes (er) para elegir qué método debe ser virtual o no. Por un lado, es el uso de componentes (lo que tiene sentido ser virtual), por otro, es la simulación de componentes . Puede usar cuñas para simular componentes de terceros, pero esto a menudo conduce a un mal diseño y complejidad. Como puedo recordar, la discusión sobre cómo hacer que todos los métodos sean virtuales (JAVA lo tiene desde el principio) en .NET fue sobre el rendimiento. ¿Pero sigue siendo el problema? ¿Por qué no todos los métodos en .NET son virtuales o por qué cada clase no tiene al menos una interfaz?

Anton Kalcik
fuente
1
Ah sí, porque después de un par de minutos alguien me rechazó (mi primera vez) me gustaría saber por qué.
Anton Kalcik
Supongo que la persona que votó para cerrar esta pregunta como "demasiado amplia" también lo rechazó.
David Arno
1
"Programmers Stack Exchange es un sitio de preguntas y respuestas para programadores profesionales interesados ​​en preguntas conceptuales sobre el desarrollo de software" ¿Entendí algo mal?
Anton Kalcik
55
Tiendo a estar de acuerdo en que esta pregunta es demasiado amplia. "¿Por qué cada clase no tiene un virtual?" ¿Por qué debería hacerlo? Mencionas las pruebas. Tal vez podría mejorar la pregunta cambiándola a "¿por qué esta clase (la clase .net más molesta) no tiene estos métodos marcados como virtuales?" Sin embargo, puede descubrir que la respuesta es "Oh sí. Realmente no anticiparon la explosión de TDD al diseñar .NET 1.0"
perfeccionista
44
Si desea ampliarlo porque solo le interesan las opiniones, entonces está fuera de tema por dos razones: demasiado amplio y basado en opiniones .
Jörg W Mittag

Respuestas:

7

Como dice Anders , se trata en parte del rendimiento y en parte del bloqueo de diseños mal pensados ​​para reducir el alcance de los problemas causados ​​posteriormente por personas que heredan ciegamente cosas que no fueron diseñadas para ser heredadas.

El rendimiento parece obvio: si bien la mayoría de los métodos no se notarán en el hardware moderno, un captador virtual podría causar un golpe bastante notable, incluso en el hardware moderno que se basa cada vez más en instrucciones fácilmente predichas en la tubería de la CPU.

Ahora, la razón para hacer que todo sea virtual de manera predeterminada parece estar impulsada por una sola cosa: los marcos de prueba de unidad. Me parece que esta es una razón pobre, en la que estamos cambiando el código para adaptarlo al marco en lugar de diseñarlo correctamente.

Quizás el problema sea con los marcos utilizados aquí, deberían mejorar para permitir el reemplazo de funciones en tiempo de ejecución con cuñas inyectadas en lugar de crear código que lo haga (con todos los trucos para obtener métodos privados y no virtuales, no mencionar funciones estáticas y de terceros)

Entonces, la razón por la cual los métodos no son virtuales por defecto es como dijo Anders, y cualquier deseo de hacerlos virtuales para que se adapten a alguna herramienta de prueba de unidad es poner el carro delante del caballo, el diseño adecuado supera las limitaciones artificiales.

Ahora puede mitigar todo esto mediante el uso de interfaces o reelaborando toda su base de código para usar componentes (o microservicios) que se comunican mediante el paso de mensajes en lugar de llamadas a métodos directamente cableados. Si amplía la superficie de una unidad para probar que es un componente, y ese componente es completamente autónomo, realmente no necesita atornillarlo para probarlo.

gbjbaanb
fuente
No podría estar mas de acuerdo.
Robert Harvey
@gbjbaanb No entendí la última oración "Si amplía la superficie de una unidad para que la prueba sea un componente, y ese componente es completamente autónomo, no necesita atornillarlo para probarlo. "
Anton Kalcik
Excelente respuesta (mejor que la mía, me siento :)
David Arno
1
@AntonKalcik Quiero decir, si tienes un cuadro negro, entonces no debería tener dependencias que necesiten burlarse. Es decir, lo prueba disparando información y viendo lo que sale. Si su caja es pequeña y la ha diseñado para que se desacople de otros servicios, esto puede suceder fácilmente. El problema es que una clase no se puede eliminar de otras clases muy fácilmente, y por lo tanto requiere burlarse. Si piensa en una unidad como un componente (un dll tal vez, o un microservicio) en lugar de una clase, puede probar de esta manera.
gbjbaanb
Respecto al rendimiento: la CPU puede predecir fácilmente la llamada al método virtual (si siempre es la misma). El gran problema es que es mucho más difícil para el compilador JIT incorporar llamadas a métodos virtuales, lo que impide más optimizaciones.
svick
6

Hay dos elementos en su pregunta, así que intentaré abordarlos a su vez:

  1. ¿Por qué los métodos .NET no son virtuales por defecto?

    La herencia, en la práctica, tiene muchos problemas . Entonces, si se va a usar como parte de un diseño, entonces se debe considerar cuidadosamente. Al hacer que los métodos no sean virtuales por defecto, la teoría es que alienta al desarrollador a pensar en la herencia al diseñar una clase. Si eso funciona en la práctica es otra cuestión de rutina.

  2. ¿Por qué no todas las clases implementan una interfaz?

    Mal diseño es la respuesta simple. A pesar de ser comúnmente reconocido como buenas prácticas durante mucho tiempo, lamentablemente mucha gente todavía no diseña sus sistemas con DI y TDD en mente.

La combinación de clases que no son completamente heredables y que no se burlan fácilmente crea una "tormenta perfecta" de código difícil de probar. Sin embargo, hasta que lleguemos al día en que todos usen DI y el principio de diseño de interfaz, no se puede hacer mucho para aliviar el dolor.

David Arno
fuente
3
"Un diseño pobre es la respuesta simple": tiendo a estar de acuerdo, pero hay excepciones. Ver objetos de transferencia de datos u objetos de registro. Para los objetos simples de POCO / POJO / insert language here, no tiene sentido que implementen un interfacesolo porque. ¿Qué valor tiene tener interfacepara lo que es esencialmente una tupla glorificada? (Aunque tal vez usar un POJO / POCO en sí mismo sea un defecto de diseño)
Dan Pantry
2
@AntonKalcik, el equipo de C # adopta un enfoque muy conservador de "nunca introducir un cambio radical" en el lenguaje y hacer que las interfaces sean obligatorias definitivamente sería un cambio radical. Así que sospecho que no verás eso agregado a C #.
David Arno
3
@DanPantry, sin embargo, los objetos de datos simples rara vez necesitan burlarse, por lo que normalmente no necesitan interfaces.
David Arno
2
En C♯, las interfaces definen objetos, las clases definen tipos de datos abstractos. (Es un error común pensar que las clases son lo que usa en C♯ para escribir código orientado a objetos. Todo lo contrario: el código OO en C♯ nunca puede usar clases o estructuras como tipos, solo interfaces. Tan pronto como use clases como tipos , ya no estás haciendo OO.) Por lo tanto, no llamaría a no usar interfaces un error de diseño. Es una opción de diseño: los ADT y los Objetos tienen dos fortalezas y debilidades. Debe elegir lo que tiene más sentido en su dominio particular.
Jörg W Mittag
55
@ JörgWMittag: As soon as you use classes as types, you are no longer doing OO - Las clases son tipos. ¿¿Qué?? La herencia de clase normal es OO, no importa lo que digas. Ya no puedes decidir que ya no es OO solo porque DI está de moda ahora.
Robert Harvey