¿Cómo implementar la herencia RealNumber y ComplexNumber?

11

Esperemos que no sea demasiado académico ...

Digamos que necesito números reales y complejos en mi biblioteca SW.

Basado en la relación is-a (o aquí ), el número real es un número complejo, donde b en la parte imaginaria del número complejo es simplemente 0.

Por otro lado, mi implementación sería que ese hijo extiende al padre, por lo que en el padre RealNumber tendría una parte real y el niño ComplexNumber agregaría arte imaginario.

También hay una opinión, que la herencia es malvada .

Recuerdo que ayer, cuando estaba aprendiendo OOP en la universidad, mi profesor dijo que este no es un buen ejemplo de herencia ya que el valor absoluto de esos dos se calcula de manera diferente (pero para eso tenemos una sobrecarga de métodos / polimorfismo, ¿verdad?). .

Mi experiencia es que a menudo usamos la herencia para resolver DRY, como resultado, a menudo tenemos clases abstractas artificiales en la jerarquía (a menudo tenemos problemas para encontrar nombres porque no representan objetos de un mundo real).

Betlista
fuente
77
esto parece cubierto en la pregunta anterior: ¿El rectángulo debe heredar del cuadrado?
mosquito el
1
@gnat Oh hombre, ese fue otro ejemplo que quería usar ... ¡Gracias!
Betlista el
77
... Tenga en cuenta que la oración "el número real es un número complejo" en el sentido matemático solo es válido para números inmutables , por lo que si usa objetos inmutables, puede evitar la violación de LSP (lo mismo ocurre con los cuadrados y rectángulos, vea esto SO respuesta ).
Doc Brown
55
... Tenga en cuenta además que el cálculo del valor absoluto para números complejos también funciona para números reales, por lo que no estoy seguro de lo que quiso decir su profesor. Si implementa un método "Abs ()" correctamente en un número complejo inmutable y deriva un "real" de él, el método Abs () aún entregará resultados correctos.
Doc Brown
3
Posible duplicado de ¿Debería el rectángulo heredar del cuadrado?
BobDalgleish el

Respuestas:

17

Incluso si en un sentido matemático, un número real es un número complejo, no es una buena idea derivar real de complejo. Viola el Principio de sustitución de Liskov diciendo (entre otras cosas) que una clase derivada no debe ocultar las propiedades de una clase base.

En este caso, un número real tendría que ocultar la parte imaginaria del número complejo. Está claro que no tiene sentido almacenar un número de punto flotante oculto (parte imaginaria) si solo necesita la parte real.

Este es básicamente el mismo problema que el ejemplo de rectángulo / cuadrado mencionado en un comentario.

Frank Puffer
fuente
2
Hoy vi este "Principio de sustitución de Liskow" varias veces, tendré que leer más al respecto, porque no lo sé.
Betlista el
77
Está perfectamente bien informar la parte imaginaria de un número real como cero, por ejemplo, a través de un método de solo lectura. Pero no tiene sentido implementar un número real como complejo donde la parte imaginaria se establece en cero. Este es exactamente un caso en el que la herencia es engañosa: si bien la herencia de la interfaz podría estar bien aquí, la herencia de la implementación daría como resultado un diseño problemático.
amon
44
Tiene mucho sentido tener números reales heredados de números complejos, siempre que ambos sean inmutables. Y no te importa la sobrecarga.
Deduplicador el
@Deduplicator: punto interesante. La inmutabilidad resuelve muchos problemas, pero todavía no estoy completamente convencido en este caso. Tengo que pensarlo.
Frank Puffer
3

no es un buen ejemplo de herencia ya que el valor absoluto de esos dos se calcula de manera diferente

Esto no es realmente una razón convincente contra toda herencia aquí, solo el modelo class RealNumber<-> propuesto class ComplexNumber.

Es posible definir una interfaz razonablemente Number, que tanto RealNumber y ComplexNumberllevaría a cabo.

Eso podría parecer

interface Number
{
    Number Add(Number rhs);
    Number Subtract(Number rhs);
    // ... etc
}

Pero luego querría restringir los otros Numberparámetros en estas operaciones para que sean del mismo tipo derivado que this, al que puede acercarse con

interface Number<T>
{
    Number<T> Add(Number<T> rhs);
    Number<T> Subtract(Number<T> rhs);
    // ... etc
}

O, en cambio, usaría un lenguaje que permitiera el polimorfismo estructural, en lugar del subtipo de polimorfismo. Para el caso específico de los números, es posible que solo necesite la capacidad de sobrecargar operadores aritméticos.

complex operator + (complex lhs, complex rhs);
complex operator - (complex lhs, complex rhs);
// ... etc

Number frobnicate<Number>(List<Number> foos, Number bar); // uses arithmetic operations
Caleth
fuente
0

Solución: no tener una RealNumberclase pública

Me resultaría totalmente correcto si ComplexNumbertuviera un método de fábrica estático fromDouble(double)que devolvería un número complejo con imaginario cero. Luego puede usar todas las operaciones que usaría en una RealNumberinstancia en esta ComplexNumberinstancia.

Pero tengo problemas para ver por qué querrías / necesitarías tener una RealNumberclase pública heredada . Por lo general, la herencia se usa por estas razones (fuera de mi cabeza, corríjame si se perdió alguna)

  • extendiendo el comportamiento. RealNumbersno puede hacer ninguna operación adicional que el número complejo no puede hacer, así que no tiene sentido hacerlo.

  • implementando un comportamiento abstracto con una implementación específica. Como ComplexNumberno debe ser abstracto, esto tampoco se aplica.

  • reutilización de código. Si solo usa la ComplexNumberclase, reutiliza el 100% del código.

  • Implementación más específica / eficiente / precisa para una tarea específica. Esto podría aplicarse aquí, RealNumberspodría implementar algunas funcionalidades más rápido. Pero entonces esta subclase debería estar oculta detrás de la estática fromDouble(double)y no debería ser conocida afuera. De esta manera no necesitaría ocultar la parte imaginaria. Para el exterior solo debe haber números complejos (que son los números reales). También puede devolver esta clase privada RealNumber de cualquier operación en la clase de números complejos que dé como resultado un número real. (Esto supone que las clases son inmutables como la mayoría de las clases de números).

Es como implementar una subclase de Integer que se llama Cero y codificar algunas de las operaciones ya que son triviales para cero. Puedes hacer esto, ya que cada cero es un número entero, pero no lo hagas público, escóndelo detrás de un método de fábrica.

findusl
fuente
No me sorprende recibir un voto negativo, ya que no tengo fuente para probar. Además, si nadie más tenía una idea, siempre sospecho que podría haber alguna razón para eso. Pero por favor dígame por qué cree que está mal y cómo lo mejoraría.
findusl
0

Decir que un número real es un número complejo tiene más significado en matemáticas, especialmente en teoría de conjuntos, que en informática.
En matemáticas decimos:

  • Un número real es un número complejo porque el conjunto de números complejos incluye el conjunto de números reales.
  • Un número racional es un número real porque el conjunto de números reales incluye el conjunto de números racionales (y el conjunto de números irracionales).
  • Un número entero es un número racional porque el conjunto de números racionales incluye el conjunto de números enteros.

Sin embargo, esto no significa que deba, o incluso deba, usar la herencia al diseñar su biblioteca para incluir una clase RealNumber y ComplexNumber. En Effective Java, Segunda edición de Joshua Bloch; El artículo 16 es "Composición de favor sobre herencia". Para evitar los problemas mencionados en ese elemento, una vez que haya definido su clase RealNumber, puede usarse en su clase ComplexNumber:

public class ComplexNumber {
    private RealNumber realPart;
    private RealNumber imaginaryPart;

    // Implementation details are for you to write
}

Esto le permite todo el poder de reutilizar su clase RealNumber para mantener su código SECO mientras evita los problemas identificados por Joshua Bloch.

Craig Noah
fuente
0

Hay dos problemas aquí. La primera es que es común usar los mismos términos para los tipos de contenedores y los tipos de sus contenidos, especialmente con tipos primitivos como los números. El término double, por ejemplo, se usa para describir tanto un valor de coma flotante de doble precisión como un contenedor en el que se puede almacenar uno.

La segunda cuestión es que, si bien las relaciones entre contenedores desde las que se pueden leer varios tipos de objetos se comportan de la misma manera que las relaciones entre los objetos mismos, las relaciones entre contenedores en los que se pueden colocar varios tipos de objetos se comportan de manera opuesta a las de su contenido . Cada jaula que se sabe que contiene una instancia de Catserá una jaula que contenga una instancia de Animal, pero no necesita ser una jaula que contenga una instancia de SiameseCat. Por otro lado, cada jaula que puede contener todas las instancias de Catserá una jaula que puede contener todas las instancias de SiameseCat, pero no necesita ser una jaula que pueda contener todas las instancias de Animal. El único tipo de jaula que puede contener todas las instancias Caty puede garantizarse que nunca contenga otra cosa que no sea una instancia deCat, es una jaula de Cat. Cualquier otro tipo de jaula sería incapaz de aceptar algunas instancias de lo Catque debería aceptar, o sería capaz de aceptar cosas que no son instancias Cat.

Super gato
fuente