Stable extender contract via `fit` / `_fit` resp `predict` / `_predict` separation

### Describe the workflow you want to enable

tl;dr, I am suggestion to refactor `scikit-learn` internals to a layer separation with boilerplate between `fit` and `_fit` resp `predict` and `_predict` methods, to make extender interfaces more stable. Also see https://github.com/scikit-learn/scikit-learn/issues/31728

More background: Currently, every time `scikit-learn` releases a new minor version - e.g., 1.5.0, 1.6.0, 1.7.0 - compliant extensions, e.g., custom transformers, classifiers, etc, break - specifically, referring to the API conformance as tested through `check_estimator` or `parametrize_with_checks`.

These repeated breakages in the "extender contract" contrast the stability of the usage contract, which is stable and professionally managed.

For a package like `scikit-learn` which means to be a standard not just for ML algorithms but also an API standard that everyone uses, this is not a good state to be in - "do not break user code" is the maxim that gets broken for power users writing extensions.

Of course maintaining downwards compatibility is not always possible, but nothing should break without a proper warning.

### Describe your proposed solution

The `fit`/`_fit` separation would ensure stability of the extension contract - and would also allow to build secondary deprecation patterns in relation to it.

The (oop) pattern this would implement is the so-called "template pattern".

It would allow to remove likely changing parts such as the boilerplate (e.g., `validate_data` vs `_validate_data` and such) from the extension locus, and thus completely prevent breakage in relation to boilerplate changes.
Reference: https://refactoring.guru/design-patterns/template-method

Examples of how this can be used to improve stability:

* `sktime`, for a different API, has a separation between `fit` calling an internal `_fit`, where change-prone boilerplate is sandwiched between a stable user contract (`fit`) and a stable extender contract (`_fit`); similarly `predict` and `_predict`
* `feature-engine` overrides the `BaseTransformer` `scikit-learn` extension contract with a similar pattern using `super()` calls in `fit` etc.

The proposed `fit`/`_fit` pairing that combines strategy and template pattern can be introduced easily via pure internal refactoring - this also add a layer of tests that checks the stability of the extension contract.

As a side effect, this would likely reduce the amount of copy-pasted boilerplate among the core estimators in `scikit-learn` proper as well.

### Describe alternatives you've considered, if relevant

The obvious alternative is "do nothing" - which will lead to packages with extensions producing their own version of mitigating patterns for the unstable status quo.

The problem with this is that the extending packages are mutually incompatible with their extender pattern. It would be better if there is a single stable extension pattern prescribed - or at least recommended - by `scikit-learn`.

### Additional context

_No response_

COMMENT:
It sounds like the problem this issue is trying to address is the challenge of creating a third-party estimator that is considered compatible/conformant with several versions of scikit-learn at the same time.

One thing that would help me to think about this is a concrete example of splitting `fit` into `fit` and `_fit`, combined with a explanation of how this would have or would not have avoided a incompatibility in a third party estimator.

I'd like to avoid discussing possible solutions until we have a somewhat well defined problem/set of "test cases".

A good starting point would be a concrete list of what broke as alluded to in

> Currently, every time scikit-learn releases a new minor version - e.g., 1.5.0, 1.6.0, 1.7.0 - compliant extensions, e.g., custom transformers, classifiers, etc, break - specifically, referring to the API conformance as tested through `check_estimator` or `parametrize_with_checks`.

as well as example from other third-party libraries. Once we have a list of things that broke or needed adjusting in third-party libraries we can look at which of those were "one off" and which aren't. I think in the last few releases we have made some changes that I'd class as "one off" (eg. tag infrastructure reorganisation) that were done in order to make the developer API more well defined. The chances of such a change happening again are low, so we don't need to fix it. While these changes are disruptive and we should think about how often and severe we can make them, they are "unique snowflake" events.

Once we have some repeat offenders or repeat patterns of changes that disrupt third-party estimators we can then create an example `fit`/`_fit` (or `predict`/`_predict`) split and look at how many of those cases we can solve.

When it comes to creating third-party estimators that are compatible with several scikit-learn versions there are also tools like https://github.com/sklearn-compat/sklearn-compat

Labels: RFC, Developer API