Il principio di sostituzione di Liskov è incompatibile con Introspection o Duck Typing?


11

Comprendo correttamente che il principio di sostituzione di Liskov non può essere osservato nelle lingue in cui gli oggetti possono ispezionarsi, come è normale nelle lingue tipizzate con l'anatra?

Ad esempio, in Ruby, se una classe Beredita da una classe A, allora per ogni oggetto xdi A, x.classtornerà A, ma se xè un oggetto di B, x.classnon tornerà A.

Ecco una dichiarazione di LSP:

Let q (x) sia una dimostrabile proprietà sugli oggetti x di tipo T . Poi q (y) deve essere dimostrabile per oggetti y di tipo S dove S è un sottotipo di T .

Quindi in Ruby, ad esempio,

class T; end
class S < T; end

violare LSP in questo modulo, come testimoniato dalla proprietà q (x) =x.class.name == 'T'


Addizione. Se la risposta è "sì" (LSP incompatibile con l'introspezione), allora la mia altra domanda sarebbe: c'è qualche forma modificata di LSP "debole" che può eventualmente valere per un linguaggio dinamico, possibilmente in alcune condizioni aggiuntive e solo con tipi speciali di proprietà .


Aggiornare. Per riferimento, ecco un'altra formulazione di LSP che ho trovato sul web:

Le funzioni che utilizzano puntatori o riferimenti a classi di base devono essere in grado di utilizzare oggetti di classi derivate senza saperlo.

E un altro:

Se S è un sottotipo dichiarato di T, gli oggetti di tipo S devono comportarsi come dovrebbero comportarsi oggetti di tipo T, se trattati come oggetti di tipo T.

L'ultimo è annotato con:

Si noti che l'SPL riguarda il comportamento previsto degli oggetti. Si può seguire l'LSP solo se si è chiari su quale sia il comportamento previsto degli oggetti.

Questo sembra essere più debole di quello originale e potrebbe essere possibile osservarlo, ma vorrei vederlo formalizzato, in particolare spiegato chi decide quale sia il comportamento previsto.

Quindi LSP non è una proprietà di una coppia di classi in un linguaggio di programmazione, ma di una coppia di classi insieme a un determinato insieme di proprietà, soddisfatte dalla classe antenata? In pratica, ciò significherebbe che per costruire una sottoclasse (classe discendente) rispettando LSP, tutti i possibili usi della classe antenata devono essere conosciuti? Secondo LSP, la classe degli antenati dovrebbe essere sostituibile con qualsiasi classe discendente, giusto?


Aggiornare. Ho già accettato la risposta, ma vorrei aggiungere un altro esempio concreto di Ruby per illustrare la domanda. In Ruby, ogni classe è un modulo nel senso che la Classclasse è una discendente della Moduleclasse. Tuttavia:

class C; end
C.is_a?(Module) # => true
C.class # => Class
Class.superclass # => Module

module M; end
M.class # => Module

o = Object.new

o.extend(M) # ok
o.extend(C) # => TypeError: wrong argument type Class (expected Module)

2
Quasi tutte le lingue moderne forniscono un certo grado di introspezione, quindi la domanda non è specifica per Ruby.
Joachim Sauer,

Capisco, ho dato a Ruby solo un esempio. Non lo so, forse in alcune altre lingue con introspezione ci sono alcune "forme deboli" di LSP, ma, se ho capito bene il principio, è incompatibile con l'introspezione.
Alexey,

Ho rimosso "Ruby" dal titolo.
Alexey,

2
La risposta breve è che sono compatibili. Ecco un post sul blog che concordo in gran parte con: Principio di sostituzione di Liskov per Duck Typing
K.Steff

2
@Alexey Le proprietà in questo contesto sono invarianti di un oggetto. Ad esempio, gli oggetti immutabili hanno la proprietà che i loro valori non cambiano. Se osservi buoni test unitari, dovrebbero testare esattamente queste proprietà.
K.Steff,

Risposte:


29

Ecco il vero principio :

Sia q(x)una proprietà dimostrabile su oggetti xdi tipo T. Quindi q(y)dovrebbe essere dimostrabile per oggetti ydi tipo in Scui Sè un sottotipo di T.

E l'ottimo sommario di Wikipedia :

Afferma che, in un programma per computer, se S è un sottotipo di T, allora gli oggetti di tipo T possono essere sostituiti con oggetti di tipo S (ovvero, gli oggetti di tipo S possono essere sostituiti con oggetti di tipo T) senza alterare nessuno dei le proprietà desiderabili di quel programma (correttezza, compito svolto, ecc.).

E alcune citazioni rilevanti dall'articolo:

Ciò che è necessario è un requisito più forte che limiti il ​​comportamento dei sottotipi: le proprietà che possono essere provate utilizzando la specifica del tipo presunto di un oggetto dovrebbero essere valide anche se l'oggetto è in realtà un membro di un sottotipo di quel tipo ...

Una specifica di tipo include le seguenti informazioni:
- il nome del tipo;
- Una descrizione dello spazio dei valori del tipo;
- Per ciascuno dei metodi del tipo:
--- Il suo nome;
--- La sua firma (comprese le eccezioni segnalate);
--- Il suo comportamento in termini di pre-condizioni e post-condizioni.

Quindi, alla domanda:

Comprendo correttamente che il principio di sostituzione di Liskov non può essere osservato nelle lingue in cui gli oggetti possono ispezionarsi, come è normale nelle lingue tipizzate con l'anatra?

No.

A.classrestituisce una classe.
B.classrestituisce una classe.

Dal momento che è possibile effettuare la stessa chiamata sul tipo più specifico e ottenere un risultato compatibile, LSP rimane attivo. Il problema è che con i linguaggi dinamici, puoi ancora chiamare le cose sul risultato aspettandoti che siano lì.

Ma consideriamo un linguaggio tipicamente statico, strutturale (anatra). In questo caso, A.classrestituirebbe un tipo con un vincolo che deve essere Ao un sottotipo di A. Ciò fornisce la garanzia statica che qualsiasi sottotipo di Adeve fornire un metodo il T.classcui risultato è un tipo che soddisfa tale vincolo.

Ciò fornisce un'affermazione più forte che LSP detiene in lingue che supportano la tipizzazione anatra e che qualsiasi violazione di LSP in qualcosa come Ruby si verifica più a causa di un abuso dinamico normale che di un'incompatibilità del design del linguaggio.


1
"Dal momento che è possibile effettuare la stessa chiamata sul tipo più specifico e ottenere un risultato compatibile, LSP tiene". LSP vale se i risultati sono identici, se l'ho capito correttamente. Forse può esserci una forma di "settimana" di LSP rispetto a determinati vincoli, che richiede invece di tutte le proprietà che siano soddisfatti solo i vincoli indicati. In ogni caso, apprezzerei qualsiasi riferimento.
Alexey,

@Alexey modificato per includere il significato di LSP. Se posso usare B dove mi aspetto A, allora LSP vale. Sono curioso di sapere come pensi che .class di Ruby possa violarlo.
Telastyn,

3
@Alexey - Se il tuo programma contiene fail unless x.foo == 42e un sottotipo restituisce 0, è la stessa cosa. Questo non è un fallimento di LSP, è il normale funzionamento del tuo programma. Il polimorfismo non è una violazione di LSP.
Telastyn,

1
@Alexey - Sicuro. Supponiamo che sia una proprietà. In tal caso il codice viola LSP poiché non consente ai sottotipi di avere lo stesso comportamento semantico. Ma non è particolarmente speciale per le lingue dinamiche o dattiloscritte. Non c'è niente che facciano nel loro design del linguaggio che causa la violazione. Il codice che hai scritto lo fa. Ricorda, LSP è un principio di progettazione del programma (quindi dovrebbe essere nella definizione) piuttosto che una proprietà matematica dei programmi.
Telastyn,

6
@Alexey: se scrivi qualcosa che dipende da x.class == A, allora è il tuo codice che viola LSP , non la lingua. È possibile scrivere codice che viola LSP in quasi tutti i linguaggi di programmazione.
Andres F.

7

Nel contesto di LSP una "proprietà" è qualcosa che può essere osservata su un tipo (o un oggetto). In particolare parla di una "proprietà dimostrabile".

Tale "proprietà" potrebbe esistere un foo()metodo che non ha valore di ritorno (e segue il contratto stabilito nella sua documentazione).

Assicurati di non confondere questo termine con "proprietà" come in " classè una proprietà di ogni oggetto in Ruby". Tale "proprietà" può essere una "proprietà LSP", ma non è automaticamente la stessa!

Ora la risposta alle tue domande dipende molto da quanto rigoroso definisci "proprietà". Se si dice "la proprietà di classe Aè che .classrestituirà il tipo di oggetto", quindi Bin realtà fa avere quella proprietà.

Se, tuttavia, si definisce la "proprietà" di essere " .classrendimenti A", allora ovviamente Bnon senza avere quella proprietà.

Tuttavia, la seconda definizione non è molto utile, in quanto hai essenzialmente trovato un modo per dichiarare una costante.


Posso solo pensare a una definizione di "proprietà" di un programma: per un dato input restituisce un dato valore, o, più in generale, se usato come blocco in un altro programma, quell'altro programma per un determinato input restituirà un valori dati. Con questa definizione, non vedo cosa significhi che " .classrestituirà il tipo di oggetto". Se ciò significa che x.class == x.classquesta non è una proprietà interessante.
Alexey,

1
@Alexey: ho aggiornato la mia domanda con un chiarimento sul significato di "proprietà" nel contesto di LSP.
Joachim Sauer,

2
@Alexey: guardando nel documento non trovo una definizione o "proprietà" specifica. Questo è probabilmente perché il termine è usato nel senso generale del CS "qualcosa che può essere osservato / provato su qualche oggetto". Non ha nulla a che fare con l'altro che significa "un campo di un oggetto".
Joachim Sauer,

4
@Alexey: non so che altro posso dirti. Uso la definizione di "una proprietà è una qualità o un attributo di un oggetto". "color" è una proprietà di un oggetto fisico visibile. la "densità" è una proprietà di un materiale. "Avere un metodo specificato" è una proprietà di una classe / oggetto.
Joachim Sauer,

4
@Alexey: Penso che stai gettando il bambino con l'acqua del bagno: Solo perché per alcune proprietà l'LSP non può essere tenuto in mano non significa che sia inutile o "non regge in nessuna lingua". Ma quella discussione andrebbe molto lontano qui.
Joachim Sauer,

5

A quanto ho capito, non c'è nulla di introspezione che sarebbe incompatibile con LSP. Fondamentalmente, fintanto che un oggetto supporta gli stessi metodi di un altro, i due dovrebbero essere intercambiabili. Cioè, se il codice si aspetta un Addressoggetto, quindi non importa se si tratta di una CustomerAddresso di un WarehouseAddress, a patto che entrambi forniscono (ad esempio) getStreetAddress(), getCityName(), getRegion()e getPostalCode(). Potresti certamente creare un tipo di decoratore che prende un diverso tipo di oggetto e usa l'introspezione per fornire i metodi richiesti (ad esempio, una DestinationAddressclasse che prende un Shipmentoggetto e presenta l'indirizzo di spedizione come un Address), ma non è richiesto e sicuramente non lo farebbe non impedire l'applicazione dell'LSP.


2
@Alexey: gli oggetti sono "uguali" se supportano gli stessi metodi. Ciò significa lo stesso nome, lo stesso numero e tipo di argomenti, lo stesso tipo di ritorno e gli stessi effetti collaterali (purché siano visibili al codice chiamante). I metodi possono comportarsi in modo completamente diverso, ma purché rispettino il contratto, va bene.
TMN,

1
@Alexey: ma perché dovrei avere un contratto del genere? Quale uso effettivo soddisfa tale contratto? Se avessi un tale contratto si potrebbe semplicemente sostituire ogni occorrenza di x.class.namecon 'A' , di fatto rendendo x.class.name inutile .
Joachim Sauer,

1
@Alexey: di nuovo: solo perché è possibile definire un contratto che non può essere completato estendendo un'altra classe non si rompe LSP. Significa solo che hai creato una classe non estendibile. Se definisco un metodo per "restituire se il blocco di codice fornito termina a tempo finito ", ho anche un contratto che non può essere adempiuto. Ciò non significa che la programmazione sia inutile.
Joachim Sauer,

2
@Alexey sta provando a determinare se x.class.name == 'A'è un anti-pattern nella tipizzazione delle anatre: dopo tutto, la tipizzazione delle anatre viene da "Se si rompe e cammina come un'anatra, è un'anatra". Quindi, se si comporta come Ae onora i contratti stipulati A, è un Aesempio
K.Steff

1
@Alexey Ti sono state presentate definizioni chiare. La classe di un oggetto non fa parte del suo comportamento o contratto o come si desidera chiamarlo. Stai equivocando "proprietà" con "campo oggetto, come x.myField", che come è stato sottolineato, NON è lo stesso. In questo contesto, una proprietà è più simile a una proprietà matematica , come gli invarianti di tipo. Inoltre, è un anti-pattern per verificare il tipo esatto se si desidera digitare l'anatra. Quindi, qual è il tuo problema con l'LSP e la duck ducking? ;)
Andres F.

4

Dopo aver esaminato il documento originale di Barbara Liskov, ho scoperto come completare la definizione di Wikipedia in modo che LSP potesse davvero essere soddisfatto in quasi tutte le lingue.

Prima di tutto, la parola "dimostrabile" è importante nella definizione. Non è spiegato nell'articolo di Wikipedia e i "vincoli" sono menzionati altrove senza riferimento ad esso.

Ecco la prima importante citazione dall'articolo:

Ciò che è necessario è un requisito più forte che limiti il ​​comportamento dei sottotipi: le proprietà che possono essere provate utilizzando la specifica del tipo presunto di un oggetto dovrebbero essere valide anche se l'oggetto è in realtà un membro di un sottotipo di quel tipo ...

Ed ecco il secondo, che spiega cos'è una specifica di tipo :

Una specifica di tipo include le seguenti informazioni:

  • Il nome del tipo;
  • Una descrizione dello spazio dei valori del tipo;
  • Per ciascuno dei metodi del tipo:
    • Il suo nome;
    • La sua firma (comprese le eccezioni segnalate);
    • Il suo comportamento in termini di pre-condizioni e post-condizioni.

Quindi, LSP ha senso solo rispetto alle specifiche del tipo dato , e per una specifica del tipo appropriata (ad esempio quella vuota), può essere soddisfatto probabilmente in qualsiasi lingua.

Ritengo che la risposta di Telastyn sia la più vicina a ciò che stavo cercando perché i "vincoli" sono stati menzionati esplicitamente.


Telastyn, se potessi aggiungere queste citazioni alla tua risposta, preferirei accettare le tue piuttosto che le mie.
Alexey,

2
il markup non è più lo stesso e ho modificato un po 'l'enfesi, ma le virgolette sono state incluse.
Telastyn,

Siamo spiacenti, Joachim Sauer ha già menzionato le proprietà "dimostrabili", che non ti sono piaciute. In generale, hai semplicemente riformulato le risposte esistenti. Onestamente, non so cosa stai cercando ...
Andres F.

No, non è stato spiegato "provabile da cosa?". La proprietà x.class.name = 'A'è dimostrabile per tutta la xclasse Ase si consente troppa conoscenza. La specifica del tipo non è stata definita e neanche la sua esatta relazione con LSP, sebbene in modo informale siano state fornite alcune indicazioni. Ho già trovato quello che cercavo nel documento di Liskov e ho risposto alla mia domanda sopra.
Alexey,

Penso che le parole che hai incoraggiato siano la chiave. Se il supertipo documenta che per uno qualsiasi xdi quel tipo, x.woozleprodurrà undefined, nessun tipo per il quale x.woozlenon produce undefinedsarà un sottotipo appropriato. Se il supertipo non documenta nulla in merito x.woozle, il fatto che l'utilizzo x.woozledel supertipo si tradurrà in resa undefinednon implicherebbe nulla al riguardo di ciò che potrebbe fare sul sottotipo.
Supercat,

3

Per citare l'articolo di Wikipedia su LSP , "la sostituibilità è un principio nella programmazione orientata agli oggetti". È un principio e parte della progettazione del tuo programma. Se scrivi codice che dipende x.class == A, allora è il tuo codice che viola LSP. Nota che questo tipo di codice non funzionante è possibile anche in Java, non è necessario digitare l'anatra.

Nulla nella tipizzazione anatra interrompe intrinsecamente LSP. Solo se lo usi in modo improprio, come nel tuo esempio.

Pensiero aggiuntivo: il controllo esplicito della classe di un oggetto non sconfigge comunque lo scopo della tipizzazione anatra?


Andres, puoi dare la tua definizione di LSP, per favore?
Alexey,

1
@Alexey La definizione precisa di LSP è dichiarata in Wikipedia in termini di sottotipi. La definizione informale è Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).. La classe esatta di un oggetto NON è una delle "proprietà desiderabili del programma"; altrimenti sarebbe contrario non solo alla tipizzazione delle anatre ma al sottotipo in generale, incluso il sapore di Java.
Andres F.

2
@Alexey Nota anche che il tuo esempio di x.class == Aviolazione sia della LSP che della tipizzazione anatra. Non ha senso usare la tipizzazione anatra se hai intenzione di verificare i tipi reali.
Andres F.

Andres, questa definizione non è abbastanza precisa per me da capire. Quale programma, un dato programma o uno qualsiasi? Cos'è una proprietà desiderabile? Se la classe si trova in una libreria, diverse applicazioni possono considerare desiderabili proprietà diverse. A non vedo come la linea di codice possa violare LSP, perché pensavo che LSP fosse una proprietà di una coppia di classi in un determinato linguaggio di programmazione: ( A, B) soddisfare LSP o no. Se LSP dipende dal codice utilizzato altrove, non viene spiegato quale codice è consentito. Spero di trovare qualcosa qui: cse.ohio-state.edu/~neelam/courses/788/lwb.pdf
Alexey,

2
@Alexey LSP vale (o no) per un design specifico. È qualcosa da cercare in un design; non è una proprietà di una lingua in generale. Non c'è niente di più preciso rispetto alla definizione attuale: Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T. È ovvio che x.classnon è una delle proprietà interessanti qui; altrimenti neanche il polimorfismo di Java funzionerebbe. Non c'è nulla di inerente all'anatra digitando nel tuo "problema x.class". Sei d'accordo finora?
Andres F.
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.