Note
By the time I wrote my answer, you had changed your example from an interface implementation to a base class inheritance. My answer is still correct; since the LSP applies to interfaces and base classes as per this StackOverflow answer.
You did change more in the example than just inheritance/interface, which does influence the answer. I've addressed this at the bottom of this answer.
This is not a violation of the LSP.
It would be a violation if the method is wholly unusable, i.e. you're not supposed to ever call the method TagTransformer.transform
.
Examples of the difference.
Excuse the C# syntax, it's been a while since I did PHP.
public interface ICalculation
{
void SetFirstNumber(int num);
void SetSecondNumber(int num);
int CalculateOutputFromNumbers();
}
Implementation 1:
public class Addition : ICalculation
{
public void SetFirstNumber(int num)
{
this.Number1 = num;
}
public void SetSecondNumber(int num)
{
this.Number2 = num;
}
public int CalculateOutputFromNumbers()
{
return this.Number1 + this.Number2;
}
}
This is clearly using the interface as intended. No issue here.
Implementation 2:
public class SquareRoot: ICalculation
{
public void SetFirstNumber(int num)
{
this.Number1 = num;
}
public void SetSecondNumber(int num)
{
throw new Exception("Square roots only take one input value!);
}
public int CalculateOutputFromNumbers()
{
return Math.Sqrt(this.Number1);
}
}
Notice how you're never going to be allowed to call the SetSecondNumber
method from SquareRoot
, even though the method is part of the ICalculation
interface and SquareRoot
implements ICalculation
.
This violates LSP. In order to calculate a square root, SquareRoot
class needs to be treated differently from other ICalculation
-implementing classes.
Implementation 3:
public class Division : ICalculation
{
public void SetFirstNumber(int num)
{
this.Number1 = num;
}
public void SetSecondNumber(int num)
{
if(num == 0)
throw new Exception("You can't divide by zero!");
this.Number2 = num;
}
public int CalculateOutputFromNumbers()
{
return this.Number1 / this.Number2;
}
}
Based on your question, it seems that you think this is a violation of LSP. This is essentially what's happening in your code example, a specific exception is being thrown for a given invalid value.
This is not a violation. Notice how you're allowed to call the SetSecondNumber
method from Division
, but you simply can't use an impossible value (0).
This isn't a matter of having to use the Division
class differently from ICalculation
-implementing classes; it's simply a matter of bad input, no possible output.
That is significantly different from the SquareRoot
example; at least in relation to LSP.
In response to your new example
Your new example does in a way invalidate your original question.
Your old example was a PHP snippet:
public function transform($origin)
{
if (!is_string($origin)) throw new InvalidArgumentException();
...
}
It's important to note here that there is no type constraint on $origin
. This means that checking for a usable type is a logical consequence, and not inherently bad design (since the language allows for untyped parameters).
However, this is significantly different in your revised example:
public function eat(Food $food)
{
if ($food instanceof Meat) throw new InvalidArgumentException();
...
}
It's important to note here that there is a type constraint on $food
.
You're no longer using a typeless parameter. You're specifying that the parameter is of type Food
.
At this point, it becomes an LSP violation. Your input validation is no longer a matter of how the language works; but rather a consequence of the contract that is specified by your Animal
base class.
You're trying to create a Cat
which inherits from Animal
but actually (partially) outright refuses to implement Eat(Food)
. That is a willful exclusion of functionality, which does make it an LSP violation.
I would consider this an LSP violation of Meat
/Food
, more than it is an LSP violation of Cat
/Animal
. Meat is clearly being treated differently from Food, which violates the contract that says that Meat is a Food.
eat
ortransform
method in your post. Is throwing anInvalidArgumentException
an allowed action under the contract of the base class? If yes, it's not a violation, if no, it is a violation.