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

11

Ho ben capito che il principio di sostituzione di Liskov non può essere osservato nelle lingue in cui gli oggetti possono ispezionare se stessi, come quello che è normale in lingue dattilografate?

Ad esempio, in Ruby, se una classe B eredita da una classe A , quindi per ogni oggetto x di A , x.class restituirà A , ma se x è un oggetto di B , x.class non restituirà A .

Ecco una dichiarazione di LSP:

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.

Quindi, in Ruby, per 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 "debole" modificata di LSP che può eventualmente contenere per una dinamica linguaggio, eventualmente in alcune condizioni aggiuntive e con solo tipi speciali di proprietà .

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

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

E un altro:

If S is a declared subtype of T, objects of type S should behave as objects of type T are expected to behave, if they are treated as objects of type T.

L'ultimo è annotato con:

Note that the LSP is all about expected behaviour of objects. One can only follow the LSP if one is clear about what the expected behaviour of objects is.

Questo sembra essere più debole di quello originale e potrebbe essere possibile osservarlo, ma mi piacerebbe vederlo formalizzato, in particolare ha 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) nel rispetto dell'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?

Aggiornamento. 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 Class class è un discendente di Module class. 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)
    
posta Alexey 30.07.2012 - 11:56
fonte

5 risposte

29

Ecco il principio effettivo :

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.

E l'eccellente wikipedia sommario:

It states that, in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may be substituted for objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.).

E alcune citazioni pertinenti dal documento:

What is needed is a stronger requirement that constrains the behavior of sub-types: properties that can be proved using the specification of an object’s presumed type should hold even though the object is actually a member of a subtype of that type...

A type specification includes the following information:
- The type’s name;
- A description of the type's value space;
- For each of the type's methods:
--- Its name;
--- Its signature (including signaled exceptions);
--- Its behavior in terms of pre-conditions and post-conditions.

Quindi alla domanda:

Do I understand correctly that Liskov Substitution Principle cannot be observed in languages where objects can inspect themselves, like what is usual in duck typed languages?

No.

A.class restituisce una classe.
B.class restituisce una classe.

Poiché è possibile effettuare la stessa chiamata sul tipo più specifico e ottenere un risultato compatibile, LSP mantiene. Il problema è che con le lingue dinamiche, puoi ancora chiamare le cose sul risultato aspettandoti che siano lì.

Ma prendiamo in considerazione un linguaggio tipicamente statico, strutturale (anatra). In questo caso, A.class restituirebbe un tipo con un vincolo che deve essere A o un sottotipo di A . Ciò fornisce la garanzia statica che qualsiasi sottotipo di A deve fornire un metodo T.class il cui risultato è un tipo che soddisfa tale vincolo.

Ciò fornisce una più strong asserzione che LSP detenga in linguaggi che supportano la digitazione anatra e che qualsiasi violazione di LSP in qualcosa come Ruby si verifica più a causa del normale abuso dinamico rispetto a un'incompatibilità di linguaggio design.

    
risposta data 30.07.2012 - 15:02
fonte
7

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

Tale "proprietà" potrebbe essere l'esistenza di un metodo foo() che non ha alcun valore di ritorno (e segue il contratto impostato nella sua documentazione).

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

Ora la risposta alle tue domande dipende molto da quanto tu definisci rigorosa la "proprietà". Se dici "la proprietà della classe A è che .class restituirà il tipo dell'oggetto", allora B in realtà fa ha quella proprietà.

Se, tuttavia, definisci la "proprietà" come " .class restituisce A ", allora ovviamente B fa non ha quella proprietà.

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

    
risposta data 30.07.2012 - 12:11
fonte
5

A quanto ho capito, non c'è nulla riguardo all'introspezione che sarebbe incompatibile con LSP. Fondamentalmente, finché un oggetto supporta gli stessi metodi di un altro, i due dovrebbero essere intercambiabili. Cioè, se il tuo codice si aspetta un oggetto Address , allora non importa se è un CustomerAddress o un WarehouseAddress , purché entrambi forniscano (es.) getStreetAddress() , getCityName() , getRegion() e getPostalCode() . Potresti sicuramente creare un tipo di decoratore che accetta un diverso tipo di oggetto e usa l'introspezione per fornire i metodi richiesti (ad esempio, una classe DestinationAddress che prende un oggetto Shipment e presenta l'indirizzo di spedizione come Address ), ma non è richiesto e certamente non impedirebbe l'applicazione dell'LSP.

    
risposta data 30.07.2012 - 14:45
fonte
4

Dopo aver esaminato il documento originale di Barbara Liskov, ho trovato come completare la definizione di Wikipedia in modo che LSP possa essere effettivamente 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 citazione importante del documento:

What is needed is a stronger requirement that constrains the behavior of sub-types: properties that can be proved using the specification of an object’s presumed type should hold even though the object is actually a member of a subtype of that type...

Ed ecco il secondo, che spiega che specifica del tipo è:

A type specification includes the following information:

  • The type’s name;
  • A description of the type's value space;
  • For each of the type's methods:
    • Its name;
    • Its signature (including signaled exceptions);
    • Its behavior in terms of pre-conditions and post-conditions.

Quindi, LSP ha senso solo rispetto a specifiche tipo e, per una specifica di tipo appropriata (per esempio quella vuota), può essere soddisfatta probabilmente in qualsiasi lingua.

Considero la risposta di Telastyn il più vicino a quello che stavo cercando perché i "vincoli" sono stati menzionati in modo esplicito.

    
risposta data 31.07.2012 - 19:54
fonte
3

Per citare articolo di Wikipedia su LSP , "la sostituibilità è un principio nella programmazione orientata agli oggetti." È un principio e parte del design del tuo programma. Se scrivi codice che dipende da x.class == A , allora è il tuo codice che sta violando LSP. Nota che questo tipo di codice non funzionante è anche possibile in Java, non è necessaria alcuna digitazione anatra.

Niente nella digitazione anatra interrompe intrinsecamente LSP. Solo se la usi impropriamente, come nel tuo esempio.

Pensiero aggiuntivo: non controlla esplicitamente che la classe di un oggetto sconfigga lo scopo della digitazione anatra, comunque?

    
risposta data 30.07.2012 - 19:46
fonte