2010-11-22 6 views
2
Descendant

J'ai les classes suivantesDesign Classe - Classe de base avec générique

class GridBase 
    { 
    public object DataSource { get; set; } 
    } 

    class GenericGrid<T> : GridBase 
    { 
    public new T DataSource { get; set; } 
    } 

les deux classes de grille GridBase et générique peut être instancié et l'on peut descendre soit aussi bien. Est-ce considéré comme le moyen correct/accepté de mettre en œuvre une telle hiérarchie? Ou devriez-vous aller le mile supplémentaire et mettre en œuvre comme le

class GridBase 
    { 
    protected object dataSource; 
    public object DataSource { get { return dataSource; } set { dataSource = value; } } 
    } 

    class GenericGrid<T> : GridBase 
    { 
    public new T DataSource { get { return (T)dataSource; } set { dataSource = value; } } 
    } 

suivant est de même pour les classes non génériques lorsqu'une propriété est réintroduite dans un descendant, je suis juste en utilisant un exemple générique ici.

Une autre affaire et la question

abstract class SomeBase 
    { 
    protected abstract void DoSomething(); 
    } 

    class Child : SomeBase 
    { 
    protected override void DoSomething() 
    { 
     /* Some implementation here */ 
    } 
    } 

La situation ici est que le cadre « X » déclare UneBase vous permettant de définir vos propres descendants. Les classes qu'ils créent (au moment de l'exécution) puis descendent de votre classe (Child dans le cas présent). Cependant, ils n'appellent pas votre méthode DoSomething(), à partir de leur implémentation de DoSomething().

Pour leur part, ils ne peuvent pas appeler aveuglément base.Dosomething() soit parce que le cas typique est que la classe qu'ils génèrent normalement descend de SomeBase et puisque la méthode est abstraite qui n'est pas valide. (Personnellement, je n'aime pas ce comportement en C#).

Mais de toute façon, ce design est-il bon ou accepté, cela n'appelle pas base.xxx(), surtout quand l '"intention" semble contredire?

EDIT Du point de vue de la conception du cadre. Est-ce acceptable/acceptable que cela fasse ça? Sinon, comment serait-il conçu de manière à empêcher un tel cas ou mieux de communiquer leur intention (dans les deux cas).

+2

Votre deuxième question devrait être une question distincte. – jason

Répondre

1

La deuxième forme de l'héritage générique (coulée de l'attribut de classe de base) est plus correct car il ne viole pas Liskov Substitution Principle. Il est concevable qu'une instance de la classe générique soit transtypée en classe de base et que l'accès aux données via la classe de base pointe vers une propriété différente. Vous devrez garder les deux synchronisés afin que la classe dérivée soit substituable pour la classe de base.

Alternativement, vous pouvez implémenter une sorte de modèle de stratégie où la classe de base demande la propriété Data de la classe dérivée, afin d'éviter la downcasting maladroite. Voici ce que j'avais à l'esprit:

public class Base { 
    private readonly object m_Data; //immutable data, as per JaredPar suggestion that base class shouldn't be able to change it 

    publlic Base(object data) { 
    m_Data = data; 
    } 


    protected virtual object GetData() {return m_Data;} 

    public Object DataSource {get {return GetData();}} 
} 

public class Derived<T> : Base { 
    private T m_Data; 

    public Derived():base(null){} 
    protected override object GetData() {return m_Data;} 

    protected new T Data {return m_Data;} 
} 

En ce qui concerne la deuxième question, je suis sûr de bien comprendre la question. Cela ressemble au problème que vous rencontrez avec le framework qui n'appelle pas la méthode abstraite lorsqu'il génère un proxy lors de l'exécution, ce qui est toujours légal dans les classes abstraites, car le seul moyen d'exécuter ce code est une classe dérivée qui doit remplace la méthode abstraite.

+0

En ce qui concerne le second cas, vous semblez le comprendre correctement. Cependant, lorsque le proxy est créé, il descend de Child et Child a une implémentation de DoSomething(). Donc, dans une telle situation, je m'attendrais à ce que la classe générée appelle base.DoSomething() à un moment donné, c'est sa propre implémentation de DoSomething(). Et si ce n'est pas le cas, comment communiqueriez-vous cela via le design? –

+0

Je ne pense pas que vous pouvez concevoir autour de la structure n'appelant pas la classe de base. En fonction de ce que c'est (par exemple Entity Framework), vous devrez peut-être modifier les modèles générant les classes proxy. –

+0

la question est plus sur la façon dont le cadre devrait être conçu. Ou comment le cadre pourrait mieux véhiculer ou prévenir de telles situations en utilisant une meilleure conception (si leur mise en œuvre est considérée comme «c'est bien»). –

1

je préférerais quelque chose comme ceci:

interface IGrid { 
    object DataSource { get; } 
} 

interface IGrid<T> { 
    T DataSource { get; } 
} 

public Grid : IGrid { 
    public object DataSource { get; private set; } 

    // details elided 
} 

public Grid<T> : IGrid<T> { 
    public T DataSource { get; private set; } 
    object IGrid.DataSource { get { return this.DataSource; } } 

    // details elided 
} 

Notez que je ne suis pas héritant de Grid.

2

Pour la DataSource question que je préfère le modèle suivant

abstract class GridBase { 
    public abstract object DataSource { get; } 
} 

class GenericGrid<T> : GridBase { 
    private T m_data; 

    public override object DataSource { 
    get { return m_data; } 
    } 
    public T DataSourceTyped { 
    get { return m_data; } 
    set { m_data = value; } 
    } 
} 

Raisons

  • comportant l'élément GridBase.DataSource être accessible en écriture est de type dangereux. Il me permet de rompre le contrat de GenericGrid<T> en mettant la valeur à non-T instance
  • Ceci est plus d'une question d'opinion, mais je n'aime pas l'utilisation de new car il confond souvent les utilisateurs. Je préfère le suffixe ~ Type "pour ce scénario
  • Ceci nécessite seulement que les données soient stockées une fois
  • Ne nécessite pas de coulée non sûre.

EDIT OP corrigé que GridBase et GenericGrid sont les deux types utilisables

Dans ce cas je vous dire besoin de reconsidérer votre conception un peu. Les avoir tous les deux en tant que types utilisables vous ouvre à des erreurs de type très faciles à exposer.

GenericGrid<int> grid = new GenericGrid<int>(); 
GridBase baseGrid = grid; 
baseGrid.DataSource = "bad"; 
Console.Write(grid.DataSource); // Error!!! 

La conception sera beaucoup plus fiable si séparer le stockage de l'accès des valeurs d'une manière comme mon échantillon original. Vous pouvez étendre plus loin avec le code suivant pour un utile contenant non générique

class Grid : GridBase { 
    private objecm m_data; 
    public override object DataSource { 
    get { return m_data; } 
    } 
    public object DataSourceTyped { 
    get { return m_data; } 
    set { m_data = value; } 
    } 

} 
+0

J'aurais dû mentionner que GridBase et GenericGrid sont des classes utilisables, c'est-à-dire que vous pouvez instancier les deux. Je vais modifier ma question –

+0

J'irais encore plus loin et je proposerais que GridBase soit générique aussi. Cela éviterait à la fois le mot-clé 'new' et le besoin d'une seconde propriété' ~ Typed'. –

+0

@James B qui empêcherait le stockage du concept abstrait de 'GridBase'. Par exemple, imaginez que je voulais stocker un tas de valeurs 'GridBase' et les imprimer plus tard. Avec 'GridBase' étant générique, il n'y a aucun moyen de le faire pour toutes les instances' GridBase 'possibles. – JaredPar

Questions connexes