2010-10-14 4 views
3

En essayant de modéliser mon domaine, j'ai rencontré le problème suivant. image Let nous avons une chose:Comment concevoir autour de l'absence de const en C#/Java?

class Thing 
{ 
    public int X { get; set; } 
} 

Les choses ont un X. propriété Ensuite, il y a des packs, que les choses globales. Mais le domaine exige qu'il y ait une certaine restriction sur les choses que les Packs peuvent contenir. Que ce soit par exemple que la valeur cumulée des Xes ne peut pas être plus élevé alors une limite spécifique:

class Pack 
{ 
    private readonly List<Thing> myThings = new List<Thing>(); 
    private const int MaxValue = 5; 

    public void Add(Thing thing) 
    { 
     if (myThings.Sum(t => t.X) + thing.X > MaxValue) 
      throw new Exception("this thing doesn't fit here"); 
     myThings.Add(thing); 
    } 

    public int Count 
    { 
     get { return myThings.Count; } 
    } 

    public Thing this[int index] 
    { 
     get { return myThings[index]; } 
    } 
} 

donc je vérifie avant d'ajouter une chose à un pack pour la condition, mais il est encore si facile d'entrer dans problèmes:

var pack = new Pack(); 
pack.Add(new Thing { X = 2 }); 
pack.Add(new Thing { X = 1 }); 

var thingOne = new Thing { X = 1 }; 
var thingTwo = new Thing { X = 3 }; 

//pack.Add(thingTwo); // exception 
pack.Add(thingOne); // OK 

thingOne.X = 5;  // trouble 
pack[0].X = 10;  // more trouble 

En C++ la solution serait de faire une copie lors de l'insertion et de renvoyer la référence const dans l'indexeur. Comment concevoir autour de ce problème en C# (et probablement Java)? Je ne peux pas sembler penser à une bonne solution:

  1. rendre Thing immuable - mais que faire si il doit être mutable? Regarder les Things in Pack avec un événement/un observateur - mais cela signifie que Pack impose la conception de Thing; Et si les choses ont plus de propriétés? Ensuite, je finirai avec un seul événement en raison de la nécessité pour Pack de surveiller les changements - cela me semble étrange.

Des idées ou des solutions préférées?

EDIT:

Pour en revenir à cette question ... Je l'ai accepté la réponse de Itay. Il a raison. Le problème initial était que, d'un contexte, vous vouliez qu'un objet Thing soit immuable, et que vous vouliez le rendre mutable dans un contexte différent. Et cela appelle une interface séparée ... peut-être. J'ai dit "peut-être", parce que la plupart du temps, Pack serait un agrégat de choses (au sens DDD) et donc le propriétaire des objets - ce qui signifie qu'il ne devrait pas vous donner la possibilité de changer l'objet possédé (soit renvoie une copie ou renvoie une interface immuable).

C'est sympa qu'en C++ cette chose particulière puisse être manipulée si facilement par le modificateur const. On dirait beaucoup moins de codage si vous voulez garder les choses dans un état cohérent.

+0

Pouvez-vous poster du code C++ pour montrer ce que vous essayez d'accomplir? – CadentOrange

+0

J'essaie d'interdire la rupture de la cohérence de l'objet Pack. – Stefan

+0

J'ai édité ma réponse - vous pouvez utiliser des interfaces pour obtenir la même fonctionnalité. –

Répondre

6

Rendre Thing immuable.

class Thing 
{ 
    public Thing (int x) 
    { 
     X = x; 
    } 
    public int X { get; private set; } 
} 

En outre, au lieu de if (myThings.Sum(t => t.X) + thing.X > MaxValue) je pense qu'il est préférable de maintenir un champ de somme en pack de sorte que vous n'avez pas à recalculer la somme à chaque fois.

EDIT
Désolé - j'ai raté le fait que vous avez déclaré que vous en avez besoin mutable.
Mais ... Comment fonctionnerait votre solution C++? Je ne connais pas beaucoup de C++ mais la référence constante C++ ne va-t-elle pas empêcher les changements sur les instances du Pack?

EDIT2
Utilisez une interface

public interface IThing 
{ 
    int X { get; } 
} 

public class Thing : IThing 
{ 
    int X { get; set; } 
} 

class Pack 
{ 
    private readonly List<IThing> myThings = new List<IThing>(); 
    private const int MaxValue = 5; 

    public void Add(IThing thing) 
    { 
     if (myThings.Sum(t => t.X) + thing.X > MaxValue) 
      throw new Exception("this thing doesn't fit here"); 
     myThings.Add(new InnerThing(thing)); 
    } 

    public int Count 
    { 
     get { return myThings.Count; } 
    } 

    public IThing this[int index] 
    { 
     get { return myThings[index]; } 
    } 

    private class InnerThing : IThing 
    { 
     public InnerThing(IThing thing) 
     { 
     X = thing.X; 
     } 
     int X { get; private set; } 
    } 
} 
+0

En C++, si vous avez une liste de références const à Pack, vous ne pourrez pas faire le pack [0] .setX (X) si setX a été déclaré const alors que vous modifiez la valeur de X et cela déclenchera un erreur de compilation.Cependant, rien ne vous empêche d'ajouter thingOne au pack et de modifier thingOne directement. – CadentOrange

+0

@Phillip - Merci –

+0

Merci pour votre réponse, mais je ne pense pas que ce soit une bonne solution. Je pense que vous êtes sur le point d'abuser de l'interface (je ne pense pas qu'ils étaient destinés à ce que vous essayez d'atteindre). La deuxième chose est que je ne peux tout simplement pas contourner et faire des copies de choses si facilement, car ils ont une certaine identité (ils sont chargés à partir de la couche d'accès aux données). – Stefan

1

Pour les objets simples que vous pouvez les rendre immuable. Mais n'en abusez pas. Si vous faites des objets complexes immuables, vous devrez probablement créer une classe de construction pour que vous puissiez travailler avec. Et cela ne vaut généralement pas le coup.

Et vous ne devriez pas faire des objets qui ont une identité immuable. Généralement, vous rendez un objet immuable si vous voulez qu'il se comporte comme un type de valeur.

Dans l'un de mes projets, j'ai piraté l'immuabilité en donnant à la classe de base uniquement des getters et en cachant les setters dans la classe dérivée. Ensuite, pour rendre l'objet immuable, je l'ai jeté à la classe de base. Bien sûr, ce type d'immutabilité n'est pas appliqué par le moteur d'exécution et ne fonctionne pas si vous avez besoin de votre hiérarchie de classes à d'autres fins.

+0

Ces objets ont une identité, donc les rendre immuables n'est pas une solution - merci de le signaler! – Stefan

0

Malheureusement il n'y a rien en C# qui ait autant de puissance que const en C/C++ pour s'assurer (au moment de la compilation) qu'une valeur ne sera pas changée. La seule chose qui leur vient presque est l'immutabilité (comme mentionné par vous-même et Itay), mais il n'a pas la flexibilité const. :(

Si vous en avez besoin mutable dans certains scénarios vous ne pouvez faire quelque chose à l'exécution comme celui-ci

class Thing 
{ 
    public bool IsReadOnly {get; set;} 

    private int _X; 
    public int X 
    { 
     get 
     { 
      return _X; 
     } 
     set 
     { 
      if(IsReadOnly) 
      { 
       throw new ArgumentException("X"); 
      } 

      _X = value; 
     } 
    } 
} 

mais il pollue votre code avec try/catch et en changeant la valeur de IsReadOnly. Donc, pas très élégant, mais peut-être quelque chose à envisager.

+0

Je suppose qu'il n'y a aucun moyen de ne pas polluer la classe Chose, malheureusement. – Stefan

2

Après vos déclarations que vous avez vraiment une certaine logique commerciale derrière le X qui doit également être appliquée lorsque X est réglé. vous devez réfléchir à la façon dont vous concevez X si elle est censée déclencher som la logique métier. C'est plus qu'une simple propriété.

Vous pourriez envisager les solutions suivantes:

  • Avez-vous vraiment besoin pour permettre de changer X? Même si la classe n'est pas immuable, vous pouvez toujours faire X en lecture seule.
  • Fournissez une méthode SetX(...) qui encapsule la logique métier pour vérifier si Thing fait partie d'un Pack et appelle cette logique de validation Pack.
  • Bien sûr, vous pouvez utiliser la propriété set { } à la place de la méthode setter pour cette logique.
  • Dans Pack.Add, créez une copie du Thing, qui est en fait une sous-classe qui ajoute la logique de validation.
  • ...

Quoi que vous fassiez, vous ne serez pas contourner la question suivante de conception très simple: Soit vous permettre de changer X, alors vous devez introduire la logique de validation/d'affaires dans toutes les régions qui permet de changer X. Ou vous faites X en lecture seule et adaptez les parties de l'application qui pourraient vouloir changer X en conséquence (par exemple supprimer un ancien objet, en ajouter un nouveau ...).

+0

"(par exemple supprimer un objet ancien, ajouter un nouveau ...)" Excellent point chiccodoro, je n'ai pas pensé à cela dans ma réponse. Les choses pourraient être faites pour être en lecture seule objets de valeur et détruit/créé lorsque la valeur doit changer. +1 – Tony

+0

Fair points, merci pour la réponse. En réalité, X est de paire à date et les Packs ne peuvent pas contenir de chevauchement, donc il n'y a pas beaucoup de logique derrière cela. Et oui, je veux pouvoir modifier ces dates. – Stefan

+0

Salut Stefan. Ne pas autoriser les dates qui se chevauchent * est une * logique métier. Si vous avez besoin de changer les dates par la suite, alors le const en C++ ne vous servira à rien, n'est-ce pas? Vous devrez garder une référence au Pack dans votre Chose ou soulever un évènement comme vous l'avez déjà mentionné dans votre question, pas question ... – chiccodoro

0

Je envisagerais de revoir la façon dont vous utilisez le Pack.

Si vous supprimez la chose que vous souhaitez modifier de la collection, et non seulement autorisez un accès direct, elle sera vérifiée lors de son retour.

Bien sûr, l'inconvénient est que vous devez vous rappeler de le remettre dans le Pack!

Vous pouvez également fournir des méthodes sur le Pack pour modifier les choses, de sorte que vous pouvez vérifier que la valeur définie ne viole pas les règles de domaine.

+0

Pour faire le premier travail de conception je suppose que je devrais interdire d'obtenir la référence aux choses dans le paquet spécifique. Mais cela m'interdirait même d'énumérer le Pack. – Stefan

+0

Oui, cela empêcherait le dénombrement. Pourquoi ne pas en faire des objets de valeur en lecture seule? Lorsque vous voulez changer la valeur, construisez un nouvel objet et détruisez l'ancien, comme suggéré par chiccodoro. Cela nécessitera le retrait et la réinsertion dans le Pack, mais permettra de vérifier la valeur modifiée. – Tony

+0

Autre chose à considérer, c'est peut-être le fait de permettre l'obtention/la définition d'une variable membre qui est la cause de votre dilemme de conception. Voici un article intéressant sur les méthodes getter/setter (il parle de Java mais les principes sont applicables à n'importe quelle langue): http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html – Tony

3

Vous pourriez avoir besoin d'implémenter l'interface iReadOnlyThing, qui n'a accès à ReadOnly qu'à chacune des propriétés de Thing.

Cela signifie implémenter la plupart de vos propriétés deux fois, et cela signifie également que vous faites confiance à d'autres codeurs pour respecter l'utilisation de l'interface iReadOnlyThing et ne pas renvoyer d'iReadOnlyThings à Things.

Vous pouvez aller jusqu'à la mise en œuvre d'une classe de conteneur en lecture seule de la même manière que

System.Collections.ObjectModel.ObservableCollection<T> 

et

System.Collections.ObjectModel.ReadOnlyObservableCollection<T> 

faire. Le constructeur de ReadOnlyObservableCollection prend un ObservableCollection comme argument et autorise l'accès en lecture à la collection sans autoriser l'accès en écriture. Il a l'avantage de ne pas permettre à ReadOnlyObservableCollection d'être renvoyé à une ObservableCollection. Je pense que je dois souligner que le modèle Model-View-ViewModel utilisé dans le développement de WPF fonctionne, en fait sur le principe que vous suggérez, d'avoir un seul événement NotifyPropertyChanged, qui contient une chaîne identifiant la propriété qui a changé ...

Les frais généraux de codage de modèle MVVM est significatif, mais certaines personnes semblent aimer ...

+1

Merci d'avoir suggéré NotifyPropertyChanged. Semble une solution raisonnable, bien que je n'aime pas la conception résultante. – Stefan

+1

Je dois admettre que la transition entre les connexions logiques codées en dur et le monde basé sur le texte de WPF et XAML est inquiétante. Par exemple, "myThing" comme "myThnig" comme source de données dans le code XAML, par exemple, ne produit aucune des erreurs de compilation qui résulteraient normalement d'une telle erreur dans le code "réel"; les écouteurs sur le contrôle WPF commencent simplement à écouter les gestionnaires d'événements pour les objets inexistants ... On pourrait penser que cela ressemblerait à un bug pour quelqu'un, mais apparemment c'est en fait une fonctionnalité ... – Frosty840

1

que diriez-vous d'avoir l'événement Changement de chose; Le Pack s'abonne à cet événement et lorsque vous modifiez X dans Thing, Pack effectue la vérification nécessaire dans le gestionnaire d'événements et si le changement ne correspond pas; jette une exception?

+0

Oui, semble la meilleure solution pour le problème . Mais comme je l'ai mentionné, rend l'interface de la Chose un peu gênant. – Stefan