Cuanto más aprendo sobre diferentes paradigmas de programación, como la programación funcional, más empiezo a cuestionar la sabiduría de los conceptos de OOP como la herencia y el polimorfismo. Primero aprendí sobre la herencia y el polimorfismo en la escuela, y en ese momento el polimorfismo parecía una forma maravillosa de escribir código genérico que permitía una fácil extensibilidad.
Pero frente a la tipificación de patos (tanto dinámicos como estáticos) y características funcionales como funciones de orden superior, he comenzado a considerar la herencia y el polimorfismo como una restricción innecesaria basada en un conjunto frágil de relaciones entre objetos. La idea general detrás del polimorfismo es que escribe una función una vez, y luego puede agregar una nueva funcionalidad a su programa sin cambiar la función original; todo lo que necesita hacer es crear otra clase derivada que implemente los métodos necesarios.
Pero esto es mucho más simple de lograr a través del tipeo de pato, ya sea en un lenguaje dinámico como Python o en un lenguaje estático como C ++.
Como ejemplo, considere la siguiente función de Python, seguida de su equivalente estático de C ++:
def foo(obj):
obj.doSomething()
template <class Obj>
void foo(Obj& obj)
{
obj.doSomething();
}
El equivalente de OOP sería algo así como el siguiente código Java:
public void foo(DoSomethingable obj)
{
obj.doSomething();
}
La principal diferencia, por supuesto, es que la versión de Java requiere la creación de una interfaz o una jerarquía de herencia antes de que funcione. La versión de Java implica más trabajo y es menos flexible. Además, encuentro que la mayoría de las jerarquías de herencia del mundo real son algo inestables. Todos hemos visto ejemplos artificiales de Formas y animales, pero en el mundo real, a medida que cambian los requisitos comerciales y se agregan nuevas características, es difícil realizar cualquier trabajo antes de que realmente necesite estirar la relación "es-una" entre subclases, o bien remodelar / refactorizar su jerarquía para incluir más clases base o interfaces para acomodar nuevos requisitos. Con la escritura de pato, no necesita preocuparse por modelar nada, solo le preocupa la funcionalidad que necesita.
Sin embargo, la herencia y el polimorfismo son tan populares que dudo que sea una exageración llamarlos la estrategia dominante para la extensibilidad y la reutilización del código. Entonces, ¿por qué la herencia y el polimorfismo son tan exitosos? ¿Estoy pasando por alto algunas ventajas serias que tiene la herencia / polimorfismo sobre la tipificación de patos?
fuente
obj
no tiene undoSomething
método? ¿Se plantea una excepción? ¿No pasa nada?Respuestas:
Principalmente estoy de acuerdo contigo, pero por diversión jugaré a Devil's Advocate. Las interfaces explícitas brindan un lugar único para buscar un contrato explícitamente especificado formalmente , que le indica qué se supone que debe hacer un tipo. Esto puede ser importante cuando no eres el único desarrollador en un proyecto.
Además, estas interfaces explícitas se pueden implementar de manera más eficiente que la escritura de pato. Una llamada de función virtual tiene apenas más gastos generales que una llamada de función normal, excepto que no puede estar en línea. La escritura de patos tiene una sobrecarga considerable. La tipificación estructural de estilo C ++ (usando plantillas) puede generar grandes cantidades de hinchazón de archivos de objetos (ya que cada instancia es independiente a nivel de archivo de objetos) y no funciona cuando necesita polimorfismo en tiempo de ejecución, no tiempo de compilación.
En pocas palabras: estoy de acuerdo en que la herencia de estilo Java y el polimorfismo pueden ser un PITA y las alternativas deben usarse con más frecuencia, pero aún tiene sus ventajas.
fuente
La herencia y el polimorfismo se usan ampliamente porque funcionan para ciertos tipos de problemas de programación.
No es que se enseñen ampliamente en las escuelas, eso es al revés: se enseñan ampliamente en las escuelas porque la gente (también conocida como el mercado) descubrió que funcionaban mejor que las herramientas antiguas, por lo que las escuelas comenzaron a enseñarles. [Anécdota: cuando estaba aprendiendo OOP por primera vez, fue extremadamente difícil encontrar una universidad que enseñara cualquier idioma de OOP. Diez años después, fue difícil encontrar una universidad que no enseñara un idioma OOP.]
Tu dijiste:
Yo digo:
No es
Lo que usted describe no es polimorfismo, sino herencia. ¡No es de extrañar que tengas problemas de OOP! ;-)
Retroceda un paso: el polimorfismo es un beneficio de la transmisión de mensajes; simplemente significa que cada objeto es libre de responder a un mensaje a su manera.
Entonces ... Duck Typing es (o más bien, habilita) polimorfismo
La esencia de su pregunta parece no ser que no comprende la POO o que no le gusta, sino que no le gusta definir interfaces . Eso está bien, y mientras tengas cuidado, las cosas funcionarán bien. El inconveniente es que si cometió un error, omitió un método, por ejemplo, no lo descubrirá hasta el tiempo de ejecución.
Eso es algo estático vs dinámico, que es una discusión tan antigua como Lisp, y no se limita a OOP.
fuente
¿Por qué?
La herencia (con o sin escritura de pato) asegura la reutilización de una característica común. Si es común, puede asegurarse de que se reutilice constantemente en las subclases.
Eso es todo. No hay "restricción innecesaria". Es una simplificación
El polimorfismo, de manera similar, es lo que significa "escribir pato". Los mismos métodos Muchas clases con una interfaz idéntica, pero diferentes implementaciones.
No es una restricción. Es una simplificación
fuente
Se abusa de la herencia, pero también se teclea el pato. Ambos pueden y conducen a problemas.
Con un tipeo fuerte se obtienen muchas "pruebas unitarias" en tiempo de compilación. Con la escritura de patos, a menudo tienes que escribirlos.
fuente
Lo bueno de aprender cosas en la escuela es que se hace aprenderlos. Lo que no es tan bueno es que puede aceptarlos demasiado dogmáticamente, sin comprender cuándo son útiles y cuándo no.
Entonces, si lo ha aprendido dogmáticamente, más tarde puede "rebelarse" igual de dogmáticamente en la otra dirección. Eso tampoco es bueno.
Como con cualquier idea de este tipo, es mejor adoptar un enfoque pragmático. Desarrolle una comprensión de dónde encajan y dónde no. E ignore todas las formas en que han sido sobrevendidos.
fuente
Sí, la escritura estática y las interfaces son restricciones. Pero todo desde que se inventó la programación estructurada (es decir, "se considera dañino") se ha tratado de limitarnos. El tío Bob tiene una excelente visión de esto en su video blog .
Ahora, uno puede argumentar que la constricción es mala, pero por otro lado, trae orden, control y familiaridad a un tema que de otra manera sería muy complejo.
Restringir las restricciones al (re) introducir la escritura dinámica e incluso el acceso directo a la memoria es un concepto muy poderoso, pero también puede dificultar el tratamiento de muchos programadores. Especialmente los programadores solían confiar en el compilador y escribir la seguridad para gran parte de su trabajo.
fuente
La herencia es una relación muy fuerte entre dos clases. No creo que Java tenga más fuerza. Por lo tanto, solo debe usarlo cuando lo diga en serio. La herencia pública es una relación "es-a", no una "generalmente es-a". Es realmente muy fácil abusar de la herencia y terminar con un desastre. En muchos casos, la herencia se utiliza para representar "has-a" o "toma-funcionalidad-de-a", y eso suele hacerse mejor por composición.
El polimorfismo es una consecuencia directa de la relación "es-a". Si Derived hereda de Base, entonces cada Base "is-a" Derivada, y por lo tanto, puede usar un Derivado donde sea que use una Base. Si esto no tiene sentido en una jerarquía de herencia, entonces la jerarquía es incorrecta y probablemente haya demasiada herencia.
La escritura de pato es una característica interesante, pero el compilador no te avisará si vas a usarla mal. Si no desea manejar las excepciones en tiempo de ejecución, debe asegurarse de obtener los resultados correctos en todo momento. Puede ser más fácil solo definir una jerarquía de herencia estática.
No soy un verdadero fanático de la escritura estática (considero que a menudo es una forma de optimización prematura), pero elimina algunas clases de errores, y muchas personas piensan que vale la pena eliminar esas clases.
Si te gusta la escritura dinámica y la escritura de pato mejor que la escritura estática y las jerarquías de herencia definidas, está bien. Sin embargo, la forma Java tiene sus ventajas.
fuente
Me di cuenta de que cuanto más uso los cierres de C #, menos hago POO tradicional. La herencia solía ser la única forma de compartir fácilmente la implementación, por lo que creo que a menudo se usó en exceso y los límites de la concepción se llevaron demasiado lejos.
Si bien generalmente puede usar cierres para hacer la mayor parte de lo que haría con la herencia, también puede ponerse feo.
Básicamente es una situación de herramienta adecuada para el trabajo: la OOP tradicional puede funcionar realmente bien cuando tiene un modelo que le conviene, y los cierres pueden funcionar realmente bien cuando no lo hace.
fuente
La verdad se encuentra en algún lugar en el medio. Me gusta cómo el lenguaje C # 4.0 estáticamente tipado admite "tipear pato" por palabra clave "dinámica".
fuente
La herencia, incluso cuando la razón desde una perspectiva FP, es un gran concepto; no solo ahorra mucho tiempo, da sentido a la relación entre ciertos objetos en su programa.
Aquí la clase
GoldenRetriever
tiene lo mismoSound
queDog
gratis gracias a la herencia.Escribiré el mismo ejemplo con mi nivel de Haskell para que veas la diferencia
Aquí no se escapa tener que especificar
sound
paraGoldenRetriever
. Lo más fácil en general seríapero solo imagen si tienes 20 funciones! Si hay un experto de Haskell, muéstrenos una manera más fácil.
Dicho esto, sería genial tener coincidencia de patrones y herencia al mismo tiempo, donde una función pasaría por defecto a la clase base si la instancia actual no tiene la implementación.
fuente
Animal
es el anti-tutorial de OOP.