Come implementare l'eredità RealNumber e ComplexNumber?


11

Spero non troppo accademico ...

Diciamo che ho bisogno di numeri reali e complessi nella mia libreria SW.

Basato sulla relazione is-a (o qui ), il numero reale è un numero complesso, dove b nella parte immaginaria del numero complesso è semplicemente 0.

D'altra parte, la mia implementazione sarebbe che quel bambino estende il genitore, quindi nel genitore RealNumber avrei una parte reale e il figlio ComplexNumber aggiungerebbe arte immaginaria.

Inoltre c'è un'opinione che l' eredità sia malvagia .

Ricordo come ieri, quando stavo imparando OOP all'università, disse il mio professore, questo non è un buon esempio di eredità in quanto il valore assoluto di quei due è calcolato in modo diverso (ma per questo abbiamo un metodo di sovraccarico / polimorfismo, giusto?) .. .

La mia esperienza è che usiamo spesso l'ereditarietà per risolvere il DRY, di conseguenza abbiamo spesso classi astratte artificiali nella gerarchia (spesso abbiamo problemi a trovare nomi perché non rappresentano oggetti di un mondo reale).


7
questo sembra coperto dalla domanda precedente: il rettangolo dovrebbe ereditare dal quadrato?
moscerino del

1
@gnat Oh amico, quello era un altro esempio che volevo usare ... Grazie!
Betlista,

7
... Nota che la frase "il numero reale è un numero complesso" in senso matematico è valida solo per i numeri immutabili , quindi se usi oggetti immutabili, puoi evitare la violazione di LSP (lo stesso vale anche per quadrati e rettangoli, vedi questo SO risposta ).
Doc Brown

5
... Nota inoltre che il calcolo del valore assoluto per i numeri complessi funziona anche per i numeri reali, quindi non sono sicuro del significato del tuo professore. Se si implementa correttamente un metodo "Abs ()" in un numero complesso immutabile e ne deriva un "reale", il metodo Abs () fornirà comunque risultati corretti.
Doc Brown

Risposte:


17

Anche se in senso matematico, un numero reale è un numero complesso, non è una buona idea derivare reale dal complesso. Viola il principio di sostituzione di Liskov dicendo (tra le altre cose) che una classe derivata non dovrebbe nascondere le proprietà di una classe base.

In questo caso un numero reale dovrebbe nascondere la parte immaginaria del numero complesso. È chiaro che non ha senso memorizzare un numero in virgola mobile nascosto (parte immaginaria) se è necessaria solo la parte reale.

Questo è fondamentalmente lo stesso problema dell'esempio rettangolo / quadrato menzionato in un commento.


2
Oggi ho visto questo "Principio di sostituzione di Liskow" più volte, dovrò leggere di più, perché non lo so.
Betlista,

7
È perfettamente corretto riportare la parte immaginaria di un numero reale come zero, ad esempio mediante un metodo di sola lettura. Ma non ha senso implementare un reale come un numero complesso in cui la parte immaginaria è impostata su zero. Questo è esattamente un caso in cui l'ereditarietà è fuorviante: mentre l'ereditarietà dell'interfaccia andrebbe probabilmente bene qui, l'ereditarietà dell'implementazione comporterebbe una progettazione problematica.
amon,

4
Ha perfettamente senso ereditare numeri reali da numeri complessi, purché entrambi siano immutabili. E non ti dispiace il sovraccarico.
Deduplicatore

@Deduplicator: punto interessante. L'immutabilità risolve molti problemi, ma in questo caso non sono ancora completamente convinto. Ci devo pensare.
Frank Puffer,

3

non un buon esempio di eredità in quanto il valore assoluto di questi due viene calcolato in modo diverso

Questo non è in realtà un motivo convincente contro ogni eredità qui, solo il modello class RealNumber<-> class ComplexNumberproposto.

Potresti definire ragionevolmente un'interfaccia Number, che sia RealNumber e ComplexNumberche implementerebbe.

Potrebbe sembrare

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

Ma poi vorresti vincolare gli altri Numberparametri in queste operazioni allo stesso tipo derivato di this, a cui puoi avvicinarti

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

O invece useresti un linguaggio che permettesse il polimorfismo strutturale, invece del polimorfismo del sottotipo. Per il caso specifico dei numeri, potrebbe essere necessaria solo la possibilità di sovraccaricare gli operatori aritmetici.

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

Soluzione: non avere una RealNumberclasse pubblica

Lo troverei totalmente OK se ComplexNumberavesse un metodo factory statico fromDouble(double)che restituirebbe un numero complesso con immaginario essere zero. È quindi possibile utilizzare tutte le operazioni da utilizzare su RealNumberun'istanza in questa ComplexNumberistanza.

Ma ho difficoltà a capire perché vorresti / avresti bisogno di una RealNumberclasse ereditata dal pubblico . Di solito l'ereditarietà viene utilizzata per questi motivi (fuori di testa, correggimi se ne ho perso alcuni)

  • estendere il comportamento. RealNumbersimpossibile eseguire operazioni extra che un numero complesso non può eseguire, quindi non ha senso farlo.

  • attuare comportamenti astratti con un'implementazione specifica. Dal momento ComplexNumberche non dovrebbe essere astratto, anche questo non si applica.

  • riutilizzo del codice. Se usi solo la ComplexNumberclasse, riutilizzi il 100% del codice.

  • implementazione più specifica / efficiente / accurata per un compito specifico. Questo potrebbe essere applicato qui, RealNumberspotrebbe implementare alcune funzionalità più velocemente. Ma allora questa sottoclasse dovrebbe essere nascosta dietro lo statico fromDouble(double)e non dovrebbe essere conosciuta all'esterno. In questo modo non avrebbe bisogno di nascondere la parte immaginaria. Per l'esterno dovrebbero esserci solo numeri complessi (quali sono i numeri reali). È inoltre possibile restituire questa classe privata RealNumber da qualsiasi operazione nella classe numerica complessa che risulta in un numero reale. (Ciò presuppone che le classi siano immutabili come la maggior parte delle classi numeriche.)

È come implementare una sottoclasse di Integer che si chiama Zero e hardcode alcune delle operazioni poiché sono banali per zero. Potresti farlo, poiché ogni zero è un numero intero, ma non renderlo pubblico, nascondilo dietro un metodo factory.


Non sono sorpreso di ottenere qualche voto negativo, poiché non ho alcuna fonte per dimostrarlo. Inoltre, se nessun altro ha avuto un'idea, ho sempre il sospetto che potrebbe esserci qualche motivo per questo. Ma per favore dimmi perché pensi che sia sbagliato e come lo miglioreresti.
findusl

0

Dire che un numero reale è un numero complesso ha più significato in matematica, specialmente nella teoria degli insiemi, rispetto all'informatica.
In matematica diciamo:

  • Un numero reale è un numero complesso perché l'insieme di numeri complessi include l'insieme di numeri reali.
  • Un numero razionale è un numero reale perché l'insieme dei numeri reali include l'insieme dei numeri razionali (e l'insieme dei numeri irrazionali).
  • Un numero intero è un numero razionale perché l'insieme di numeri razionali include l'insieme di numeri interi.

Tuttavia, ciò non significa che è necessario o addirittura utilizzare l'ereditarietà durante la progettazione della libreria per includere una classe RealNumber e ComplexNumber. In Effective Java, Seconda edizione di Joshua Bloch; L'articolo 16 è "Composizione del favore rispetto all'ereditarietà". Per evitare i problemi menzionati in quell'elemento, una volta definita la classe RealNumber, può essere utilizzata nella classe ComplexNumber:

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

    // Implementation details are for you to write
}

Ciò ti consente di riutilizzare la tua classe RealNumber per mantenere il tuo codice DRY, evitando i problemi identificati da Joshua Bloch.


0

Ci sono due problemi qui. Il primo è che è comune usare gli stessi termini per i tipi di contenitori e i tipi del loro contenuto, specialmente con tipi primitivi come i numeri. Il termine double, ad esempio, viene utilizzato per descrivere sia un valore a virgola mobile a precisione doppia sia un contenitore in cui è possibile memorizzarne uno.

Il secondo problema è che mentre le relazioni is-a tra contenitori da cui è possibile leggere vari tipi di oggetti si comportano allo stesso modo delle relazioni tra gli oggetti stessi, quelle tra contenitori in cui è possibile posizionare vari tipi di oggetti si comportano in modo opposto a quello dei loro contenuti . Ogni gabbia che è nota per contenere un'istanza di Catsarà una gabbia che contiene un'istanza di Animal, ma non è necessario che sia una gabbia che contiene un'istanza di SiameseCat. D'altra parte, ogni gabbia che può contenere tutte le istanze di Catsarà una gabbia che può contenere tutte le istanze di SiameseCat, ma non è necessario che sia una gabbia che può contenere tutte le istanze di Animal. L'unico tipo di gabbia che può contenere tutte le istanze Cate che può essere garantito non può contenere altro che un'istanza diCat, è una gabbia di Cat. Qualsiasi altro tipo di gabbia sarebbe incapace di accettare alcuni casi di Catciò che dovrebbe accettare, o sarebbe in grado di accettare cose che non sono esempi di Cat.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.