'Violation of the LSP-principle using extra constructor parameters in subtypes

I've been reading about the Liskov Substitution Principle when I noticed this answer. It has a Circle and a ColoredCircle type where the constructor of ColoredCircle takes one extra argument; the color.

class Circle:
    radius: int

    def __init__(self, radius: int) -> None:
        self.radius = radius

class ColoredCircle(Circle):
    radius: int
    color: str

    def __init__(self, radius: int, color: str) -> None:
        super().__init__(radius)
        self.color = color

Doesn't this violate one of the requirements below? (taken from this answer). The only other option in the case of the ColoredCircle would be a public variable or a set_color method.

Pre-conditions cannot be strengthened: Assume your base class works with a member int. Now your sub-type requires that int to be positive. This is strengthened pre-conditions, and now any code that worked perfectly fine before with negative ints is broken.

If I'm searching in the wrong direction here, please let me know. Also, what if a subtype has many more parameters to handle, how would one normally manage those, is a new abstraction always nessacery?



Solution 1:[1]

When a class X has a constructor, the constructor is not a method on objects of type X. Since it is not a method on objects of type X, it doesn't have to exist as a method on objects of derived types, either -- it's irrelevant to LSP.

Solution 2:[2]

Doesn't this violate one of the requirements below?

It would depend on the language; but at least in Java, constructors are not inherited, so their signatures are not subject to the LSP, which governs inheritance.

Subtypes work best for modifying behavior (of a supertype). This is known as polymorphism, and subtypes do it well (when the LSP is followed). Subtypes work poorly for code reuse, such as sharing variables. This is the idea behind the famous principle, prefer composition over inheritance.

Solution 3:[3]

The intent of the Liskov Substitution Principle is that types and their subtypes should be substitutable, which in turn allows decoupling. Consumers shouldn't have to know the implementation of an object, only its declared type. If a class creates its own dependency by calling its constructor, it is coupled to that specific type. In that case the LSP becomes irrelevant. There's no way to substitute another type, so it doesn't matter if the types are substitutable or not.

Put another way - a class that creates an instance of another class generally cannot benefit from the LSP, because it mostly rules out the possibility of substituting one type for another. If it passes that object to other methods which interact with it in ways other than creating it, that's where we benefit from the LSP.

Based on that reasoning, I'd say that varying constructors don't violate the intent of the Liskov Substitution Principle.

In many languages we use dependency injection to decouple classes from the construction of dependencies. That means consumers deal with every aspect of a type except for its constructor. The constructor is out of the equation and subtypes are (or should be) substitutable for the types from which they inherit.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 jaco0646
Solution 3