Ci scusiamo per la risposta molto tarda, ma questo è uno scenario valido per PHP fino ad oggi (v5.6 / 7.1), e confonde molti programmatori. Ho svolto molte ricerche sul tema dell'SPL in relazione agli obblighi contrattuali e alle violazioni, poiché sto sviluppando un quadro pienamente conforme a SOLID e posso dire che ci sono molte opinioni diverse in merito.
Dato che PHP non ha attualmente DbC ( Design by Contract ), e nella migliore delle ipotesi ha un tipo di suggerimento invariante per il metodo parametri e tipi di rendimento, dobbiamo considerare il sentimento di fondo del Principio di sostituzione di Liskov. Sebbene sia possibile applicare parametri e tipi di ritorno in entrambe le interfacce e funzioni astratte, questo è solo uno strumento per garantire che Liskov non sia violato, ma non copre DbC. Come afferma Robert Martin nel suo articolo Il principio di sostituzione di Liskov (grassetto / corsivo aggiunto da me):
The LSP makes clear that in OOD the ISA relationship pertains to
behavior. Not intrinsic private behavior, but extrinsic public
behavior; behavior that clients depend upon
In DbC, la validità di un DataType
in relazione a una classe di consumo è qualcosa che è concettualmente identico su tutto il cemento DataTypeHandlers
- se passi un DataType
a un DataTypeHandler
, è valido o non valido. Lo rivisiteremo più tardi.
Ora, per la mente umana sembra logico implementare quella regola come parametro digitato nel metodo, in questo modo:
public function getWhereConditions(DataValue $dataValue) {...}
// Override in a child class...
public function getWhereConditions(NumberDataValue $dataValue) {...}
Ma questo viola il LSP in quanto implica una covarianza dei parametri del metodo (cioè NumberDataValue è un più raffinato tipo di DataValue) e modifica il comportamento pubblico estrinseco.
Se, anziché applicare questa regola come tipo di parametro del metodo (pubblico), la estrapoliamo invece a un controllo di validità (privato) rispetto all'argomento passato, quindi spostiamo il comportamento da "pubblico" a "privato" . Come affermato da Robert Martin, questo è non una violazione di LSP. (Leggi le ultime pagine alla fine del suo articolo per vedere un esempio usando PersistentSet che non hanno una relazione ISA con Sets, che esegue il backup di questo reclamo).
Ecco un esempio in PHP che dimostra cosa intendo. Si noti che i metodi astratti hanno parametri invarianti (cioè DataType), ma consentono la variazione del comportamento privato. Inoltre, tieni presente che il metodo getWhereConditions()
è definitivo e ereditato da tutte le implementazioni concrete, assicurando che il comportamento pubblico rimanga coerente (lancia un DataTypeException
o restituisca un WhereConditionResultInterface
):
abstract class DataHandler()
{
public function getWhereConditions(DataValue $dataValue)
{
if(!$this->isDataValid($dataValid) {
throw new DataTypeException('Invalid data given');
}
$this->processGetWhereConditions($dataValue);
}
abstract protected function isDataValid(DataValue $dataValue);
/** @return WhereConditionResultInterface */
abstract protected function processGetWhereConditions(DataValue $dataValue);
}
final class NumberDataHandler extends DataHandler
{
protected function isDataValid(DataValue $dataValue)
{
return is_a($dataValue, NumberDataValueInterface::class);
}
protected function processGetWhereConditions(DataValue $dataValue)
{
/** @var NumberDataValueInterface $dataValue - Type hinting for modern IDEs */
// Do stuff specific to NumberDataValueInterface here
}
}
final class GeoCoordinateDataHandler extends DataHandler
{
protected function isDataValid(DataValue $dataValue)
{
return is_a($dataValue, GeoCoordinateDataValueInterface::class);
}
protected function processGetWhereConditions(DataValue $dataValue)
{
/** @var GeoCoordinateDataValueInterface $dataValue - Type hinting for modern IDEs */
// Do stuff specific to GeoCoordinateDataValueInterface here
}
}