In addition to other comments, keep in mind that these options aren‘t necessarily mutually exclusive. As a simple example for that, you can combine approach 1 and 3 into a pretty flexible system:
Have a dispatch-map and -fn like in your post‘s first example, but make the function call a multimethod when resolution from the map fails. This way you have a sort-of-overwrite mechanism apart from what multimethods offer (which could be for extension from the outside, with the „overwrite“ map ensuring some reserved names and/or functionality around the multimethod).
If you spin this further, combining multiple multimethods in your dispatch-fn approaches protocols conceptually, and it’s good to think about whether or not they are a good tool for a given purpose. There‘s a lot of pros and cons to „building your own“ dispatch ensemble, as there are to using Clojure‘s facilities „as-is“ (records, multimethods, protocols, …) or on their own.