¿Por qué la herencia y el polimorfismo son tan ampliamente utilizados?


18

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?


No soy un experto en Python, así que tengo que preguntar: ¿qué pasaría si objno tiene un doSomethingmétodo? ¿Se plantea una excepción? ¿No pasa nada?
FrustratedWithFormsDesigner

66
@ Frustrado: se plantea una excepción muy clara y específica.
dsimcha

11
¿No es el tipeo de pato solo polimorfismo tomado hasta las once? Supongo que no estás frustrado con OOP sino con sus encarnaciones estáticamente escritas. Puedo entender eso, realmente, pero eso no hace que OOP en su conjunto sea una mala idea.

3
En primer lugar leer "ampliamente" como "salvaje" ...

Respuestas:


22

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.


29

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:

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

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.


2
+1 para "escribir pato es polimorfismo". El polimorfismo y la herencia se han encadenado inconscientemente durante demasiado tiempo, y la mecanografía estática estricta es la única razón real por la que tuvieron que ser.
cHao

+1. Creo que la distinción estática vs dinámica debería ser más que una nota al margen: está en el centro de la distinción entre tipeo de pato y polimorfismo de estilo java.
Sava B.

8

Comencé a considerar la herencia y el polimorfismo como una restricción innecesaria basada en un conjunto frágil de relaciones entre objetos.

¿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


7

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.


3
Su comentario sobre las pruebas solo se aplica a la escritura dinámica de pato. La escritura estática de pato estilo C ++ (con plantillas) le da errores en tiempo de compilación. (Aunque, concedidos C ++ plantilla errores son bastante difíciles de descifrar, pero eso es un asunto completamente diferente.)
Channel72

2

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.


2

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.


1

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.


0

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.


-1

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".


-1

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.

public class Animal {
    public virtual string Sound () {
        return "Some Sound";
    }
}

public class Dog : Animal {
    public override string Sound () {
        return "Woof";
    }
}

public class Cat : Animal {
    public override string Sound () {
        return "Mew";
    }
}

public class GoldenRetriever : Dog {

}

Aquí la clase GoldenRetrievertiene lo mismo Soundque Doggratis gracias a la herencia.

Escribiré el mismo ejemplo con mi nivel de Haskell para que veas la diferencia

data Animal = Animal | Dog | Cat | GoldenRetriever

sound :: Animal -> String
sound Animal = "Some Sound"
sound Dog = "Woof"
sound Cat = "Mew"
sound GoldenRetriever = "Woof"

Aquí no se escapa tener que especificar soundpara GoldenRetriever. Lo más fácil en general sería

sound GoldenRetriever = sound Dog

pero 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.


55
Lo juro, Animales el anti-tutorial de OOP.
Telastyn

@Telastyn Aprendí el ejemplo de Derek Banas en Youtube. Gran profesor. En mi opinión, es muy fácil de visualizar y razonar. Puedes editar la publicación con un mejor ejemplo.
Cristian Garcia

Esto no parece agregar nada sustancial sobre las 10 respuestas anteriores
mosquito

@gnat Mientras la "sustancia" ya está disponible, una persona nueva en el tema podría encontrar un ejemplo más fácil de entender.
Cristian Garcia

2
Los animales y las formas son realmente malas aplicaciones del polimorfismo por varias razones que se han discutido hasta la saciedad en este sitio. Básicamente, decir que un gato "es un" animal no tiene sentido, ya que no existe un "animal" concreto. Lo que realmente quieres modelar es comportamiento, no identidad. Definir una interfaz "CanSpeak" que podría implementarse como un maullido o un ladrido tiene sentido. Definir una interfaz "HasArea" que un cuadrado y un círculo puedan implementar tiene sentido, pero no una forma genérica.
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.