web-dev-qa-db-fra.com

Construire un objet: Devrais-je exposer ou masquer les paramètres passés au constructeur?

J'ai une habitude que je viens de faire mécaniquement sans même penser trop à ce sujet.

Chaque fois qu'un constructeur attend certains paramètres, je considère que cela doit être disponible par le code d'appel ultérieurement, si vous le souhaitez.

Par exemple:

public class FooRepository : IFooRepository
{
    public FooRepository(IDbConnection dbConnection)
    {
        DbConnection = dbConnection ?? throw new ArgumentNullException(nameof(dbConnection));
    }

    public IDbConnection DbConnection { get; }
}

Le code d'appel qui instancié a FooRepository objet passe un objet IDbConnection _ objet et a donc le droit d'accéder à ces informations plus tard, mais ne peut plus le modifier (no set sur la propriété DbConnection)

Le paramètre dbConnection pourrait être transmis explicitement ou par injection de dépendance, peu importe. Le FooRepository ne devrait pas être au courant de ces détails.

Cependant, hier, lors de la programmation par des pairs avec un collègue, il m'a dit que toute classe I écrite devrait exposer uniquement les informations utiles minimales. Il a déclaré que les développeurs ne devraient pas être en mesure d'analyser et de gâcher l'état interne d'un objet.

Je ne suis pas tout à fait d'accord avec lui. Afin de ne pas perdre trop de temps, je ne veux pas réfléchir quelques minutes à chaque paramètre pour déterminer si cela serait une bonne idée de l'exposer ou non. À mon avis, il y a quelques cas d'utilisation que nous ne pouvons tout simplement pas penser, lorsque nous écrivons d'abord une nouvelle classe.

Que la classe soit finalement incluse ou non dans un package Nuget, n'aimait pas vraiment. Je ne veux tout simplement pas limiter les utilisateurs de ma classe d'accéder aux informations explicitement adoptées lors de l'instanciation de l'objet ou de pouvoir facilement extraire du cadre d'injection de dépendance.

Quelqu'un pourrait-il m'expliquer ce qui est considéré comme une bonne pratique ici?

Devrais-je vraiment penser si chaque paramètre a du sens d'être exposé? Ou existe-t-il un modèle de conception que je peux simplement appliquer instinctivement sans perdre trop de temps?

Toute ressource sur le sujet est la bienvenue.

22
Jérôme MEVEL

Votre collègue a raison. L'état interne doit être encapsulé par défaut et uniquement exposé là où il y a une bonne raison de. Donc, en cas de doute, cachez-vous.

Devrais-je vraiment penser pour chaque paramètre s'il est logique de l'exposer? Ou existe-t-il un modèle de conception que je peux simplement appliquer instinctivement sans perdre trop de temps?

Il suffit de les cacher tout par défaut, c'est le plus facile. Il vous sera évident que si certains biens doivent être exposés.

En général, un objet devrait exposer la même surface que nécessaire pour résoudre sa propre préoccupation. Cela évite le couplage accidentel et rend le code global plus modulaire et plus facile à changer.

Cela fait partie du modèle plus général que l'accès à toutes les données ou fonctionnalités doit être aussi limité que possible:

  • La portée d'une variable doit être aussi petite que possible (la locale est meilleure que la portée intérieure globale, meilleure que la portée extérieure, etc.)
  • Accès aux membres comme étant contraint que possible (c'est pourquoi les membres par défaut à private).
  • Toute interface aussi petite que possible
  • La durée de vie de tout objet aussi court que possible.
  • Les objets immuables sont meilleurs que mutables.

Tout est tombé à réduire le nombre de choses qu'une section de code donnée peut affecter et qui peut affecter le code. Moins de dépendances et d'interactions, moins de bogues et plus il est facile de changer le code et d'ajouter de nouvelles fonctionnalités sans rompre les choses à gauche et à droite.

Je ne veux tout simplement pas limiter les utilisateurs de ma classe d'accéder aux informations qu'ils ont explicitement adoptée lors de l'instanciation de l'objet

Je pense que vous pourrait Avoir un anticipateur y aller. Si l'utilisateur de la classe a passé les informations, il doit déjà avoir les informations, non? Alors, pourquoi aurait-il besoin de la récupérer à nouveau de l'objet qu'il vient de le passer?

Dans votre exemple, la connexion utilisée par un référentiel est un détail de mise en œuvre. Toute l'idée d'un référentiel est que vous pouvez l'utiliser sans préoccupation concernant le stockage sous-jacent. Donc, la nécessité de récupérer la connexion à partir d'un référentiel suggère un problème différent dans la conception.

9
JacquesB

Pensez à la raison pour laquelle vous créez un référentiel en premier lieu. Vous essayez de résumer le concept de persistance afin que les clients n'ont pas besoin de savoir comment les données sont stockées. Vous faites cela afin que vous puissiez protéger les clients des modifications futures du mécanisme de persistance.

Exposer un DbConnection de votre référentiel fait partie de votre interface publique. Vous dites "Je vais toujours utiliser une base de données, spécifiquement un IDbConnection", ce qui permet de changer la mise en œuvre (à la mémoire ou à la base de fichier, par exemple).

Si vous avez vraiment besoin d'exposer DbConnection, vous devez d'abord créer une interface IFooRepository _ qui ne contient que des méthodes de persistance génériques et une implémentation concrète DbFooRepository qui expose en outre la connexion. Généralement, vous voulez que les clients ne dépendent que de l'interface IFooRepository _ afin qu'elles ne soient pas couplées à la base de données, mais si un client spécifique a vraiment besoin d'un accès direct à la base de données, ils peuvent utiliser DbFooRepository .

À mon avis, il y a des cas d'utilisation que nous ne pouvons tout simplement pas penser lorsque nous écrivons d'une nouvelle classe.

YAGNI . Il est plus facile de l'ajouter lorsque vous devez (ce qui peut ne jamais être). N'exposez pas simplement un tas de détails "juste au cas où" quelqu'un pourrait en avoir besoin à l'avenir. Surtout si vous allez publier un package Nuget, gardez à l'esprit qu'une fois que vous effectuez une API publique, vous ne pouvez jamais l'enlever.

6
casablanca

Vous devriez exposer ce que vous devez avoir pour faire ce que vous voulez que la classe fasse. Tout le reste est au meilleur doré, et a pire le consommateur avec des détails superflus. À titre d'exemple, il est assez improbable qu'un utilisateur de votre foorepositoire ait besoin de savoir quel enregistreur vous utilisez, mais c'est quelque chose qui est injecté dans de nombreux référentiels.

L'information publique n'est pas une information intéressante, ni des informations utiles, cela devrait être nécessaire. Si vous ne pouvez pas trouver une raison pour une raison pour laquelle vos utilisateurs exigent les informations, affirmant que la classe est inutilisable sans cela, laissez-la sortir. Lorsque vous développez la classe, vous pouvez trouver une raison pour laquelle vous devez exposer certains aspects, et c'est bien. Mais même si la classe est un constructeur, il aura probablement beaucoup de détails qui n'ont pas besoin d'être partagés. Ne pas trop partager.

0
jmoreno