Como implementar a herança RealNumber e ComplexNumber?


11

Espero que não seja muito acadêmico ...

Digamos que eu precise de números reais e complexos na minha biblioteca de SW.

Com base no relacionamento is-a (ou aqui ), o número real é um número complexo, onde b na parte imaginária do número complexo é simplesmente 0.

Por outro lado, minha implementação seria que esse filho estende o pai, portanto, no pai RealNumber, eu teria uma parte real e o filho ComplexNumber acrescentaria arte imaginária.

Também há uma opinião de que herança é má .

Lembro-me como ontem, quando eu estava aprendendo POO na universidade, disse meu professor, este não é um bom exemplo de herança, já que o valor absoluto desses dois é calculado de maneira diferente (mas para isso temos método de sobrecarga / polimorfismo, certo?) .. .

Minha experiência é que muitas vezes usamos herança para resolver DRY, como resultado, muitas vezes temos classes abstratas artificiais na hierarquia (muitas vezes temos problemas para encontrar nomes, pois eles não representam objetos do mundo real).


7
isto parece coberto na pergunta anterior: o retângulo deve herdar do quadrado?
mosquito

1
@gnat Oh cara, esse foi outro exemplo que eu queria usar ... Obrigado!
Betlista 9/01/19

7
... Observe que a frase "número real é um número complexo" no sentido matemático é válida apenas para números imutáveis , portanto, se você usar objetos imutáveis, poderá evitar a violação do LSP (o mesmo vale também para quadrados e retângulos, veja isto Resposta SO ).
Doc Brown

5
... Observe ainda que o cálculo do valor absoluto para números complexos também funciona para números reais, por isso não tenho certeza do que seu professor quis dizer. Se você implementar um método "Abs ()" corretamente em um número complexo imutável e derivar um "real" dele, o método Abs () ainda fornecerá resultados corretos.
Doc Brown

Respostas:


17

Mesmo que, em um sentido matemático, um número real seja um número complexo, não é uma boa idéia derivar real do complexo. Ele viola o Princípio de Substituição de Liskov dizendo (entre outras coisas) que uma classe derivada não deve ocultar propriedades de uma classe base.

Nesse caso, um número real teria que ocultar a parte imaginária do número complexo. É claro que não faz sentido armazenar um número de ponto flutuante oculto (parte imaginária) se você precisar apenas da parte real.

Esse é basicamente o mesmo problema que o exemplo de retângulo / quadrado mencionado em um comentário.


2
Hoje eu vi esse "Princípio da Substituição de Liskow" várias vezes, vou ter que ler mais sobre isso, porque não sei disso.
Betlista 9/01/19

7
É perfeitamente bom relatar a parte imaginária de um número real como zero, por exemplo, através de um método somente leitura. Mas não faz sentido implementar um real como um número complexo em que a parte imaginária está definida como zero. Este é exatamente um caso em que a herança é enganosa: embora a herança da interface esteja bem aqui, a herança da implementação resultaria em um design problemático.
amon

4
Faz todo o sentido ter números reais herdados de números complexos, desde que ambos sejam imutáveis. E você não se importa com a sobrecarga.
Deduplicator

@ Deduplicator: ponto interessante. A imutabilidade resolve muitos problemas, mas ainda não estou totalmente convencido. Tem que pensar sobre isso.
Frank soprador

3

não é um bom exemplo de herança, pois o valor absoluto desses dois é calculado de maneira diferente

Esta não é realmente uma razão convincente contra todas as heranças aqui, apenas o modelo class RealNumber<-> proposto class ComplexNumber.

Você pode razoavelmente definir uma interface Number, que tanto RealNumber e ComplexNumberiria implementar.

Isso pode parecer

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

Mas, então, você deseja restringir os outros Numberparâmetros nessas operações para serem do mesmo tipo derivado this, com o qual você pode se aproximar

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

Ou então, você usaria uma linguagem que permitisse polimorfismo estrutural, em vez de polimorfismo de subtipo. Para o caso específico de números, talvez você precise da capacidade de sobrecarregar 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

0

Solução: Não tem RealNumberclasse pública

Eu acharia totalmente aceitável se ComplexNumbertivesse um método estático de fábrica fromDouble(double)que retornasse um número complexo com o imaginário sendo zero. Você pode usar todas as operações que usaria em uma RealNumberinstância nessa ComplexNumberinstância.

Mas tenho problemas para entender por que você deseja / precisa ter uma RealNumberclasse pública herdada . Geralmente, a herança é usada por esses motivos (fora da minha cabeça, me corrija se faltou alguma)

  • estendendo o comportamento. RealNumbersnão pode fazer operações extras que um número complexo não possa fazer, portanto, não faz sentido.

  • implementar comportamento abstrato com uma implementação específica. Como ComplexNumbernão deve ser abstrato, isso também não se aplica.

  • reutilização de código. Se você apenas usa a ComplexNumberclasse, reutiliza 100% do código.

  • implementação mais específica / eficiente / precisa para uma tarefa específica. Isso poderia ser aplicado aqui, RealNumberspoderia implementar algumas funcionalidades mais rapidamente. Mas essa subclasse deve estar oculta por trás da estática fromDouble(double)e não deve ser conhecida do lado de fora. Dessa forma, não seria necessário ocultar a parte imaginária. Para o exterior, deve haver apenas números complexos (quais são os números reais). Você também pode retornar essa classe RealNumber privada de qualquer operação na classe numérica complexa que resulte em um número real. (Isso pressupõe que as classes são imutáveis ​​como a maioria das classes numéricas.)

É como implementar uma subclasse de Inteiro chamada Zero e codificar algumas das operações, pois elas são triviais para zero. Você pode fazer isso, pois todo zero é um número inteiro, mas não o torne público, oculte-o atrás de um método de fábrica.


Não estou surpreso ao receber um voto negativo, pois não tenho fonte para provar. Além disso, se ninguém mais teve uma ideia, sempre suspeito que possa haver alguma razão para isso. Mas, por favor, diga-me por que você acha que está errado e como você o tornaria melhor.
findusl

0

Dizer que um número real é um número complexo tem mais significado na matemática, especialmente na teoria dos conjuntos, do que na ciência da computação.
Em matemática, dizemos:

  • Um número real é um número complexo porque o conjunto de números complexos inclui o conjunto de números reais.
  • Um número racional é um número real porque o conjunto de números reais inclui o conjunto de números racionais (e o conjunto de números irracionais).
  • Um número inteiro é um número racional porque o conjunto de números racionais inclui o conjunto de números inteiros.

No entanto, isso não significa que você deva usar a herança ao projetar sua biblioteca para incluir uma classe RealNumber e ComplexNumber. Em Java eficaz, segunda edição por Joshua Bloch; O item 16 é "Favorecer a composição sobre a herança". Para evitar os problemas mencionados nesse item, depois de definir sua classe RealNumber, ela poderá ser usada na sua classe ComplexNumber:

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

    // Implementation details are for you to write
}

Isso permite que você reutilize sua classe RealNumber para manter seu código SECO, evitando os problemas identificados por Joshua Bloch.


0

Há duas questões aqui. A primeira é que é comum usar os mesmos termos para os tipos de contêineres e os tipos de seu conteúdo, especialmente com tipos primitivos como números. O termo double, por exemplo, é usado para descrever um valor de ponto flutuante de precisão dupla e um contêiner no qual um pode ser armazenado.

A segunda questão é que, embora seja - um relacionamento entre contêineres a partir do qual vários tipos de objetos podem ser lidos se comporte da mesma maneira que os relacionamentos entre os próprios objetos, aqueles entre contêineres nos quais vários tipos de objetos podem ser colocados se comportam opostos aos de seu conteúdo . Toda gaiola que é conhecida por conter uma instância de Catserá uma gaiola que contém uma instância de Animal, mas não precisa ser uma gaiola que contém uma instância de SiameseCat. Por outro lado, toda gaiola que pode conter todas as instâncias de Catserá uma gaiola que pode conter todas as instâncias de SiameseCat, mas não precisa ser uma gaiola que possa conter todas as instâncias de Animal. O único tipo de gaiola que pode conter todas as instâncias Cate pode ser garantido nunca contém outra coisa senão uma instância deCat, é uma gaiola de Cat. Qualquer outro tipo de gaiola seria incapaz de aceitar algumas instâncias Catdaquilo que deveria aceitar ou seria capaz de aceitar coisas que não são instâncias Cat.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.