47 votes

Immuable modèle objet en C# qu'en pensez-vous?

J'ai au cours de quelques-uns des projets mis au point un modèle pour la création d'immuable (readonly) objets et immuable graphes d'objets. Des objets immuables réaliser l'avantage d'être 100% "thread-safe" et peuvent donc être réutilisés dans les threads. Dans mon travail, j'ai très souvent l'utilisation de ce modèle dans les applications Web pour les paramètres de configuration et d'autres objets que j'ai charger et de mettre en cache en mémoire. Les objets mis en cache doit toujours être immuables que vous voulez vous assurer qu'ils ne sont pas modifié de façon inattendue.

Maintenant, vous pouvez facilement concevoir des objets immuables comme dans l'exemple suivant:

public class SampleElement
{
  private Guid id;
  private string name;

  public SampleElement(Guid id, string name)
  {
    this.id = id;
    this.name = name;
  }

  public Guid Id
  {
    get { return id; }
  }

  public string Name
  {
    get { return name; }
  }
}

C'est très bien pour de simples classes - mais pour les plus complexes des classes je n'ai pas envie de la notion de transmission de toutes les valeurs par le biais d'un constructeur. Avoir des poseurs sur les propriétés est plus souhaitable et votre code de la construction d'un nouvel objet, il devient plus facile à lire.

Alors, comment voulez-vous créer des objets immuables avec les poseurs?

Eh bien, dans mon modèle, les objets commencent comme étant entièrement mutable jusqu'à ce que vous les congeler avec un seul appel de méthode. Une fois qu'un objet est gelé, il restera immuable à jamais - il ne peut pas être transformé en un objet mutable nouveau. Si vous avez besoin d'une mutable version de l'objet, il vous suffit de le cloner.

Ok, maintenant, sur une partie du code. J'ai dans les extraits de code suivants essayé de faire bouillir le modèle réduit à sa forme la plus simple. Le IElement est l'interface de base que tous les objets immuables doivent en fin de compte mettre en œuvre.

public interface IElement : ICloneable
{
  bool IsReadOnly { get; }
  void MakeReadOnly();
}

L'Élément de la classe est l'implémentation par défaut de la IElement interface:

public abstract class Element : IElement
{
  private bool immutable;

  public bool IsReadOnly
  {
    get { return immutable; }
  }

  public virtual void MakeReadOnly()
  {
    immutable = true;
  }

  protected virtual void FailIfImmutable()
  {
    if (immutable) throw new ImmutableElementException(this);
  }

  ...
}

Nous allons refactoriser le SampleElement classe ci-dessus pour mettre en œuvre les lois immuables de l'objet modèle:

public class SampleElement : Element
{
  private Guid id;
  private string name;

  public SampleElement() {}

  public Guid Id
  {
    get 
    { 
      return id; 
    }
    set
    {
      FailIfImmutable();
      id = value;
    }
  }

  public string Name
  {
    get 
    { 
      return name; 
    }
    set
    {
      FailIfImmutable();
      name = value;
    }
  }
}

Vous pouvez maintenant modifier la propriété Id et le Nom de la propriété, tant que l'objet n'a pas été marquée comme immuable par l'appel de la MakeReadOnly() la méthode. Une fois qu'il est immuable, en appelant un setter permettra d'obtenir un ImmutableElementException.

Note finale: Le modèle complet est plus complexe que les extraits de code présentés ici. Il contient également un soutien pour les collections d'objets immuables et complète les graphes d'objets immuables graphes d'objets. Le modèle complet vous permet de transformer l'ensemble d'un graphe d'objets immuables par l'appel de la MakeReadOnly() la méthode la plus externe de l'objet. Une fois que vous commencez à créer de plus grands modèles d'objet à l'aide de ce modèle, le risque de fuites d'objets augmente. Une fuite de l'objet est un objet qui ne parvient pas à appeler le FailIfImmutable() la méthode avant de faire une modification à l'objet. Pour vérifier la présence de fuites, j'ai également développé un générique détecteur de fuite de classe pour une utilisation dans les tests unitaires. Il utilise la réflexion pour tester si toutes les propriétés et méthodes de jeter le ImmutableElementException dans l'état immuable. En d'autres termes TDD est utilisé ici.

J'ai appris à l'aimer, ce modèle de beaucoup de choses et de trouver de grands avantages à elle. Donc ce que je voudrais savoir c'est si l'un de vous à l'aide des modèles similaires? Si oui, connaissez-vous des bonnes ressources que du document? Je suis essentiellement la recherche d'améliorations possibles et pour tous les standards qui pourrait déjà exister sur ce sujet.

31voto

Marc Gravell Points 482669

Pour info, la deuxième approche est appelée "popsicle immutabilité".

Eric Lippert a une série de billets de blog sur l'immuabilité de départ ici. Je suis encore à se familiariser avec la CTP (C# 4.0), mais il a l'air intéressant ce en option / paramètres nommés (à l' .ctor) pourrait faire ici (quand mappés à des champs en lecture seule)... [mise à jour: j'ai blogué sur ce ici]

Pour info, je ne serais probablement pas rendre ces méthodes virtual - nous ne voulons pas sous-classes d'être en mesure de le faire non congelable. Si vous voulez être en mesure d'ajouter du code supplémentaire, je proposerais quelque chose comme:

[public|protected] void Freeze()
{
    if(!frozen)
    {
        frozen = true;
        OnFrozen();
    }
}
protected virtual void OnFrozen() {} // subclass can add code here.

Aussi - de l'AOP (comme PostSharp) pourrait être une option viable pour l'ajout de tous ces ThrowIfFrozen() vérifie.

(toutes mes excuses si j'ai changé la terminologie et les noms de méthode - AFIN de ne pas garder le post original visible lors de la rédaction des réponses)

17voto

Herms Points 13069

Une autre option serait de créer une sorte de Générateur de classe.

Pour un exemple, en Java et en C# et en plusieurs autres langues) String est immuable. Si vous voulez faire plusieurs opérations pour créer une Chaîne, vous utilisez un StringBuilder. C'est mutable, et puis une fois que vous avez terminé, vous avez de revenir à vous à la Chaîne finale de l'objet. À partir de là, il est immuable.

Vous pourriez faire quelque chose de similaire pour les autres classes. Vous avez votre immuable Élément, puis un ElementBuilder. Tous le constructeur devrait faire est de stocker les options que vous définissez, puis quand vous le finaliser, il construit et renvoie l'Élément immuable.

C'est un peu plus de code, mais je pense que c'est plus propre que d'avoir des setters sur une classe qui est censé être immuable.

10voto

Konrad Rudolph Points 231505

Après mon malaise initial sur le fait que j'ai dû créer un nouveau System.Drawing.Point sur chaque modification, je l'ai totalement adopté le concept il y a quelques années. En fait, je vais maintenant créer chaque champ readonly par défaut et de ne modifier qu'il soit mutable si il y a une raison impérieuse qui n'ont que très rarement.

Je n'ai pas de soins très à propos de la croix-threading questions, même si (j'utilise rarement le code là où cela est pertinent). Je trouve qu'il est beaucoup, beaucoup mieux, parce que de la sémantique de l'expressivité. L'immuabilité est la quintessence d'une interface qui est difficile à utiliser correctement.

8voto

Cory Foy Points 5181

Vous êtes toujours aux prises avec l'état, et peut donc encore être mordu si vos objets sont mises en parallèle avant d'être immuable.

Plus fonctionnelle et peut-être renvoyer une nouvelle instance de l'objet à chaque setter. Ou créer un objet mutable et le passer dans le constructeur.

6voto

Charles Bretana Points 59899

Le (relativement) nouveau Logiciel de Conception de paradigme appelé Domain Driven design, fait la distinction entre les objets de l'entité et des objets de valeur.

Entité Objets sont définis comme étant tout ce qui est en correspondance avec une clé piloté par objet dans une banque de données persistantes, comme un employé ou un client, ou d'une facture, etc... où la modification des propriétés de l'objet implique que vous avez besoin pour enregistrer la modification d'une banque de données quelque part, et l'existence de plusieurs instances d'une classe avec le même "clé" imnplies nécessaire de les synchroniser, ou coordonner leur persistance de la banque de données de sorte qu'une instance' modifications à ne pas écraser les autres. Modification des propriétés d'une entité de l'objet implique que vous changer quelque chose à propos de l'objet - pas de modification de l'objet DONT vous faites référence...

Des objets de valeur de otoh, que, sont des objets qui peuvent être considérés comme immuables, dont l'utilité est définie strictement par la valeur de leur propriété, et pour laquelle de multiples instances, n'ont pas besoin d'être coordonnés de façon... comme les adresses ou numéros de téléphone, ou les roues d'une voiture, ou les lettres dans un document... ces choses sont totalement définis par leurs propriétés... une majuscule " A "de l'objet dans un éditeur de texte peuvent être échangés de façon transparente avec tous les autres majuscules" Un " objet tout au long du document, vous n'avez pas besoin d'une clé pour le distinguer de tous les autres "A, Dans ce sens, il est immuable, parce que si vous le remplacez par un" B " (un peu comme changer la chaîne du numéro de téléphone à un numéro de téléphone de l'objet, vous ne modifiez pas les données associées à certains mutable entité, vous êtes de passer d'une valeur à l'autre... tout comme lorsque vous modifiez la valeur d'une chaîne...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X