Neither "transitive dependency towards the concrete elements" nor "supertype knowing about the subtypes" leads to a conclusion of LSP being violated. The first one is harmless. The second one is merely a smoking gun.
Visitor pattern can be used for several purposes. Judging from the way this question is asked, this answer will put the focus on the case of Double Dispatch.
Visitor pattern might also be used for other purposes. The possibility of those other purposes violating OCP or LSP is practically nil; therefore they will not be discussed in this answer.
In cases where the Visitor pattern is used to implement Double Dispatch (or equivalently to get around the lack of double dispatch in OOP languages), it is often said to be incompatible with Open/Closed Principle (OCP), although generally we can't say much about whether it violates Liskov Substitution Principle (LSP) unless there is an implementation mistake.
This article on Double Dispatch explains it better than I do.
An example of double dispatch is that you have one inheritance hierarchy of
Shape, and then a different inheritance hierarchy of
ISurface in the article linked above). The project requires an implementation of a polymorphic method, whose behavior varies with both the concrete
Shape subtype and the concrete
Display subtype. If you have 3 concrete
Shape subtypes and 5 concrete
Display subtypes, this requires 15 concrete implementations of the polymorphic method, i.e. it is a Cartesian product of implementations.
The most widely used OOP languages do not support Double Dispatch out-of-the-box. There are several ways to get around this; using the Visitor pattern is one way.
However, using the Visitor pattern to achieve Double Dispatch requires you to lock down one of the two inheritance hierarchies: you will not be able to add support for new subtypes to the locked-down inheritance hierarchy anymore (without modifying the supertype/interface). Using the above example, you have to choose either: (1) lock down the
Shape hierarchy, or (2) lock down the
Display hierarchy. This breaks the "open for extension" part of the OCP.
If the nature of one of the inheritance hierarchy is that it is "complete", i.e. there will never be a future necessity to add any new subtypes to that hierarchy, then locking it down is not an issue, so it does not violate OCP.
There is an alternative implementation of Double Dispatch that does not break OCP, i.e. that will allow adding new subtypes to both the
Display inheritance hierarchies, without modifying existing code. However, this alternative implementation does require checking the concrete types of both arguments via
Some would argue that checking the concrete argument types with
instanceof is itself an antipattern, or a smell, or something undesirable. This is somewhat an exaggeration of the original opinions held by Java mentors, which was to prefer polymorphism over instanceof and downcasting, not to outright forbid it even when necessitated by software requirements.
This brings up another discussion. In light of agile methodology, it is often the case that new software requirements necessitate code changes to certain parts of the project that were once considered immutable. In other words, breaking changes are sometimes needed.
From this perspective, the Open/Closed Principle (OCP) itself often came up as being too rigid/inflexible.
In the past, violation of OCP will cause harm to compiled libraries (binaries) which are then packaged and shipped. Propagating breaking changes in upstream dependencies often has a ripple effect that result in a large number of compiled libraries to be updated.
Agile methodology intends to solve this issue with:
- Versioning (leave the old system in place, but introduce the new system as an alternative, while making both available to customers)
- Continuous integration (literally, recompile everything, every time)
- Open source (customers can see and modify the source code inside dependencies)
Agile methodology also views violations of Liskov Substitution Principle in a different way. Instead of branding a violation as a sign that the system or design is unsafe or having questionable correctness, it is merely seen as being a pain point. So, a LSP violation is not a death knell, but is a bad mark that adds up.
There are plenty of new software requirements which require the violation of LSP. In the past, such feature requests will be rejected on the grounds of correctness. Nowadays, it will be given business considerations. If a violation of LSP is accepted, then it shall be properly documented.
The additional cost of documenting an LSP violation, the displeasure of a programmer caught by the astonishment of software breakage (due to not heeding the documented warning), and the software maintenance and support overhead stemming from such breakage, are seen as business costs that are weighted against the business gains from implementing the requested feature.