J'ai une question sur les "meilleures pratiques" concernant la POO en C # (mais elle s'applique en quelque sorte à tous les langages).
Envisagez d'avoir une classe de bibliothèque avec un objet qui doit être exposé au public, par exemple via un accesseur de propriété, mais nous ne voulons pas que le public (les personnes utilisant cette classe de bibliothèque) le modifie.
class A
{
// Note: List is just example, I am interested in objects in general.
private List<string> _items = new List<string>() { "hello" }
public List<string> Items
{
get
{
// Option A (not read-only), can be modified from outside:
return _items;
// Option B (sort-of read-only):
return new List<string>( _items );
// Option C, must change return type to ReadOnlyCollection<string>
return new ReadOnlyCollection<string>( _items );
}
}
}
Évidemment, la meilleure approche est "l'option C", mais très peu d'objets ont une variante ReadOnly (et certainement aucune classe définie par l'utilisateur ne l'a).
Si vous étiez l’utilisateur de la classe A, vous attendriez-vous à des changements
List<string> someList = ( new A() ).Items;
se propager à l'objet original (A)? Ou est-il correct de renvoyer un clone à condition qu'il ait été écrit ainsi dans les commentaires / documentation? Je pense que cette approche clone pourrait conduire à des bugs assez difficiles à suivre.
Je me souviens qu'en C ++, nous pouvions retourner des objets const et vous ne pouviez appeler que des méthodes marquées comme const dessus. Je suppose qu'il n'y a pas une telle fonctionnalité / modèle dans le C #? Sinon, pourquoi ne l'incluraient-ils pas? Je crois que cela s'appelle la constance.
Mais là encore, ma principale question concerne «à quoi vous attendriez-vous» ou comment gérer l'option A vs B.
la source
Réponses:
Outre la réponse de @ Jesse, qui est la meilleure solution pour les collections: l'idée générale est de concevoir votre interface publique de A de manière à ce qu'elle ne revienne que
types de valeurs (comme int, double, struct)
objets immuables (comme "chaîne" ou classes définies par l'utilisateur qui n'offrent que des méthodes en lecture seule, tout comme votre classe A)
(seulement si vous devez): des copies d'objets, comme le montre votre "Option B"
IEnumerable<immutable_type_here>
est immuable en soi, il ne s'agit donc que d'un cas particulier de ce qui précède.la source
Notez également que vous devez programmer des interfaces et, dans l'ensemble, ce que vous souhaitez retourner à partir de cette propriété est un
IEnumerable<string>
. AList<string>
est un peu un non-non car il est généralement modifiable et j'irai jusqu'à dire qu'enReadOnlyCollection<string>
tant qu'implémentateur de, ilICollection<string>
semble qu'il viole le principe de substitution de Liskov.la source
IEnumerable<string>
est exactement ce que l'on veut ici.IReadOnlyList<T>
.IList<T>
)J'ai rencontré un problème similaire il y a quelque temps et ma solution était en dessous, mais cela ne fonctionne vraiment que si vous contrôlez également la bibliothèque:
Commencez avec votre classe
A
Dérivez ensuite une interface de ceci avec seulement la partie get des propriétés (et toutes les méthodes de lecture) et ayez un implémentation qui
Bien sûr, lorsque vous souhaitez utiliser la version en lecture seule, une simple conversion suffit. C'est exactement ce que votre solution C est, vraiment, mais j'ai pensé offrir la méthode avec laquelle je l'ai réalisée
la source
Pour tous ceux qui trouvent cette question et qui sont toujours intéressés par la réponse - il existe maintenant une autre option qui expose
ImmutableArray<T>
. Ce sont quelques points pour lesquels je pense que c'est mieux alorsIEnumerable<T>
ouReadOnlyCollection<T>
:IEnumerable<T>
faitIEnumerable
ouIReadOnlyCollect
il / elle ne peut pas supposer que cela ne change jamais (en d'autres termes, il n'y a aucune garantie que les éléments d'une collection n'ont pas été modifiés alors que la référence à cette collection reste inchangée)De plus, si APi doit informer le client des modifications apportées à la collection, j'exposerais un événement en tant que membre distinct.
Notez que je ne dis pas que
IEnumerable<T>
c'est mauvais en général. Je l'emploierais toujours lorsque je passais une collection en argument ou retournais une collection initialisée paresseusement (dans ce cas particulier, il est logique de masquer le nombre d'éléments car il n'est pas encore connu).la source