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 ...