Principio di Liskov: violazione per tipo di suggerimento

2

Secondo il principio di Liskov, una costruzione come quella qui sotto non è valida, in quanto rafforza una pre-condizione.

So che l'esempio è inutile / senza senso, ma quando ho fatto l'ultima volta una domanda come questa, e ho usato un esempio di codice più elaborato, sembrava distrarre troppo la gente dalla domanda vera e propria.

//Data models
abstract class Argument
{
    protected $value = null;
    public function getValue()
    {
        return $this->value;
    }
    abstract public function setValue($val);
}
class Numeric extends Argument
{
    public function setValue($val)
    {
        $this->value = $val + 0;//coerce to number
        return $this;
    }
}
//used here:
abstract class Output
{
    public function printValue(Argument $arg)
    {
        echo $this->format($arg);
        return $this;
    }
    abstract public function format(Argument $arg);
}
class OutputNumeric extends Output
{
    public function format(Numeric $arg)//<-- VIOLATION!
    {
        $format = is_float($arg->getValue()) ? '%.3f' : '%d';
        return sprintf($format, $arg->getValue());
    }
}

Perché questo tipo di "violazione" può essere considerato dannoso? Tanto che alcune lingue, come quella che ho usato in questo esempio (PHP), non consentono nemmeno questo?

Non sono autorizzato a rafforzare il tipo di suggerimento di un metodo astratto ma, sovrascrivendo il metodo printValue , I am è autorizzato a scrivere:

class OutputNumeric extends Output
{
    final public function printValue(Numeric $arg)
    {
        echo $this->format($arg);
    }
    public function format(Argument $arg)
    {
        $format = is_float($arg->getValue()) ? '%.3f' : '%d';
        return sprintf($format, $arg->getValue());
    }
}

Ma ciò implicherebbe ripetermi per ogni bambino di Output , e rende più difficile il riutilizzo dei miei oggetti.

Capisco perché esiste il principio di Liskov, non fraintendetemi, ma trovo un po 'difficile capire perché la firma di un metodo abstract in un abstract la classe deve essere applicata a un metodo molto più rigoroso rispetto a un metodo non astratto.

Perché non sono autorizzato a fare il salto in una classe figlia, in una classe figlia?

Per come la vedo io, la classe figlia OutputNumeric è un caso d'uso specifico di Output , e quindi potrebbe aver bisogno di un'istanza specifica di Argument , cioè Numeric . È davvero così sbagliato da parte mia scrivere un codice come questo, quando mi sono autorizzato a scrivere questo:

abstract class Output
{
    public function printValue(Argument $arg)
    {
        echo $this->format($arg);
    }
    public function(Argument $arg)
    {
        throw new RuntimeException(__METHOD__. ' Has to be overridden by child class');
    }
}
class OutputNumeric extends Output
{
    final public function format(Numeric $arg)
    {
        $format = is_float($arg->getValue()) ? '%.3f' : '%d';
        return sprintf($format, $arg->getValue());
    }
}

Questo tipo di acchiappi la stessa cosa, ma è solo un codice hacky e, secondo me, orribile ...

    
posta Elias Van Ootegem 12.11.2013 - 10:51
fonte

1 risposta

5

Non stai solo infrangendo LSP qui, stai infrangendo il concetto di base dell'ereditarietà. Se hai un format(Argument $arg) nel tuo tipo di base, allora devi avere qualcosa che possa corrispondere al tipo derivato.

Considera che se il tuo esempio fosse permesso, cosa accadrebbe se tentassi di passare una stringa a OutputNumeric::format ? Se avessi informazioni specifiche sul tipo e sapessi che lo stavi passando ad un OutputNumeric, sarebbe un errore di tipo. Se non lo facessi e lo stavi passando ad un Output, non sarebbe un errore di tipo, ma dove potrebbe essere inviato?

Questa non è tanto una violazione di LSP in quanto è una violazione del contratto di base stabilito da quella superclasse, e non si può andare in giro violando quelli o improvvisamente il compilatore può entrare in situazioni in cui non può ti dico che ti stai sbagliando, ma non ha nemmeno un posto dove inviare la funzione!

Ciò che potrebbe essere una violazione di LSP in questo caso sarebbe se tu avessi un NumericOutput che ha accettato qualsiasi numero naturale, ma anche una sottoclasse che fallirebbe su qualsiasi numero superiore a n . Il contratto della classe (o "interfaccia") non è cambiato, ma le precondizioni lo sono.

    
risposta data 12.11.2013 - 11:03
fonte

Leggi altre domande sui tag