3. Liskov Principle - mrbahadoor/symfony5-solid GitHub Wiki
Background
Developed by Barbara Liskov, computer scientist and winner of Turing award(like nobel prize for IT).
The Liskov Substitution Principle (LSP) is one of the SOLID principles of object-oriented programming. It states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. In other words, a subclass should be substitutable for its superclass without breaking the behavior expected by clients of the superclass.
Generic definition
Subtypes(any class that extends a base class or implements an interface) must be substituable for their base types (abstract / parent classes or interfaces).
Simplified definition
You should be able to substitute a class for a sub-class without breaking you app.
Rules
- You cannot change / override the type of a protected property:
Example: If parent class hasprotected int $quantity
, in child classprotected float $quantity
cannot be set.
- You cannot narrow (make more specific) the type of an argument (type hint) in a subtype, for a method that lies in the base type:
Example: If parent class haspublic function calucateScore(UserInterface $user)
, in child classpublic function calculateScore(User $user)
is not allowed. The inverse is allowed.
- You cannot widen (make less specific) the return type of a method (return type), in a subtype, for a method that lies in the base type:
Example: If parent class haspublic function calculateScore(Object $score): childObject
, in child classpublic function calculateScore(Object $score): parentObject
is not allowed. The inverse is allowed.
- Follow your parent type's rules on when and why to throw exceptions
Note: Rules 1 to 3 are impossible to violate in php.
Class Substitution
To substitute a service sub-class for auto-wiring, use class key in services.yaml:
App\Service\SightingScorer:
class: App\Service\DebuggableSightingScorer
argugments:
$scoringFactors: !tagged_iterator scoring.factor
In subclass:
class DebuggableSightingScorer extends SightingScorer
{
public function score(BigFootSighting $sighting): BigFootSightingScore
{
return parent::score($sighting);
}
}
LSP for Interfaces: When a class implements an interface, it's essentially promising to fulfill the contract specified by that interface. Therefore, the class should be substitutable for any other class implementing the same interface, without altering the expected behavior of the program.
Thus, even though both classes adhere to the interface contract by implementing the eat() and fly() methods, the Liskov Substitution Principle reminds us that a Penguin should be substitutable for a Bird, which includes the ability to fly, but it doesn't behave as expected.
To adhere to LSP, one could consider redefining the interface to be more flexible or use different interfaces that define subsets of behaviors that are cohesive, adhering to the Interface Segregation Principle.
Changes allowed
- Narrower (subclasses) return types are allowed.
- Wider argument types (parent / abstract / interface) are allowed.
Consideration
For a client class (Controller, Manager or Service) that depends on an interface (base type), the subtype(classes that implements interfaces) are allowed in the __constructor() method.