Il problema dell'ellisse circolare può essere risolto invertendo la relazione?

11

Avere Circle estendere Ellipse rompe Liskov Principio di sottostazione , perché modifica una postcondizione: ovvero, puoi impostare X e Y indipendentemente per disegnare un'ellisse, ma X deve sempre uguagliare Y per cerchi.

Ma non è questo il problema causato dall'avere Circle come sottotipo di un'Ellipse? Non potremmo invertire la relazione?

Quindi Circle è il supertipo - ha un solo metodo setRadius .

Quindi, Ellipse estende Circle aggiungendo setX e setY . Chiamando setRadius su Ellipse si impostano sia X che Y, il che significa che la post-condizione su setRadius viene mantenuta, ma ora puoi impostare X e Y in modo indipendente attraverso un'interfaccia estesa.

    
posta HorusKol 04.04.2016 - 07:21
fonte

6 risposte

35

But isn't the problem here caused by having Circle be the subtype of an Ellipse? Couldn't we reverse the relationship?

Il problema con questo (e il problema del quadrato / rettangolo) sta assumendo erroneamente una relazione in un dominio (geometria) in un altro (comportamento)

Un cerchio e un'ellisse sono correlati se li stai guardando attraverso il prisma della teoria geometrica. Ma questo non è l'unico dominio che puoi guardare.

La progettazione orientata agli oggetti riguarda comportamento .

La caratteristica di definizione di un oggetto è il comportamento di cui è responsabile l'oggetto. E nel dominio del comportamento, un cerchio e un'ellisse hanno un comportamento così diverso che è probabilmente meglio non pensarli affatto correlati. In questo dominio, un'ellisse e un cerchio non hanno alcuna relazione significativa.

La lezione qui è di scegliere il dominio che ha più senso per OOD, non provare e calzare in una relazione semplicemente perché esiste in un dominio diverso.

L'esempio più comune di questo errore nel mondo reale consiste nell'assumere che gli oggetti siano correlati (o anche la stessa classe) perché hanno dati simili anche se il loro comportamento è molto diverso. Questo è un problema comune quando inizi a costruire oggetti "prima i dati" definendo dove vanno i dati. Puoi finire con una classe correlata tramite dati che hanno un comportamento completamente diverso. Ad esempio, sia la busta paga che gli oggetti dei dipendenti potrebbero avere un attributo "stipendio lordo", ma un dipendente non è un tipo di busta paga e una busta paga non è un tipo di dipendente.

    
risposta data 04.04.2016 - 12:41
fonte
8

I cerchi sono un caso speciale di ellissi, vale a dire che entrambi gli assi degli ellissi sono gli stessi. È fondamentalmente falso nel dominio del problema (la geometria) affermare che le ellissi potrebbero essere un tipo di cerchio. L'utilizzo di questo modello errato violerebbe molte garanzie di un cerchio, ad esempio "tutti i punti del cerchio hanno la stessa distanza dal centro". Anche quella sarebbe una violazione del Principio di sostituzione di Liskov. In che modo un'ellisse ha un raggio singolo? (Non setRadius() ma, soprattutto, getRadius() )

Sebbene la modellazione dei cerchi come sottotipo di ellissi non sia fondamentalmente errata, è l'introduzione della mutabilità a rompere questo modello. Senza i metodi setX() e setY() , non vi è alcuna violazione LSP. Se è necessario disporre di un oggetto con dimensioni diverse, la creazione di una nuova istanza rappresenta una soluzione migliore:

class Ellipse {
  final double x;
  final double y;
  ...
  Ellipse withX(double newX) {
    return new Ellipse(x: newX, y: y);
  }
}
    
risposta data 04.04.2016 - 08:48
fonte
6

È un errore fin dall'inizio sostenere di avere una classe "Ellipse" e una classe "Circle" in cui una è una sottoclasse dell'altro. Hai due scelte realistiche: una è quella di avere classi separate. Possono avere una superclasse comune, per cose come il colore, se l'oggetto è riempito, la larghezza della linea per il disegno ecc.

L'altro è quello di avere una classe chiamata solo "Ellipse". Se hai questa classe, è abbastanza facile usarla per rappresentare i cerchi (potrebbero esserci trappole a seconda dei dettagli di implementazione: un'ellisse avrà un angolo e il calcolo di quell'angolo non deve incontrare problemi per un'ellisse a forma di cerchio). Potresti anche avere metodi specializzati per ellissi circolari, ma questi "ellissi circolari" sarebbero comunque oggetti "ellissi" pieni.

    
risposta data 04.04.2016 - 12:59
fonte
6

Cormac ha una risposta davvero fantastica, ma voglio solo approfondire un po 'il motivo della confusione, in primo luogo.

L'ereditarietà in OO viene spesso insegnata usando metafore del mondo reale, come "le mele e le arance sono entrambe sotto-classi di frutta". Sfortunatamente ciò porta a credere erroneamente che i tipi in OO dovrebbero essere modellati in base ad alcune gerarchie tassonomiche esistenti indipendenti dal programma.

Ma nella progettazione del software, i tipi dovrebbero essere modellati in base ai requisiti dell'applicazione. Classificazioni in altri domini sono di solito irrilevanti. In un'applicazione reale con oggetti "Apple" e "Orange" - ad esempio un sistema di gestione dell'inventario per un supermercato - probabilmente non saranno affatto classi distinte e categorie come "Frutta" saranno attributi piuttosto che supertipi.

Il problema dell'ellisse circolare è un'aringa rossa. In geometria un cerchio è una specializzazione di un'ellisse, ma le classi nel tuo esempio non sono figure geometriche. Fondamentalmente, le figure geometriche non sono mutabili. Possono essere trasformati , tuttavia, ma una può essere trasformata in un'ellissi. Quindi un modello in cui i cerchi possono cambiare raggio ma non cambiare in un'ellissi non corrisponde alla geometria. Un modello del genere potrebbe avere senso in una particolare applicazione (ad esempio uno strumento di disegno), ma la classificazione geometrica è irrilevante per come si progetta la gerarchia di classi.

Quindi Circle dovrebbe essere una sottoclasse di Ellipse o viceversa? Dipende totalmente dai requisiti della particolare applicazione che utilizza questi oggetti. Un'applicazione di disegno può avere scelte diverse su come trattare cerchi ed ellissi:

  1. Considera cerchi ed ellissi come tipi distinti di forme con diverse UI (ad esempio due maniglie di ridimensionamento su un ellisso, una maniglia su un cerchio). Ciò significa che puoi avere un'ellisse che è geometricamente un cerchio ma non un cerchio dal punto di vista dell'applicazione.

  2. Tratta tutte le ellissi, inclusi i cerchi uguali, ma hai un'opzione per "bloccare" xey sullo stesso valore.

  3. Gli ellissi sono solo cerchi in cui è stata applicata una trasformazione di ridimensionamento.

Ogni possibile progetto porterà a diversi modelli di oggetti -

Nel primo caso, Circle ed Ellipses saranno classi fratelli

Nel 2 °, non ci sarà affatto una classe Circle distinta

Nel 3 °, non ci sarà una classe Ellipse distinta. Quindi il cosiddetto problema dell'ellisse circolare non entra nell'immagine in nessuno di questi.

Quindi per rispondere alla domanda come posta: Dovrebbe il cerchio estendere l'ellisse? La risposta è: dipende da cosa vuoi fare con esso. Ma probabilmente no.

    
risposta data 07.04.2017 - 11:49
fonte
3

Seguendo i punti LSP, una soluzione 'adeguata' a questo problema è come @HorusKol e @Ixrec sono arrivati - derivando entrambi i tipi da Shape. Ma dipende dal modello da cui stai lavorando, quindi dovresti sempre tornare a quello.

Quello che mi è stato insegnato è:

Se il sottotipo non può eseguire lo stesso comportamento del super-tipo, la relazione non regge nella premessa IS-A - dovrebbe essere modificata.

  • Un sottotipo è un SUPERSET del super-tipo.
  • Un super-tipo è un SUBSET del sottotipo.

In inglese:

  • Un tipo derivato è un SUPERSET del tipo base.
  • Un tipo base è un SUBSET del tipo derivato.

(Esempio:

  • Una macchina con uno scarico di cattivo ragazzo è ancora un'auto (secondo alcuni).
  • Una macchina senza motore, ruote, timone, trasmissione, e solo il guscio a sinistra, non è una 'macchina', è solo un guscio.)

Ecco come funziona la classificazione (cioè nel mondo animale), e in linea di principio, in OO.

Usando questa come definizione di ereditarietà e polimorfismo (che viene sempre scritta insieme), se questo principio è rotto, dovresti provare a ripensare ai tipi che stai cercando di modellare.

Come menzionato da @HorusKul e @Ixrec, in matematica hai dei tipi ben definiti. Ma in matematica, un cerchio è un'ellisse perché è un SUBSET dell'ellisse. Ma in OOP questo non è il modo in cui funziona l'ereditarietà. Una classe dovrebbe ereditare solo se è un SUPERSET (un'estensione) di una classe esistente - il che significa che è ancora la classe base in tutti i contesti.

Sulla base di ciò, ritengo che la soluzione debba essere leggermente riformulata.

Avere un tipo di base Shape, quindi RoundedShape (effettivamente un cerchio ma ho usato un nome diverso qui DELIBERATELY ...)

... then Ellipse.

In questo modo:

  • RoundedShape è una forma.
  • Ellisse è una forma arrotondata.

(Questo ha un senso per le persone nel linguaggio. Abbiamo già un concetto chiaramente definito di "circolo" nelle nostre menti, e ciò che stiamo cercando di fare qui generalizzando (l'aggregazione) rompe quel concetto.)

    
risposta data 29.11.2017 - 04:29
fonte
-1

Da una prospettiva OO l'ellisse estende il cerchio, si specializza su di esso aggiungendo alcune proprietà. Le proprietà esistenti del cerchio sono ancora presenti nell'ellisse, ma diventano più complesse e più specifiche. In questo caso, non vedo alcun problema con il comportamento come fa Cormac, le forme non hanno alcun comportamento. L'unico problema è che in senso linguistico o matematico non sembra giusto dire "un'ellisse È un cerchio". Perché l'intero punto dell'esercizio che non è menzionato ma è comunque implicito, era quello di classificare le forme geometriche. Questa potrebbe essere una buona ragione per considerare i circoli e le ellissi come coetanei, non collegarli per ereditarietà e accettare che abbiano solo alcune delle stesse proprietà e NON lasciare che la tua mente OO contorta abbia la sua strada con quell'osservazione.

    
risposta data 17.06.2016 - 08:11
fonte

Leggi altre domande sui tag