Instanciation répétée de la même classe dans une méthode

Il s’agit en quelque sorte d’une question complémentaire sur l’ Instanciation de plusieurs objets identiques . Et je pense que le langage n'est pas vraiment spécifique, donc cela s'applique-t-il à Java et à C #?

Version A

public MyClass {
    public void methodX() {}

    public void methodY(int i, object o) {
        GeometrySplitter splitter = new GeomterySplitter(int i, object o);
        splitter.chop();
    }

    public void methodZ() {}
}

Version b

public MyClass {
    private GeometrySplitter splitter = new GeometrySplitter();

    public void methodX() {}

    public void methodY(int i, object o) {
        splitter.chop(int i, object o);
    }

    public void methodZ() {}
}

Un collègue dit que la version B est une meilleure pratique du code, car la classe n’est instanciée qu’une seule fois. Mon propre raisonnement est que la variable interne n'est utilisée que dans methodY (int i, objet o) et qu'elle devrait donc être créée dans la méthode elle-même, comme indiqué dans la version A.

Mon collègue raisonne alors que si cette méthode est appelée 4000 fois par seconde. Pour chaque appel de méthode, une nouvelle instance de cette classe est créée. Et cela nuit aux performances et à l’utilisation de la mémoire.

Est-ce vrai? Si oui ou non, quelqu'un peut-il clarifier cela?

1
ajouté édité
Vues: 1
En C#, GeometrySplitter peut être une structure, et vous pouvez avoir votre gâteau et le manger aussi: aucune allocation de tas, mais instanciée proche de l’utilisation.
ajouté l'auteur Frank Hileman, source
Pour être honnête, je pense que sans un contexte approprié, je ne peux pas dire A ou B. Si votre classe n’est qu’utile et qu’il n’ya pas d’abstraction, vous pourriez même opter pour des méthodes statiques: pas de mémoire, pas de temps à allouer, moins de code ligne.
ajouté l'auteur Walfrat, source
Etrange que les réponses ne se concentrent pas sur la sécurité des threads (bien que certaines réponses le mentionnent entre autres): avec la version B, il est possible d'accéder au séparateur à partir de différents threads, ce qui pourrait interférer avec le méthode chop .
ajouté l'auteur Boris Nikolov, source

7 Réponses

Préférez la version A à moins que vous n'ayez une raison concrète de vous inquiéter des effets de nombreuses invocations de la méthode.

Il existe un principe général selon lequel "les variables doivent être déclarées aussi proches que possible de l'endroit où elles sont utilisées", et doivent être suivies à moins que vous n'ayez une bonne raison de ne pas le faire. La version A obéit à ce principe et présente plusieurs avantages:

  • You can see the object declaration right where you are using it. In version B, once you go beyond a trivial example to real code, the declaration and instantiation of the object may be far from its use. This makes the code less clear.
  • Limiting the scope limits opportunities for error. In version A, the object only exists right when you are using it. In version B, it is hanging around and accessible to other methods. It's possible that this could lead to a bug. For example, if you have to declare several objects like this for several different methods, you might accidentally use the wrong one somewhere.
  • Memory is only being held while you use the object. Version A may cause more memory allocations, and that may be harmful in the context of languages with Java-like garbage collection. But it's worth noting that version A actually ties up less memory total in the longer term, because the object only exists while it is actually needed (and not at all if that method doesn't get called). It's not true that one version is definitely preferable in terms of memory.

Pour cette raison, je préférerais A (sans rien connaître de l’utilisation de ce code).

Il est vrai que B est bénéfique dans des cas spécifiques. Comme le remarque votre ami, si la méthode est invoquée à plusieurs reprises, la version B utiliserait la mémoire de manière plus efficace (et éviterait de manière répétée la surcharge liée à l’installation de l’objet).

Mais s’inquiéter de cela lorsque vous ne savez pas réellement que vous avez un problème de mémoire ou de performances est un cas classique d’optimisation prématurée. Et dans certains cas, la version A est plus performante. Par exemple, si votre code implique la création de milliers d'objets MyClass , alors que MethodY est rarement utilisé? Dans ce cas, A peut être la meilleure conception en termes de mémoire.

10
ajouté
L'appel d'une méthode avec des arguments alloue également de la mémoire. L'attribution pour l'objet n'est différente que dans le sens où cela se produit sur le tas. C'est encore rapide. C'est juste un ajout de pointeur. S'il vous plaît vous soucier plus de l'écriture facile à comprendre le code. Si cela devenait un réel problème de performances, il serait bon que nous puissions lire le code que nous devons optimiser.
ajouté l'auteur candied_orange, source
La méthode B pourrait utiliser moins d’allocations. La gigue peut facilement voir que la durée de vie de l'objet est strictement liée au corps de la méthode et l'allouer dans le cadre.
ajouté l'auteur Caleth, source

La portée des membres ne résout pas complètement le problème

Même si vous déclarez GeometrySplitter en tant que membre de la classe, il sera toujours instancié par instance de MyClass et, en outre, devra être instancié séparément pour toute autre classe utilisant il. Donc, si vous vous inquiétez des frais généraux liés à la construction d'un GeometrySplitter , son déplacement en dehors de la portée locale ne résout pas complètement le problème.

Utilisez IoC pour contourner tout cela

Sous IoC , la création d'objets est considérée comme une préoccupation distincte. et faire en sorte que MyClass s’inquiète de savoir comment instancier quelque chose est une violation de SRP . Ces problèmes ne devraient pas avoir besoin d’être résolus par MyClass .

Si vous utilisez un conteneur IoC, cela élimine le problème et vous évite également une surcharge d'instanciation, non seulement entre différents appels à MyClass :: methodY , mais également entre différents appels à une méthode dans classe qui utilise le séparateur.

Par exemple:

public MyClass {

    protected readonly IUnityContainer _container;

    public MyClass(IUnityContainer container) { 
        _container = container; 
    }

    public void methodY(int i, object o) {
        IGeometrySplitter splitter = _container.Resolve();
        splitter.chop();
    }
}

Les règles d'instanciation appartiennent à la racine de la composition

Si vous souhaitez une nouvelle instance à chaque fois, configurez votre composition root de cette façon:

container.RegisterType();

Si vous souhaitez qu'une seule instance (non thread-safe) soit réutilisée:

container.RegisterType(new PerThreadLifetimeManager());

Si vous voulez une seule instance (thread-safe) qui est réutilisée:

container.RegisterType(new ContainerControlledLifetimeManager());

Ensuite, enregistrez MyClass dans le conteneur également, afin qu’il s’injecte dans le processus d’instanciation:

container.RegisterType(container);
container.RegisterType();

En procédant ainsi, Unity s’injectera automatiquement en tant qu’argument constructeur pour MyClass afin que méthodeY puisse l’appeler.

Pour instancier MyClass , utilisez:

var myClass = container.Resolve();

Remarques

  • Mon exemple ci-dessus utilise Unity , qui correspond à la technologie ac #. En Java, je pense que vous utiliseriez Spring à la place (pas tout à fait sûr). Mais le principe est indépendant de la langue et les techniques de contrôle de la durée de vie des objets doivent être similaires.

  • Bien que ce modèle ne soit pas si rare, certaines personnes diraient qu'il s'agit d'un anti-pattern . Ils diraient que vous devez injecter un GeometrySplitterFactory spécifique au lieu du conteneur IoC lui-même et mettre en œuvre les règles d'instanciation en usine. Mais le principe est le même: supprimez les règles d'instanciation dans MyClass .

2
ajouté
Je suis d'accord avec la réponse actuelle la plus récente - ce qui semble bien si le conteneur le prend en charge. J'ai donc ajouté quelques réponses. pense à d’autres récipients IoC et passe un peu de temps à regarder le livre de Mark sur l’injection de dépendance (qui a maintenant environ 5 ans mais qui semble être toujours pertinent).
ajouté l'auteur Ben Cottrell, source
Il n'est pas nécessaire d'injecter le conteneur Unity dans l'instance, certaines personnes l'envisagent même être un anti-modèle (mais n'hésitez pas à être en désaccord avec M. Seemann). Cependant, le schéma habituel est de laisser le conteneur transmettre la dépendance au constructeur (ou peut-être une propriété en C #), plutôt que de faire du conteneur une dépendance en soi.
ajouté l'auteur Ben Cottrell, source
Par méthode, par instance et par processus: chacun des premiers est préférable au second, en ignorant les performances. Mais une structure est la meilleure solution dans ce cas.
ajouté l'auteur Frank Hileman, source
Une question si simple. la réponse ne devrait avoir rien à voir avec une complexité supplémentaire telle que l'IoC.
ajouté l'auteur Frank Hileman, source
Je connais (et préconise fortement) la nécessité d'une portée étroite. Cependant, il y a plus que cela, généralement. Par exemple, vous voulez parfois des objets avec état, par exemple un client Web avec un magasin de cookies, ou s'il existe une notion d'atomicité transactionnelle qui couvre différents appels de méthode. Certes, une classe de fractionnement ne fait probablement pas partie de ces catégories.
ajouté l'auteur John Wu, source
C'est la bonne réponse dans certains cas, mais pas tous, je suis d'accord. Mais si la construction d'un GeometrySplitter est vraiment si coûteuse que nous nous inquiétons de savoir où nous le déclarons, l'étendue par processus devrait être une option au moins prise en compte, et l'IoC est probablement meilleur que le statique global. variables.
ajouté l'auteur John Wu, source
@Ben, merci de commenter mon autre question .
ajouté l'auteur John Wu, source
@Ben - J'ai ajouté une note par respect pour vos critiques et celles de Mark. De plus, je peux ajouter une nouvelle question à stackexchange et je vous invite à y répondre.
ajouté l'auteur John Wu, source
Intéressant. Certaines critiques de Mark semblent un peu faibles, mais je vois son point. Mais si nous faisons ce qu'il dit - injectons un GeometrySplitter et non un résolveur dans le constructeur - nous ne pourrons jamais avoir plus d'une instance par instanciation de MyClass . Pour répondre à la question de OP, nous devrions écrire un GeometrySplitterFactory supplémentaire, qui ne semble pas trop grave, mais peut-être un peu trop pour cet exemple.
ajouté l'auteur John Wu, source

En supposant que GeometrySplitter ne possède aucun état, la version A est l'option la plus propre et la plus lisible, car elle réduit la durée de vie de l'objet au moment où il est réellement nécessaire.

Cependant, vous dites que cette fonction s'appelle 4 000 fois par seconde et que votre collègue est préoccupé par les performances. Alors analysez cette performance. Configurez un harnais de test et exécutez chaque version à tour de rôle avec 4000 hits par seconde sur une période de temps convenable. Examinez l'utilisation de la mémoire et mesurez exactement comment ils répondent.

Sans savoir combien de temps cela prend pour créer un GeometrySplitter, il est difficile de savoir si la création de 4000 d'entre eux par seconde va taxer la JVM.

2
ajouté

Il n'y a pas de règle générale à ce sujet. Cela dépend du cas d'utilisation ici.

Si la méthode est rarement utilisée, il n'y a aucune raison de déplacer la classe GeometrySplitter en dehors du champ d'application de MethodY .

Cependant, l’allocation de mémoire est une opération assez coûteuse et doit être utilisée avec prudence. Si la classe GeometrySplitter ne possède aucune propriété qui dépend des paramètres d'entrée de MethodY et qu'elle ne ressemble pas à celle-ci, la seconde approche est préférable si MethodY est appelé souvent. Vous ne perdez pas de temps à allouer de la mémoire, ne la fragmentez pas et ne chargez pas le récupérateur de mémoire ultérieurement de libérer toutes les références inutiles.

La troisième option serait de passer une instance de cette classe, instanciée ailleurs, en tant qu'argument de MethodY . De cette façon, vous obtenez le meilleur des deux mondes. Vous instanciez une fois GeometrySplitter , et utilisez-le simplement là où vous en avez besoin. Cette approche indique également clairement la dépendance de MyClass.MethodY sur la classe GeometrySplitter , ce qui n'est pas clair dans les approches précédentes sans examiner le corps de la méthode.

1
ajouté
Étant donné que la classe s'appelle GeometrySplitter et que son utilisation est donnée dans l'exemple donné, on peut facilement supposer qu'elle est utilisée dans un éditeur graphique ou un processeur de données géographiques, où cette opération peut être utilisée un grand nombre de fois. Cela serait particulièrement le cas dans les applications qui traitent des données SIG. Cependant, vous avez un point en ce sens que si vous êtes certain que la méthode sera rarement utilisée, il n'y a aucune raison d'extraire la classe en dehors de la portée de la méthode dans laquelle elle est utilisée.
ajouté l'auteur Vladimir Stokic, source

Vous avez tous les deux raison (et potentiellement) tort.

Vous avez raison si l'analyse de rentabilisation indique réellement que vous n'avez pas à appeler la méthode 4 000 fois. Alors c'est bon.

Votre collègue a raison, car il s’agit là d’une meilleure pratique du code, non seulement pour les raisons évoquées par votre collègue, mais aussi parce que cet objet est disponible dans MyClass. Cela favorise les classes plus petites et plus spécifiées (comme MyClass) et l'encapsulation (cela masque la mise en oeuvre et le fonctionnement de GeometrySplitter au monde extérieur).

Aujourd'hui, vous n'avez peut-être besoin de GeometrySplitter que dans cette méthode, mais à l'avenir, vous pourrez obtenir un autre dossier commercial dans lequel vous devrez construire une autre méthode et l'instancier à nouveau, ce qui vous permettra de refactoriser davantage. La façon dont vous promouvez la création d’objets l’ouvre aux classes dites "Dieu" et à de nombreux remaniements inutiles.

1
ajouté
Bien sûr, si vous avez besoin de l’objet disponible dans MyClass, vous devez le faire persister. Mais la question présuppose sûrement qu’elle n’est nécessaire que dans une seule méthode. Vous ne devez pas donner aux variables une portée plus large que nécessaire juste au cas où elles pourraient être nécessaires quelque part dans le futur.
ajouté l'auteur mınxomaτ, source

Créer de nouveaux objets encore et encore prend du temps. Le changement pour éviter cela est trivial. Donc, vous mesurez combien de temps vous allez être en sécurité, et quand c'est significatif, vous faites le changement sans importance. Si vous ne mesurez pas, le gain n'est évidemment pas significatif.

ET encore une chose: si votre application est multi-threadée, vous devez vous assurer absolument que votre "séparateur" est thread-safe, c’est-à-dire qu’il fonctionnera correctement si le même objet séparateur est utilisé à partir de plusieurs threads. Sinon, vous créez une bombe à retardement pour vous-même.

0
ajouté

En supposant que chop n’affecte pas l’état de l’instance GeometrySplitter , alors A est préférable à B, mais il serait préférable de remplacer chop par une valeur statique. méthode.

Cela conduit à un code qui est plus simple que l'une ou l'autre des options:

public MyClass {
    public void methodX() {}

    public void methodY(int i, object o) {
        GeometrySplitter.chop(i, o);
    }

    public void methodZ() {}
}

( je suppose que chop n’affecte pas l’état de GeometrySplitter car sinon, les deux options que vous présentez seraient fonctionnellement différentes, et l’un d’eux ne fonctionne pas correctement.)

Plus généralement, vous voulez toujours réduire la quantité d’état mutable qui peut influencer toute section de code. Toutes choses étant égales par ailleurs, nous voulons limiter l’état mutable au niveau le plus étroit possible . Cela signifie que nous préférons les méthodes sans effets secondaires statiques aux méthodes d'instance, et si cela n'est pas possible, préférons les instances avec le champ d'application d'une méthode unique (option A) aux instances avec le champ de classe (option B).

0
ajouté
@VladimirStokic: Si la méthode chop affecte effectivement l'état de l'instance GeomerySplitter , les deux exemples fournis seront alors fonctionnellement différents. Dans ce cas, je ne pense pas que le PO aurait posé la question en premier lieu.
ajouté l'auteur JacquesB, source
Nous ne savons pas avec certitude si Chop affecte l'état de GeometrySplitter. Nous ne pouvons que supposer cela. De plus, nous ne savons pas si GeometrySplitter est une classe héritée d'une autre classe et si Chop est une méthode virtuelle, remplacée par GeometrySplitter, auquel cas elle ne peut pas être rendue statique. De plus, GeometrySplitter peut être une classe provenant d'une bibliothèque tierce que vous ne pouvez pas modifier, ce qui vous oblige à l'utiliser de cette façon. Si rien de ce qui précède n’est le cas, votre suggestion est alors tout à fait valable. Sinon, ce n'est pas correct. Veuillez mettre à jour votre réponse pour refléter cela.
ajouté l'auteur Vladimir Stokic, source