[RFC] Modularize the tree class in both Python and Cython to enable easy extensions

### Describe the workflow you want to enable

As we are waiting for a reviewer to review #22754 , @thomasjpfan suggested we just move forward with our goals of creating a package of more exotic tree splits. E.g. https://arxiv.org/abs/1909.11799. While we wait for reviewers, the suggestion is to make a package within `scikit-learn-contrib`. 

Although we would like for #22754 to be eventually merged into scikit-learn, we understand reviewer backlog is an issue. To move forward while reviews occur, we would need to subclass existing scikit-learn code. Ideally, we would like to introduce minor refactoring changes that would make this task significantly easier.

We would like to subclass directly from scikit-learn without requiring us to keep an up-to-date fork of scikit-learn with all the bug fixes and maintenance that the dev team here does. We can limit this if we modularize the Python/Cython functions inside the `sklearn/tree` module.

### Describe your proposed solution

I am proposing two refactoring modifications that have no impact on the performance of the current tree estimators in scikit-learn.

1. Refactor the `BaseDecisionTree` Python class to have the following functions that can be overridden in a subclass:

- `_set_tree_class`: sets the Tree Cython class that the Python API uses
- `_set_splitter`: sets the Splitter Cython class that the Python API uses

For example, this makes the subclassing of BaseDecisionTree cleaner: https://github.com/scikit-learn/scikit-learn/blob/de06afa78458d773d7c0482ff17a55a235d0886b/sklearn/tree/_classes.py#L410-L416.

2. Refactor the `Tree` Cython class to have the following functions:

- `_set_node_values`: transfers split node values to the storage node
- `_compute_feature_value`: uses the storage node and the input data to compute the feature value to split on

For example, see https://github.com/scikit-learn/scikit-learn/blob/de06afa78458d773d7c0482ff17a55a235d0886b/sklearn/tree/_tree.pyx#L770-L787.

3. Refactor the `TreeBuilder` Cython class to pass around a `Split` pointer, rather than the struct itself

This will enable someone to use C-level functions to pass around another struct with a similar structure as `Split`.

For example, see https://github.com/scikit-learn/scikit-learn/blob/de06afa78458d773d7c0482ff17a55a235d0886b/sklearn/tree/_tree.pyx#L193.

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

Alternatives would require maintaining a copy of the `sklearn/tree` module and keep it up-to-date w/ sklearn changes. If this was just one Cython file, I would say it is possible, but the necessary ingredients span some of the underlying private API, making this a very time-consuming task. Introducing modularity into the private API that does not impact existing performance, therefore seems to be the best path forward?

Moreover, by introducing these refactoring changes, #22754 has a smaller diff and lower-cost to review.

### Additional context

**#22754 demonstrates that there is no performance regression, or issues w/ existing DecisionTree, or RandomForest when introducing these changes.**

COMMENT:
Hey @jjerphan thanks for the reply!

I think for me I'm faced with a few obstacles and I'm hoping for some clarity here. Happy to discuss at OH whenever they restart...

The original issue #20819 has a few concerns that were cleared up: i) citation criterion and ii) maintainability due to stiffness of the tree code. I addressed i) and then I spent a lot of time figuring out how to make Oblique Trees inherit directly from the RF code to lower the LOC to be added. Then once I got the PR setup, the dev team requested a number of experiments to support this addition. Now those are documented in the PR #22754 . Then it was said that this would require a maintainer to review the code and everyone is busy and suggested that I try to add this to scikit-learn-contrib to allow us to build off the work that was done here. Totally understand that the dev team has other goals beyond what I want and they are busy, but I want to see what's the best path forward since there's a considerable amount of work done here to make sure a 10k+ cited paper with considerable empirical evidence is compatible with scikit-learn tree code. If the concern is maintainability, I'm happy to ask @jovo if his lab is willing to maintain this part of the code since they are one lab that is invested in extending oblique trees beyond what was proposed by Breiman.

The two options that were floated to me are:
1. PRing oblique trees into scikit learn: It seems the inclusion is now mainly dependent on a review cycle, which it seems devs would prefer not to do if it is a big PR(?) I'm not sure how else to improve the code, but I'm willing to chop up the PR into smaller digestible bites. I offered to help refactor the Cython and Python code base to enable extensions that allow generalizations to the splitter but haven't gotten a reply :/.

2. Oblique Trees as a separate scikit-learn contrib package for now: it was suggested this can help me move along with some of my projects while waiting for reviews on the direct PR of oblique trees. However this then relies on the suggested refactoring I noted above. However, it seems even this refactoring to enable scikit-learn tree code to be more generalizable is facing considerable bottlenecks.

The refactoring would not change any functionality based on the gif you sent. I understand tho this is OSS and the dev team is pretty busy with consolidation of the package. So anything I can do to help either the PR itself or the scikit-learn-contrib package move along, I'm happy to do so. Also happy to contribute more to the general scikit-learn package so the devs time is freed up. I would def appreciate any guidance here. 

Lmk if I'm perhaps misunderstanding anything.

Labels: New Feature, module:ensemble, module:tree, cython, Needs Decision - Include Feature, Refactor