On Thu, Aug 7, 2025, at 20:37, Jonathan Vollebregt wrote:
On 8/7/25 12:38 AM, Hans Krentel wrote:
From a child class’s perspective, the “public interface” is both
protected + public of the parent. If you change or misuse a parent’s
implementation or invariants, it violates LSP, even if it doesn’t affect
external clients immediately.Ah okay, that part is not in my book, this explains to me why in your
example it violates substitutability for you, and with that thinking it
also prevents or degrades implementability for me so to say, as
otherwise I could not make use of visibility in classes - it would take
away that tool from me or I would not treat it well, potentially leading
to defects in the program.Don’t believe everything you read on the internet.
If you weren’t allowed to change a parent’s implementation, then you
wouldn’t be permitted to override methods at all, at which point
inheritance is pointless.It’s funny that he’s starting to talk about invariants here. Seems he
doesn’t realize every example he’s given so far is covariant (Besides
the one at the beginning of the thread where he confused static and
non-static properties)He’s trying to argue that LSP is about implementation when it’s not.
This is an interface with a function signature:interface I {
function f(): string;
}See a fruit anywhere? Me neither. That’s because there isn’t one. It’s
not part of the signature. It’s not part of the interface. It’s not part
of the definition. It’s not part of the contract.This is a class with the same function signature:
class C {
function f(): string {
return “fruit”;
}
}In fact, this class could implement that interface without any problems,
because the signature is identical. The types are identical. There is no
LSP violation. There is also no contract that implies C::f() returns
“fruit”.If you made that assumption I’d call it a lack of error handling from
someone who only considered the happy path. Static analysis tools exist
to stop you from making these mistakes.Once more, just to drive the point home. This is an interface with a
function signature:interface I {
public int $p { get; }
}This is a class with the same function signature (And one more for good
measure, because I::$p is covariant!):class C {
public int $p;
}In fact, this class could implement that interface without any problems,
because the signature is identical. The types are identical. There is no
LSP violation. There is no contract that implies that getting C->p will
return a certain value, other than that the value will be an int.
Back to the original issue: I’m going to open a github issue on this
since it’s clearly a bug and I don’t see fixing it breaking any existing
code.
Hey Jonathan:
It seems we’re talking past each other a bit, so let me clarify.
Liskov’s original work (which is worth reading, since it seems you haven’t) frames LSP as a behavioural contract, not merely a type signature. When the principle was introduced in the 1980s, modern type systems barely existed—the focus was always on whether substituting a child for a parent would break expected behaviour, not just the surface-level types.
For example, if A::foo(): int promises to always return a non-zero integer, and a subclass overrides foo() to only return zero, that violates the contract—even though the type signatures match perfectly. This is the sort of semantic guarantee that LSP is about, and it is discussed in nearly every reputable textbook and conference talk on OO design. Languages like PHP can’t enforce these contracts, but the principle is what helps prevent subtle design bugs and behavioural “surprises.”
You mention that overriding methods is the point of inheritance. Of course! That is where LSP matters most. It is about ensuring that overriding methods (and properties) in subclasses don’t break expectations encoded or implied in the parent.
On invariants: These are a textbook part of LSP. The values and guarantees behind properties (not just their types) are what client code may rely on. That is why invariants are so often referenced in the LSP context.
As for your example with interfaces and signatures: Yes, two signatures can match perfectly, and yet LSP can still be violated if the contract (explicit or implicit) is not honoured. Static analysis tools can help, but they only go so far—they can’t check for semantic compatibility.
So while a language or type system may permit something, that doesn’t mean it’s a good idea from a design or maintainability perspective.
Looking forward to your GitHub issue.
— Rob