Restructuring Suggestions to remove lots of generic constraints
I'm trying to f开发者_开发问答igure out if there's a better way to structure some code. Although it currently works, it always gets at me due to the complexity and crazy nature that it feels to have with generics constraints. I'd be interested to know if anyone has an idea for a neater solution.
I've included a class diagram to make things a little easier.
This is all going within a library, and I wanted to try and keep all my type safety where I can. The library has 3 layers of increasing complexity. Which I'll explain in a moment.
I'm using Generics to make all the types work. For example a Route is actually a List<T>
where T
is a Visit. Now because I've got the 3 layers, I want to be able to access properties on these Visits (and the Nodes that they correspond to) from the Route itself (and to make it easier to consume). So that actually makes a Route<Visit<Node>>
. Once you add that to a Solution which needs to be strongly typed Solution<Route<Visit<Node>>>
things get complicated.
This results in some kinda unslightly code:
public abstract class Solution<TSolution, TRoute, TVisit, TNode, TResource>
where TSolution : Solution<TSolution, TRoute, TVisit, TNode, TResource>, new()
where TRoute : Route<TRoute, TVisit, TNode, TResource>, new()
where TVisit : Visit<TVisit, TNode>, new()
where TNode : Element<TNode>
where TResource : Resource<TResource>
It all works nicely, but I have to define these constraints at each class/level. At each level of complexity I create some simple consumable class such as the following, essentially hiding the generic constraints which would make it impossible to consume.
Level1.Solution : Common.Solution<Solution, Route, Visit, Node, Resource>
They have also been made recursive based on advice from this question to allow me to extend the class. For example a Level2.Solution will needs to be able to specify a Level2.Route as one of the constraints, normally this won't work (co/contra variance and generics) without the recursion.
All in all, it works, but is a bit cringy to say the least. Anyone have any ideas how this can be re-worked nicely?
a Route is actually a List
I think your code will be much simplified if you prefer composition over inheritance. If a Route has a List, it will get much simpler, much easier to manage.
I think the problem here is that you try to combine them all in a single place. For example, you don't have to tell anything to Solution about Node and its contract but in your case you do. Try to to work with interfaces rather than generic constraints - you will still be able to create Solutions from different Routes that are combined from Resources and Nodes however in the end you will need just to specify concrete solution:
interface INode {}
class Node: INode {}
interface IVisit
class Visit: IVisit
{
Visit(INode node) {}
}
interface ISolution {}
class Solution: ISolution
{
Solution(IList<IRoute> routes)
{
}
}
I admit that I could be missing something, but looking at this question and your linked question, I get the feeling that this is all overly complicated. It looks like a case of syntaxophilia gone wild. When I run into something like this, it usually pays to sit back and think about what I'm modeling in simpler terms.
As I understand it, those generic constraints are just ensuring that the types are compatible among the Resource
, Node
, Visit
, Route
, and Solution
type parameters. All well and good, if highly complicated. The only "variables" there are your TResource
and TNode
types. What I mean is, you could have:
class Solution<TResource, TNode>
where TNode : Element<TNode>
where TResource : Resource<TResource>
{
// Construct properties for the Visit and Route, as required
}
Your examples indicate that a VSPSolution always has a VSPVisit and a VSPRoute, for example. If that's the case, then the composition solution here greatly simplifies things. In fact, if I more understood the purposes of TNode
and TResource
, you could probably eliminate those generic constraints, too.
The real question is whether the Visit
(or Route
) object has to exist outside of the Solution
. It looks to me like a pretty strict hierarchy: Solution
-> Route
-> Visit
, so it makes sense for the Solution
to contain the Route
, and for the Route
to contain the Visit
collection.
Doing it that way is much simpler, and a whole lot more clear what's going on.
精彩评论