There is some truth here, principally that software evolves and design has to adapt with it. However I think you largely misdirect your critique.
If we start with Single Responsibility... yes, you can try and illustrate this with Calculator, but future extension or divergence is not really the problem we're trying to solve. The main problem is developer tendency to collect multiple responsibilities in one place - a Calculator class that also outputs JSON or formats some text or stores the history in a database. Both you and the people that (probably wrongly) argue that it does four things are missing this and focusing in on its definition in isolation.
In the Liskov example, again you are (probably over)thinking about extension but it's not the problem. The problem is building other classes that talk in unnecessarily constrained terms of Fish when Animal would be fine, and all of the further developmental consequences that derive from this. Also, there is probably nothing wrong with Sessile being an Animal and having a no-op 'move' method. You make this kind of decision at the point you need to.
In the Open example, this a straight misunderstanding. It's easy to forget what this (from 1988) is solving, given modern languages that encapsulate a lot of this. This is basically why we have private methods and why everything in Kotlin is 'final' by default - so we allow and provide clarity on extension, and discourage divergent modification.
Additionally, what you should really take from your exercise is that your interface design was bad.
interface Operation {
fun compute(): Int
}class Add(private val v1: Int, private val v2: Int): Operation {
override fun compute() = v1 + v2
}
class Sub(private val v1: Int, private val v2: Int): Operation {
override fun compute() = v1 - v2
}
class Divide(private val v1: Int, private val v2: Int): Operation {
override fun compute() = v1 / v2
}
class Inverse(private val value: Int): Operation {
override fun compute() = Divide(1, value).compute()
}class Calculator {
fun calculate(op: Operation): Int {
return op.compute()
}
}
It's easy to get caught up in the detail, so what I will tell you is this: SOLID is not there to magically remove the need for future modification. It's there to reduce the costs when modification happens. The problems you think it fails to solve are not the problems you will actually have. The problems you will have are not that someone was a bit short-sighted with the API design or class hierarchy, they are that years of developers built a tangled mess that is tightly coupled together in all kinds of different respects.
Finally, you are fundamentally wrong about this: "When that happens, the new requirements will make our original designs no longer adhere to SOLID principles"
Requirements aren't design. Design is how you deal with requirements. New requirements don't violate these principles unless you decide you will violate them in your new implementation and then fix it afterwards. When someone says, "yeah and now I want the calculator to output JSON", it's for you to figure out how to extend the product so that SOLID remains adhered to every step of the way.