2010-05-22 6 views
1

Je suis actuellement confronté à un problème très inquiétant:Generics avec des contraintes hiérarchie

interface IStateSpace<Position, Value> 
where Position : IPosition   // <-- Problem starts here 
where Value : IValue     // <-- and here as I don't 
{         //  know how to get away this 
            //  circular dependency! 
            //  Notice how I should be 
            //  defining generics parameters 
            //  here but I can't! 
    Value GetStateAt(Position position); 
    void SetStateAt(Position position, State state); 
} 

Comme vous le ici, à la fois IPosition, IValue et IState dépendent les uns des autres. Comment suis-je censé m'en tirer? Je ne peux pas penser à un autre design qui contournera cette dépendance circulaire et décrit toujours exactement ce que je veux faire!

interface IState<StateSpace, Value> 
where StateSpace : IStateSpace  //problem 
where Value : IValue     //problem 
{ 
    StateSpace StateSpace { get; }; 
    Value Value { get; set; } 
} 

interface IPosition 
{ 
} 

interface IValue<State> 
where State : IState {  //here we have the problem again 
    State State { get; } 
} 

Fondamentalement, j'avoir un espace d'état IStateSpace qui a des états IState à l'intérieur. Leur position dans l'espace d'état est donnée par un IPosition. Chaque état a ensuite une (ou plusieurs) valeurs IValue. Je simplifie la hiérarchie, car c'est un peu plus complexe que décrit. L'idée d'avoir cette hiérarchie définie avec des génériques est de permettre différentes implémentations des mêmes concepts (un IStateSpace sera implémenté à la fois comme une matrice comme un graphique, etc.).

Est-ce que je peux m'en tirer avec ça? Comment résolvez-vous généralement ce genre de problèmes? Quels types de modèles sont utilisés dans ces cas?

Merci

Répondre

3

Il n'est pas tout à fait clair quel est le problème - oui, vous avez des dépendances circulaires dans vos types génériques, mais cela fonctionne.

J'ai un "problème" similaire dans Protocol Buffers: J'ai "messages" et "constructeurs", et ils viennent par paires. Ainsi, les interfaces ressemblent à ceci:

public interface IMessage<TMessage, TBuilder> 
    where TMessage : IMessage<TMessage, TBuilder> 
    where TBuilder : IBuilder<TMessage, TBuilder> 

et

public interface IBuilder<TMessage, TBuilder> : IBuilder 
    where TMessage : IMessage<TMessage, TBuilder> 
    where TBuilder : IBuilder<TMessage, TBuilder> 

Il est certainement laid, mais ça marche. Que voulez-vous pouvoir exprimer que vous ne pouvez pas exprimer actuellement? Vous pouvez voir certaines de mes pensées à ce sujet on my blog. (Les parties 2 et 3 de la série sur les tampons de protocole sont les plus pertinentes ici.)

(En aparté, il serait si vous ajoutez un préfixe T à vos paramètres de type code plus conventionnel. Il ressemble actuellement State et Value ne sont que des classes.)

5

Je ne vois pas ce que vous essayez d'atteindre - pourquoi ne voulez-vous forcer types concrets dans vos interfaces en utilisant les génériques? Cela semble complètement contraire à l'intention des interfaces - ne pas utiliser des types concrets. Quel est le problème avec la définition non générique suivante?

public interface IStateSpace 
{ 
    IState GetStateAt(IPosition position); 
    void SetStateAt(IPosition position, IState state); 
} 

public interface IState 
{ 
    IStateSpace StateSpace { get; } 
    IValue Value { get; set; } 
} 

public interface IPosition 
{ 
} 

public interface IValue 
{ 
    IState State { get; } 
} 

Ensuite, vous pouvez créer des implémentations concrètes.

internal class MatrixStateSpace : IStateSpace 
{ 
    IState GetStateAt(IPosition position) 
    { 
     return this.Matrix[position]; 
    } 

    void SetStateAt(IPosition position, IState state) 
    { 
     this.Matrix[position] = state; 
    } 
} 

internal class GraphStateSpace : IStateSpace 
{ 
    IState GetStateAt(IPosition position) 
    { 
     return this.Graph.Find(position).State; 
    } 

    void SetStateAt(IPosition position, IState state) 
    { 
     this.Graph.Find(position).State = state; 
    } 
} 

Et vous pouvez décider d'utiliser ou MatrixStateSpaceGraphStateSpace cas où jamais une instance IStateSpace est nécessaire. La même chose vaut bien sûr pour tous les autres types.

La partie délicate de l'implémentation est lorsque vous devez créer de nouvelles instances. Par exemple, il peut y avoir une méthode IState AddNewState() définie dans IStateSpace. Toute implémentation concrète de IStateSpace fait face au problème de création d'une instance IState (idéalement) sans connaître de type concret implémentant IState. C'est là que les usines, l'injection de dépendance et les concepts connexes entrent en jeu.