xmldsig xades libre hacienda firmar epes ejemplo crlibre api_hacienda oop polymorphism

oop - libre - xades epes python



¿Cómo el polimorfismo hace que mi código sea más flexible? (11)

Estoy leyendo Head First Object Oriented Design para comprender mejor los conceptos de OOP.

El polimorfismo se explica como:

Airplane plane = new Airplane(); Airplane plane = new Jet(); Airplane plane = new Rocket();

Puede escribir código que funcione en la superclase, como un avión, pero que funcionará con cualquiera de las subclases. : - * Hmmm ... obtuve este. * .

Explica además:

-> Entonces, ¿cómo el polimorfismo hace que el código sea flexible?

Bueno, si necesita una nueva funcionalidad, podría escribir una nueva subclase de AirPlane. Pero como su código usa la superclase, su nueva clase funcionará sin ningún cambio en el resto de su código.

Ahora no lo estoy entendiendo Necesito crear una subclase de un avión. Por ejemplo: creo una clase, Randomflyer . Para usarlo tendré que crear su objeto. Entonces usaré:

Airplane plane = new Randomflyer();

No lo entiendo Incluso yo habría creado un objeto de una subclase directamente. Aún así, no veo la necesidad de cambiar mi código en ningún lado cuando agregue una nueva subclase. ¿Cómo me ahorra el uso de una superclase para hacer cambios adicionales al resto de mi código?


Digamos que tiene lo siguiente (simplificado):

Airplane plane = new MyAirplane();

Entonces haces todo tipo de cosas con él:

List<Airplane> formation = ... // superclass is important especially if working with collections formation.add(plane); // ... plane.flyStraight(); plane.crashTest(); // ... insert some other thousand lines of code that use plane

Cosa es. Cuando de repente decides cambiar tu avión a

Airplane plane = new PterdodactylSuperJet();

todo el otro código que escribí arriba simplemente funcionará (de manera diferente, por supuesto) porque el otro código depende de la interfaz (léase: métodos públicos) proporcionada por la clase general de Airplane , y no de la implementación real que usted brinda al principio. De esta manera, puede pasar diferentes implementaciones sin alterar su otro código.

Si no hubiera usado una superclase de Airplane y acaba de escribir MyAirplane y PterdodactylSuperJet en el sentido de que reemplaza

MyAriplane plane = new MyAirplane();

con

PterdodactylSuperJet plane = new PterdodactylSuperJet();

entonces tienes un punto: el resto de tu código aún puede funcionar. Pero eso simplemente funciona , porque usted escribió la misma interfaz (métodos públicos) en ambas clases, a propósito . Si usted (o algún otro desarrollador) cambiara la interfaz en una clase, moverse hacia atrás y hacia adelante entre las clases de avión hará que su código no se pueda usar.

Editar

A propósito me refiero a que implemente específicamente métodos con las mismas firmas tanto en MyAirplane como en PterodactylSuperJet para que su código se ejecute correctamente con ambos. Si usted u otra persona cambian la interfaz de una clase, su flexibilidad se rompe.

Ejemplo. Digamos que no tienes la superclase Airplane y otro desarrollador desprevenido modifica el método

public void flyStraight()

en MyAirplane a

public void flyStraight (int speed)

y suponga que su variable de plane es del tipo MyAirplane . Entonces el gran código necesitaría algunas modificaciones; supongamos que es necesario de todos modos. La cosa es que, si regresa a un PterodactylSuperJet (por ejemplo, para probarlo, compararlo, una plétora de razones), su código no se ejecutará. Whygodwhy . Debido a que necesita proporcionar PterodactylSuperJet con el método flyStraight(int speed) que no escribió. Puedes hacer eso, puedes reparar, está bien.

Ese es un escenario fácil. Pero que si

  • Este problema te muerde el culo un año después de la modificación inocente? Incluso podrías olvidar por qué hiciste eso en primer lugar.
  • No una, sino una tonelada de modificaciones que se han producido y que no se pueden seguir. Incluso si puede realizar un seguimiento, debe obtener la nueva clase al día. Casi nunca es fácil y definitivamente nunca es agradable.
  • En lugar de dos clases de avión, ¿tienes cien?
  • ¿Alguna combinación lineal (o no) de la anterior?

Si hubiera escrito una superclase de Airplane e hiciera que cada subclase anulara sus métodos relevantes, entonces al cambiar flyStraight() a flyStraight(int) en Airplane , se vería obligado a adaptar todas las subclases en consecuencia, manteniendo así la consistencia . Por lo tanto, la flexibilidad no se verá alterada.

Finalizar edición

Es por eso que una superclase se mantiene como una especie de "papá" en el sentido de que si alguien modifica su interfaz, todas las subclases seguirán, por lo tanto, su código será más flexible.


El polimorfismo adquiere propiedades y todos los comportamientos e interfaces de la superclase. Entonces, ¿el comportamiento de un avión es realmente el mismo que el de un avión?


El polimorfismo es poderoso dado que cuando hay una necesidad de cambiar un comportamiento, puedes cambiarlo anulando un método.

Su superclase hereda sus propiedades y comportamientos a sus subclases extendidas por ella. Por lo tanto, es seguro lanzar implícitamente un objeto cuyo tipo también sea de su superclase. Esos métodos comunes a sus subclases los hacen útiles para implementar una API. Con eso, el polimorfismo te brinda la capacidad de extender una funcionalidad de tu código.


El polimorfismo proviene de la herencia. La idea es que tienes una clase base general y clases derivadas más específicas. A continuación, puede escribir código que funcione con la clase base ... y polymorphims hace que su código no solo funcione con la clase base, sino con todas las clases derivadas.

Si decide que su getPlaneEngineType() tenga un método, diga getPlaneEngineType() y getPlaneEngineType() una nueva clase secundaria "Jet que hereda de Plane" . Plane jet = new Jet() seguirá / puede acceder al getPlaneEngineType de la superclase. Si bien aún puedes escribir tu propio getJetEngineType() para anular básicamente el método de la superclase con una súper llamada, esto significa que puedes escribir código que funcionará con CUALQUIER "avión", no solo con Plane o Jet o BigFlyer.


No creo que sea un buen ejemplo, ya que parece confundir la ontología y el polimorfismo.

Tienes que preguntarte, ¿qué aspecto del comportamiento de un ''Jet'' es diferente de un ''Avión'' que justificaría complicar el software para modelarlo con un subtipo diferente? La vista previa del libro se corta después de una página en el ejemplo, pero no parece ninguna justificación para el diseño. Siempre pregúntese si existe una diferencia en el comportamiento en lugar de simplemente agregar clases para categorizar las cosas; por lo general, es mejor hacerlo con un valor de propiedad o estrategias de composición que con las subclases.

Un ejemplo (simplificado de un proyecto importante que llevo a principios de la década de los noventa) sería que un avión es final pero tiene varias propiedades de tipos abstractos, uno de los cuales es el motor. Hay varias formas de calcular el empuje y el uso de combustible de un motor: para jets rápidos, tabla de interpolación bicúbica de valores de empuje y tasa de combustible contra Mach y acelerador (y presión y humedad a veces), para Rockets el método de mesa pero no requiere compensación por estancar el aire en la admisión del motor; para los accesorios, se puede usar una ecuación de arranque más simple parametrizada. Por lo tanto, tendría tres clases de AbstractAeroEngine - JetEngine, RocketEngine y BootstrapEngine que tendrían implementaciones de métodos que devolvieran el empuje y la tasa de uso de combustible con un ajuste de aceleración y el número de Mach actual. (casi nunca debe subtipo de un tipo no abstracto)

Tenga en cuenta que las diferencias entre los tipos de AbstractAeroEngine, aunque relacionadas con los diferentes motores del mundo real, son completamente diferentes en la forma en que el software calcula el empuje y el consumo de combustible del motor; no está construyendo una ontología de clases que describa una vista de lo real mundo, pero especializando las operaciones realizadas en el software para adaptarse a casos de uso específicos.

¿Cómo me ahorra el uso de una superclase para hacer cambios adicionales al resto de mi código?

Como todos los cálculos de tu motor son polimórficos, significa que cuando creas un avión, puedes usar el cálculo de empuje del motor adecuado. Si encuentra que tiene que ofrecer otro método para calcular el impulso (como lo hicimos varias veces), puede agregar otro subtipo de AeroEngine, siempre que la implementación que proporciona proporcione la confianza y la tasa de combustible, luego el resto del sistema no se preocupa por las diferencias internas; la clase de avión aún pedirá su motor para el empuje. El avión solo se preocupa porque tiene un motor que puede usar de la misma manera que cualquier otro motor, solo el código de creación tiene que saber el tipo de motor que se atornillará, y la implementación de ScramJetEngine solo se preocupa por los cálculos de supersónicos. las partes del avión que calculan elevación y arrastre, y la estrategia para volarlo no tiene que cambiar.


Otras personas han abordado más completamente sus preguntas sobre el polimorfismo en general, pero quiero responder a una pieza específica:

No lo estoy obteniendo, incluso habría creado un objeto de subclases directamente.

Esto es realmente un gran problema, y ​​la gente hace un gran esfuerzo para evitar hacer esto. Si abres algo como The Gang of Four , hay un montón de patrones dedicados a evitar solo este problema.

El enfoque principal se llama patrón de fábrica. Eso se ve así:

AirplaneFactory factory = new AirplaneFactory(); Airplane planeOne = factory.buildAirplane(); Airplane planeTwo = factory.buildJet(); Airplane planeThree = factory.buildRocket();

Esto le da más flexibilidad al abstraer la instanciación del objeto. Es posible que imagine una situación como esta: su empresa comienza principalmente a construir Jet , por lo que su fábrica tiene un método buildDefault() que se ve así:

public Airplane buildDefault() { return new Jet(); }

Un día, su jefe se acerca a usted y le dice que el negocio ha cambiado. Lo que la gente realmente quiere en estos días son los Rocket s - Jet son una cosa del pasado.

Sin AirplaneFactory , tendría que revisar su código y reemplazar posiblemente docenas de llamadas al new Jet() con el new Rocket() . Con el patrón Factory, puedes hacer un cambio como:

public Airplane buildDefault() { return new Rocket(); }

y entonces el alcance del cambio se reduce drásticamente. Y como ha estado codificando la interfaz de Airplane lugar del tipo concreto Jet o Rocket , este es el único cambio que necesita realizar .


Por lo que yo entiendo, la ventaja es que, por ejemplo, en un juego de combate de aviones, tienes que actualizar todas las posiciones de los aviones en cada ciclo, pero tienes varios aviones diferentes. Digamos que tienes:

  • MiG-21
  • Waco 10
  • Mitsubishi Zero
  • Eclipse 500
  • Espejismo

No desea tener que actualizar sus movimientos y posiciones en forma separada como esta:

Mig21 mig = new Mig21(); mig.move(); Waco waco = new Waco(); waco.move(); Mitsubishi mit = new Mitsubishi(); mit.move(); ...

Desea tener una superclase que pueda tomar cualquiera de estas subclases (Avión) y actualizar todo en un bucle:

airplaneList.append(new Mig21()); airplaneList.append(new Waco()); airplaneList.append(new Mitsubishi()); ... for(Airplane airplane : airplanesList) airplane.move()

Esto hace que tu código sea mucho más simple.


Supongamos que tiene métodos en su clase de controladores de aviones como

parkPlane(Airplane plane)

y

servicePlane(Airplane plane)

implementado en tu programa. No BREAK tu código. Es decir, no es necesario que cambie siempre que acepte argumentos como AirPlane .

Porque aceptará cualquier Avión a pesar del tipo real, flyer , highflyr , fighter , etc.

Además, en una colección:

List<Airplane> plane; // Tomará todos tus aviones.

El siguiente ejemplo aclarará tu comprensión.

interface Airplane{ parkPlane(); servicePlane(); }

Ahora tienes un avión de combate que lo implementa, entonces

public class Fighter implements Airplane { public void parkPlane(){ // Specific implementations for fighter plane to park } public void servicePlane(){ // Specific implementatoins for fighter plane to service. } }

Lo mismo para HighFlyer y otros clasess:

public class HighFlyer implements Airplane { public void parkPlane(){ // Specific implementations for HighFlyer plane to park } public void servicePlane(){ // specific implementatoins for HighFlyer plane to service. } }

Ahora piensa en tus clases de controlador usando AirPlane varias veces,

Supongamos que su clase de controlador es AirPort como a continuación,

public Class AirPort{ AirPlane plane; public AirPlane getAirPlane() { return airPlane; } public void setAirPlane(AirPlane airPlane) { this.airPlane = airPlane; } }

aquí la magia viene cuando el polimorfismo hace que tu código sea más flexible porque,

Puede hacer que AirPlane instancias de su nuevo AirPlane tantas como lo desee y no cambiará

código de la clase AirPort .

puede configurar la instancia de AirPlane como desee (Eso se llama interacción de dependencia también).

JumboJetPlane // implementing AirPlane interface. AirBus // implementing AirPlane interface.

Ahora piense en Si crea un nuevo tipo de avión, o si quita cualquier tipo de avión, ¿hace la diferencia a su AirPort ?

No, porque podemos decir que la clase AirPort refiere al AirPlane polimórfica.


Tiene toda la razón de que las subclases solo son útiles para aquellos que las crean. Esto fue resumido bien por Rich Hickey:

... cualquier clase nueva es en sí misma una isla; inutilizable por cualquier código existente escrito por cualquier persona, en cualquier lugar. Así que considera tirar al bebé con el agua del baño.

Todavía es posible usar un objeto que ha sido instanciado en otro lugar . Como un ejemplo trivial de esto, cualquier método que acepte un argumento de tipo "Objeto" probablemente recibirá una instancia de una subclase.

Sin embargo, hay otro problema, que es mucho más sutil. En general, una subclase (como Jet) no funcionará en lugar de una clase principal (como Avión). Suponer que las subclases son intercambiables con las clases principales es la causa de una gran cantidad de errores.

Esta propiedad de intercambiabilidad se conoce como el Principio de Sustitución de Liskov , y se formuló originalmente como:

Sea q (x) una propiedad demostrable sobre los objetos x de tipo T. Entonces q (y) debería ser demostrable para los objetos y de tipo S donde S es un subtipo de T.

En el contexto de su ejemplo, T es la clase de avión, S es la clase Jet, x son las instancias de avión y y son las instancias de Jet.

Las "propiedades" q son los resultados de los métodos de las instancias, los contenidos de sus propiedades, los resultados de pasarlos a otros operadores o métodos, etc. Podemos pensar que "comprobable" significa "observable"; es decir. no importa si dos objetos se implementan de manera diferente, si no hay diferencia en sus resultados. Del mismo modo, no importa si dos objetos se comportarán de manera diferente después de un ciclo infinito, ya que ese código nunca se puede alcanzar.

Definir Jet como una subclase de Airplane es una cuestión trivial de sintaxis: la declaración de Jet debe contener los tokens de extends Airplane y no debe haber un token final en la declaración de Avión. Es trivial para el compilador comprobar que los objetos obedezcan las reglas de subclasificación. Sin embargo, esto no nos dice si Jet es un subtipo de Avión; es decir. si se puede usar un Jet en lugar de un avión. Java lo permitirá, pero eso no significa que funcionará.

Una forma en que podemos hacer que Jet sea un subtipo de Avión es que Jet sea una clase vacía; todo su comportamiento proviene de Airplane. Sin embargo, incluso esta solución trivial es problemática: un avión y un avión trivial se comportarán de manera diferente cuando pasen al operador de instanceof . Por lo tanto, debemos inspeccionar todo el código que utiliza Airplane para asegurarnos de que no haya ninguna instanceof llamadas. Por supuesto, esto va completamente en contra de las ideas de encapsulación y modularidad; ¡no hay manera de que podamos inspeccionar el código que quizás ni siquiera existe!

Normalmente queremos subclasificar para hacer algo diferente a la superclase. En este caso, debemos asegurarnos de que ninguna de estas diferencias sea observable en ningún código que utilice Avión. Esto es incluso más difícil que verificar sintácticamente por instanceof ; necesitamos saber qué hace todo ese código.

Eso es imposible debido al Teorema de Rice, por lo tanto, no hay forma de verificar el subtipado automáticamente y, por lo tanto, la cantidad de errores que causa.

Por estas razones, muchos ven el polimorfismo subclase como un antipatrón. Sin embargo, existen otras formas de polimorfismo que no sufren estos problemas, por ejemplo, "polimorfismo paramétrico" (denominado "genéricos" en Java).

Principio de sustitución de Liskov

Comparación entre subclasificación y subtipificación

Polimorfismo paramétrico

Argumentos contra la subclasificación

Teorema de Rice


Un buen ejemplo de cuando el polimorfismo es útil:

Digamos que tiene la clase abstracta Animal , que define métodos comunes a todos los animales, como makeNoise()

Luego puede extenderlo con subclases como Dog , Cat , Tiger .

Cada uno de estos animales anula los métodos de la clase abstracta, como makeNoise() , para hacer que estos comportamientos sean específicos de su clase. Esto es bueno porque obvio que cada animal hace un ruido diferente.

Aquí hay un ejemplo donde el polimorfismo es una gran cosa: colecciones.

Digamos que tengo un ArrayList<Animal> animals , y está lleno de varios animales diferentes.

El polimorfismo hace posible este código:

for(Animal a: animals) { a.makeNoise(); }

Como sabemos que cada subclase tiene un método makeNoise() , podemos confiar en que esto hará que cada objeto animal llame a su versión específica de makeNoise() ( por ejemplo, el perro ladra, el gato maúlla, el moco de la vaca, todo sin ti nunca incluso tener que preocuparse por qué animal hace qué ) .

Otra ventaja es evidente cuando se trabaja con un equipo en un proyecto. Digamos que otro desarrollador agregó varios animales nuevos sin decírtelo nunca, y tienes una colección de animales que ahora tiene algunos de estos nuevos tipos de animales (¡que ni siquiera sabes que existen!). Todavía puede llamar al método makeNoise() (o cualquier otro método en la superclase animal) y confiar en que cada tipo de animal sabrá qué hacer.

Lo bueno de esta superclase animal es que puedes extender una superclase y crear tantos nuevos tipos de animales como quieras, sin cambiar NADA en la superclase ni descifrar ningún código.

Recuerde la regla de oro del polimorfismo. Puede usar una subclase en cualquier lugar donde se espere un objeto tipo superclase.

Por ejemplo:

Animal animal = new Dog;

Lleva un tiempo aprender a pensar de forma polimórfica, pero una vez que aprende, su código mejorará mucho.


Un caso de uso muy simple para demostrar el beneficio del polimorfismo es el procesamiento por lotes de una lista de objetos sin realmente preocuparse por su tipo (es decir, delegar esta responsabilidad a cada tipo concreto). Esto ayuda a realizar operaciones abstractas consistentemente en una colección de objetos.

Supongamos que desea implementar un programa de vuelo simulado, donde desea volar todos y cada uno de los tipos de avión que están presentes en su lista. Simplemente llama

for (AirPlane p : airPlanes) { p.fly(); }

Cada avión sabe cómo volar y no es necesario preocuparse por el tipo de aviones al hacer esta llamada. Esta uniformidad en el comportamiento de los objetos es lo que te proporciona el polimorfismo.