¿Cómo se usa el polimorfismo en el mundo real? [cerrado]

17

Estoy tratando de entender cómo se usa el polimorfismo en un proyecto de la vida real, pero solo puedo encontrar el ejemplo clásico (o algo similar) de tener una Animalclase principal con un método speak(), y muchas clases secundarias que anulan este método, y ahora Puede llamar al método speak()en cualquiera de los objetos secundarios, por ejemplo:

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();
Christopher
fuente
1
Las colecciones, que ves y usas todos los días, son suficientes para entender qué es el polimorfismo. Pero cómo usar el polimorfismo de manera efectiva en la resolución de problemas es una habilidad que adquieres mosty por experiencia y no simplemente por discutir. Ve y ensuciate las manos.
Durgadass S
Si tiene un conjunto de tipos que admitirán algún tipo de interfaz mínima (por ejemplo, un conjunto de objetos que deben dibujarse), una interfaz suele ser una buena opción para ocultar las diferencias entre los objetos de la llamada para dibujarla. Además, si está creando (o trabajando con) una API que tiene métodos que pueden servir a un objeto base y un número significativo de tipos que heredan de él más o menos de la misma manera , el polimorfismo puede ser la mejor manera de abstraer las diferencias entre esos tipos
jrh
En general, si con frecuencia realiza métodos sobrecargados para manejar diferentes tipos y el código es similar, o si escribe con if(x is SomeType) DoSomething()frecuencia, puede valer la pena usar polimorfismo. Para mí, el polimorfismo es una decisión similar a cuándo hacer un método separado, si descubro que repito el código varias veces, generalmente lo refactorizo ​​en un método, y si descubro que estoy haciendo if object is this type do thiscódigo a menudo, podría ser Vale la pena refactorizar y agregar una interfaz o clase.
jrh

Respuestas:

35

Stream es un gran ejemplo de polimorfismo.

La secuencia representa una "secuencia de bytes que se pueden leer o escribir". Pero esta secuencia puede provenir de archivos, memoria o muchos tipos de conexiones de red. O puede servir como decorador, que envuelve la secuencia existente y transforma los bytes de alguna manera, como el cifrado o la compresión.

De esta manera, el cliente que usa Stream no necesita preocuparse de dónde provienen los bytes. Solo que se pueden leer en secuencia.

Algunos dirían que Streames un ejemplo incorrecto de polimorfismo, porque define muchas "características" que sus implementadores no admiten, como la transmisión de red que solo permite leer o escribir, pero no ambas al mismo tiempo. O falta de búsqueda. Pero esa es solo una cuestión de complejidad, ya que Streampuede subdividirse en muchas partes que podrían implementarse de forma independiente.

Eufórico
fuente
2
En idiomas con la herencia múltiple y virtual como C ++, este ejemplo puede incluso demostrar el patrón "diamante temida" ... mediante la derivación de clases de entrada y flujo de salida a partir de una clase de secuencia base, y que se extiende tanto a crear un flujo de I / O
gyre
2
@gyre Y bien hecho, no hay razón para "temer" el patrón de diamantes. Necesitar ser consciente de la contraparte opuesta en el diamante y no causar conflictos de nombre con él es importante, y un desafío, y molesto, y una razón para evitar el patrón de diamante donde sea posible ... pero no vaya demasiado lejos temiéndolo cuando simplemente tener, por ejemplo, una convención de nomenclatura podría resolver los problemas.
KRyan
Los +1 Streamson mi ejemplo de polimorfismo favorito de todos los tiempos. Ya ni siquiera intento enseñarle a la gente que el modelo defectuoso de 'animal, mamífero y perro' Streamhace un trabajo mejor.
Pharap
@KRyan No estaba expresando mis propios pensamientos llamándolo el "diamante temido", acabo de escucharlo referido como tal. Estoy completamente de acuerdo; Creo que es algo que todo desarrollador debería poder entender y usar adecuadamente.
giro
@gyre Oh, sí, de hecho lo tengo; por eso comencé con "y" para indicar que era una extensión de su pensamiento, en lugar de una contradicción.
KRyan
7

Un ejemplo típico relacionado con los juegos sería una clase base Entity, que proporciona miembros comunes como draw()o update().

Para un ejemplo orientado a datos más puro, podría haber una clase base que Serializableproporcione un común saveToStream()y loadFromStream().

Mario
fuente
6

Existen diferentes tipos de polimorfismo, el de interés suele ser el polimorfismo / despacho dinámico en tiempo de ejecución.

Una descripción de muy alto nivel del polimorfismo de tiempo de ejecución es que una llamada a un método hace cosas diferentes dependiendo del tipo de tiempo de ejecución de sus argumentos: el objeto mismo es responsable de resolver una llamada a un método. Esto permite una gran cantidad de flexibilidad.

Una de las formas más comunes de usar esta flexibilidad es para la inyección de dependencia , por ejemplo, para que pueda cambiar entre diferentes implementaciones o inyectar objetos simulados para realizar pruebas. Si sé de antemano que solo habrá un número limitado de opciones posibles, podría intentar codificarlas con condicionales, por ejemplo:

void foo() {
  if (isTesting) {
    ... // do mock stuff
  } else {
    ... // do normal stuff
  }
}

Esto hace que el código sea difícil de seguir. La alternativa es introducir una interfaz para esa operación y escribir una implementación normal y una implementación simulada de esa interfaz, e "inyectar" a la implementación deseada en tiempo de ejecución. "Inyección de dependencia" es un término complicado para "pasar el objeto correcto como argumento".

Como ejemplo del mundo real, actualmente estoy trabajando en un tipo de problema de aprendizaje automático. Tengo un algoritmo que requiere un modelo de predicción. Pero quiero probar diferentes algoritmos de aprendizaje automático. Entonces definí una interfaz. ¿Qué necesito de mi modelo de predicción? Dada alguna muestra de entrada, la predicción y sus errores:

interface Model {
  def predict(sample) -> (prediction: float, std: float);
}

Mi algoritmo toma una función de fábrica que entrena un modelo:

def my_algorithm(..., train_model: (observations) -> Model, ...) {
  ...
  Model model = train_model(observations);
  ...
  y, std = model.predict(x)
  ...
}

Ahora tengo varias implementaciones de la interfaz del modelo y puedo compararlas entre sí. Una de estas implementaciones en realidad toma otros dos modelos y los combina en un modelo impulsado. Entonces, gracias a esta interfaz:

  • mi algoritmo no necesita saber sobre modelos específicos de antemano,
  • Puedo cambiar fácilmente modelos, y
  • Tengo mucha flexibilidad para implementar mis modelos.

Un caso de uso clásico del polimorfismo está en las GUI. En un marco GUI como Java AWT / Swing / ... hay diferentes componentes . La interfaz de componente / clase base describe acciones como pintar en la pantalla o reaccionar a los clics del mouse. Muchos componentes son contenedores que administran subcomponentes. ¿Cómo podría dibujarse tal contenedor?

void paint(Graphics g) {
  super.paint(g);
  for (Component child : this.subComponents)
    child.paint(g);
}

Aquí, el contenedor no necesita conocer de antemano los tipos exactos de los subcomponentes; siempre que se ajusten a la Componentinterfaz, el contenedor simplemente puede llamar al polimórficopaint() método . Esto me da la libertad de extender la jerarquía de clases AWT con nuevos componentes arbitrarios.

Hay muchos problemas recurrentes a lo largo del desarrollo de software que se pueden resolver aplicando el polimorfismo como técnica. Estos pares recurrentes de problemas y soluciones se denominan patrones de diseño , y algunos de ellos se recopilan en el libro del mismo nombre. En los términos de ese libro, mi modelo de aprendizaje automático inyectado sería una estrategia que utilizo para "definir una familia de algoritmos, encapsular cada uno y hacerlos intercambiables". El ejemplo de Java-AWT donde un componente puede contener subcomponentes es un ejemplo de un compuesto .

Pero no todos los diseños necesitan usar polimorfismo (más allá de habilitar la inyección de dependencia para las pruebas unitarias, que es un caso de uso realmente bueno). La mayoría de los problemas son muy estáticos. Como consecuencia, las clases y los métodos a menudo no se usan para el polimorfismo, sino simplemente como espacios de nombres convenientes y para la sintaxis de llamada del método bonito. Por ejemplo, muchos desarrolladores prefieren llamadas a métodos como account.getBalance()sobre una llamada de función en gran medida equivalente Account_getBalance(account). Ese es un enfoque perfectamente bueno, es solo que muchas llamadas de "método" no tienen nada que ver con el polimorfismo.

amon
fuente
6

Usted ve mucha herencia y polimorfismo en la mayoría de los kits de herramientas de IU.

Por ejemplo, en el kit de herramientas de JavaFX UI, Buttonhereda de ButtonBasequé hereda de Labeledqué hereda de Controlqué hereda de Regionqué hereda de Parentqué hereda de Nodequién hereda deObject . Muchas capas anulan algunos métodos de los anteriores.

Cuando desee que aparezca ese botón en la pantalla, entonces lo agrega a un Pane, que puede aceptar cualquier cosa heredada deNode niño. Pero, ¿cómo sabe un Panel qué hacer con un Botón cuando solo lo ve como un objeto Nodo genérico? Ese objeto podría ser cualquier cosa. El panel puede hacer eso porque el Botón redefine los métodos de Nodo con cualquier lógica específica de botón. El panel solo llama a los métodos definidos en Node y deja el resto al objeto mismo. Este es un ejemplo perfecto de polimorfismo aplicado.

Los juegos de herramientas de UI tienen una importancia muy alta en el mundo real, haciéndolos útiles para enseñar por razones académicas y prácticas.

Sin embargo, los kits de herramientas de IU también tienen un inconveniente significativo: tienden a ser enormes . Cuando un ingeniero de software neófito intenta comprender el funcionamiento interno de un marco de interfaz de usuario común, a menudo se encuentra con más de cien clases , la mayoría de ellas con fines muy esotéricos. "¿Qué diablos es un ReadOnlyJavaBeanLongPropertyBuilder? ¿Es importante? ¿Tengo tengo que entender lo que es bueno para?" Los principiantes pueden perderse fácilmente en esa madriguera de conejo. Por lo tanto, pueden huir aterrorizados o quedarse en la superficie donde simplemente aprenden la sintaxis y tratan de no pensar demasiado en lo que realmente está sucediendo bajo el capó.

Philipp
fuente
3

Aunque ya hay buenos ejemplos aquí, otro es reemplazar animales con dispositivos:

  • Devicepuede ser powerOn(), powerOff(), setSleep()y la lata getSerialNumber().
  • SensorDevicepuede hacer todo esto, y proporcionar funciones polimórficas, tales como getMeasuredDimension(), getMeasure(), alertAt(threashhold)y autoTest().
  • por supuesto, getMeasure()no se implementará de la misma manera para un sensor de temperatura, un detector de luz, un detector de sonido o un sensor volumétrico. Y, por supuesto, cada uno de estos sensores más especializados puede tener algunas funciones adicionales disponibles.
Christophe
fuente
2

La presentación es una aplicación muy común, quizás la más común sea ToString (). Que es básicamente Animal.Speak (): le dices a un objeto que se manifieste.

En términos más generales, le dices a un objeto que "haga lo suyo". Piense en Guardar, Cargar, Inicializar, Eliminar, ProcessData, GetStatus.

Martin Maat
fuente
2

Mi primer uso práctico del polimorfismo fue una implementación de Heap en Java.

Tuve una clase base con implementación de métodos insert, removeTop, donde la diferencia entre Heap max y min solo sería cómo funciona la comparación de métodos.

abstract class Heap {  

 abstract boolean compare ( int x , int y );

 boolean insert(int x ) { ... }

 int removeTop() { ... }
}

Entonces, cuando quería tener MaxHeap y MinHeap, podía usar la herencia.

class MaxHeap extends Heap {

   MaxHeap(int maxSize) {super(maxSize);}

   @Override
   boolean compare(int x, int y) {
       return x>y; // x<y for minHeap
   }
}
Bazil
fuente
1

Aquí hay un escenario de la vida real para el polimorfismo de la tabla de base de datos / aplicación web :

Utilizo Ruby on Rails para desarrollar aplicaciones web, y una cosa que muchos de mis proyectos tienen en común es la capacidad de cargar archivos (fotos, PDF, etc.). Entonces, por ejemplo, un Userpuede tener múltiples imágenes de perfil, y Producttambién puede tener muchas imágenes de productos. Ambos tienen el comportamiento de la carga y el almacenamiento de imágenes, así como el cambio de tamaño, la generación de imágenes en miniatura, etc. Con el fin de mantenerse seco y compartir el comportamiento de Picture, queremos hacer Picturede modo polimórfico que puede pertenecer a ambos Usery Product.

En Rails diseñaría mis modelos como tales:

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class User < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end

y una migración de base de datos para crear la picturestabla:

class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end

    add_index :pictures, [:imageable_type, :imageable_id]
  end
end

Las columnas imageable_idy imageable_typeson utilizadas por Rails internamente. Básicamente, imageable_typemantiene el nombre de la clase ( "User", "Product", etc.), y imageable_ides el ID del registro asociado. Entonces, imageable_type = "User"y imageable_id = 1sería el registro en la userstabla con id = 1.

Esto nos permite hacer cosas como user.picturesacceder a las imágenes del usuario, así como product.picturesobtener las imágenes de un producto. Luego, todo el comportamiento relacionado con la imagen se encapsula en la Photoclase (y no en una clase separada para cada modelo que necesita fotos), por lo que las cosas se mantienen SECAS.

Más lecturas: Rails asociaciones polimórficas .

Chris Cirefice
fuente
0

Hay muchos algoritmos de clasificación disponibles, como clasificación de burbujas, clasificación de inserción, clasificación rápida, clasificación de montón, etc.

El cliente proporcionado con la interfaz de clasificación solo se preocupa por proporcionar la matriz como entrada y luego recibe la matriz ordenada. Durante el tiempo de ejecución, dependiendo de ciertos factores, se puede usar la implementación de clasificación adecuada. Este es uno de los ejemplos del mundo real de dónde se usa el polimorfismo.

Lo que describí anteriormente es un ejemplo de polimorfismo en tiempo de ejecución, mientras que la sobrecarga de métodos es un ejemplo de polimorfosis en tiempo de compilación donde el cumplimiento depende de los tipos de parámetros i / p y o / p y el número de parámetros vincula al llamador con el método correcto en el tiempo de compilación en sí.

Espero que esto aclare.

rahulaga_dev
fuente