Bonne ou mauvaise pratique? Initialisation des objets dans getter

167

J'ai une étrange habitude semble-t-il ... d'après mon collègue du moins. Nous travaillons ensemble sur un petit projet. La façon dont j'ai écrit les classes est (exemple simplifié):

[Serializable()]
public class Foo
{
    public Foo()
    { }

    private Bar _bar;

    public Bar Bar
    {
        get
        {
            if (_bar == null)
                _bar = new Bar();

            return _bar;
        }
        set { _bar = value; }
    }
}

Donc, fondamentalement, je n'initialise un champ que lorsqu'un getter est appelé et que le champ est toujours nul. J'ai pensé que cela réduirait la surcharge en n'initialisant aucune propriété qui n'est utilisée nulle part.

ETA: La raison pour laquelle j'ai fait cela est que ma classe a plusieurs propriétés qui renvoient une instance d'une autre classe, qui à son tour a également des propriétés avec encore plus de classes, et ainsi de suite. L'appel du constructeur pour la classe supérieure appellerait ensuite tous les constructeurs pour toutes ces classes, quand ils ne sont pas toujours tous nécessaires.

Y a-t-il des objections contre cette pratique, autres que la préférence personnelle?

MISE À JOUR: J'ai examiné les nombreuses opinions divergentes en ce qui concerne cette question et je maintiendrai ma réponse acceptée. Cependant, je suis maintenant parvenu à une bien meilleure compréhension du concept et je suis en mesure de décider quand et quand l'utiliser.

Les inconvénients:

  • Problèmes de sécurité des threads
  • Ne pas obéir à une requête "setter" lorsque la valeur transmise est nulle
  • Micro-optimisations
  • La gestion des exceptions doit avoir lieu dans un constructeur
  • Besoin de vérifier la valeur nulle dans le code de la classe

Avantages:

  • Micro-optimisations
  • Les propriétés ne renvoient jamais null
  • Retardez ou évitez de charger des objets "lourds"

La plupart des inconvénients ne s'appliquent pas à ma bibliothèque actuelle, mais je devrais tester pour voir si les "micro-optimisations" optimisent réellement quoi que ce soit.

DERNIÈRE MISE À JOUR:

D'accord, j'ai changé ma réponse. Ma question initiale était de savoir si c'était une bonne habitude. Et je suis maintenant convaincu que ce n'est pas le cas. Peut-être que je vais encore l'utiliser dans certaines parties de mon code actuel, mais pas inconditionnellement et certainement pas tout le temps. Je vais donc perdre mon habitude et y réfléchir avant de l'utiliser. Merci tout le monde!

John Willemse
la source
14
Ceci est le modèle de chargement paresseux, ce n'est pas exactement vous donner un avantage merveilleux ici, mais c'est toujours une bonne chose à mon humble avis.
Machinarius
28
L'instanciation paresseuse a du sens si vous avez un impact mesurable sur les performances, ou si ces membres sont rarement utilisés et consomment une quantité exorbitante de mémoire, ou si cela prend beaucoup de temps pour les instancier et que vous ne voulez le faire qu'à la demande. Dans tous les cas, assurez-vous de tenir compte des problèmes de sécurité des threads (votre code actuel ne l'est pas ) et envisagez d'utiliser la classe Lazy <T> fournie .
Chris Sinclair
10
Je pense que cette question convient mieux sur codereview.stackexchange.com
Eduardo Brites
7
@PLB ce n'est pas un motif singleton.
Colin Mackay
30
Je suis surpris que personne n'ait mentionné un bug sérieux avec ce code. Vous avez une propriété publique que je peux définir de l'extérieur. Si je le mets à NULL, vous créerez toujours un nouvel objet et ignorerez mon accès de setter. Cela pourrait être un bug très sérieux. Pour les propriétés privées, cela pourrait être correct. Personnellement, je n'aime pas faire de telles optimisations prématurées. Ajoute de la complexité sans avantage supplémentaire.
SolutionYogi

Réponses:

170

Ce que vous avez ici est une implémentation - naïve - de "l'initialisation paresseuse".

Réponse courte:

L'utilisation inconditionnelle de l' initialisation paresseuse n'est pas une bonne idée. Elle a sa place mais il faut prendre en compte les impacts de cette solution.

Contexte et explication:

Mise en œuvre concrète:
regardons d'abord votre échantillon concret et pourquoi je considère sa mise en œuvre naïve:

  1. Il viole le principe de la moindre surprise (POLS) . Lorsqu'une valeur est affectée à une propriété, on s'attend à ce que cette valeur soit renvoyée. Dans votre implémentation, ce n'est pas le cas pour null:

    foo.Bar = null;
    Assert.Null(foo.Bar); // This will fail
  2. Cela introduit pas mal de problèmes de thread: deux appelants de foo.Barsur différents threads peuvent potentiellement obtenir deux instances différentes de Baret l'un d'eux sera sans connexion à l' Fooinstance. Toutes les modifications apportées à cette Barinstance sont perdues silencieusement.
    Ceci est un autre cas de violation du POLS. Lorsque seule la valeur stockée d'une propriété est accédée, elle est censée être thread-safe. Bien que vous puissiez affirmer que la classe n'est tout simplement pas sûre pour les threads - y compris le getter de votre propriété - vous devrez le documenter correctement car ce n'est pas le cas normal. De plus, l'introduction de ce numéro n'est pas nécessaire comme nous le verrons sous peu.

En général:
Il est maintenant temps de regarder l'initialisation paresseuse en général: l'
initialisation paresseuse est généralement utilisée pour retarder la construction d'objets qui prennent beaucoup de temps à être construits ou qui prennent beaucoup de mémoire une fois complètement construits.
C'est une raison très valable pour l'utilisation de l'initialisation paresseuse.

Cependant, ces propriétés n'ont normalement pas de setters, ce qui élimine le premier problème souligné ci-dessus.
De plus, une implémentation thread-safe serait utilisée - comme Lazy<T>- pour éviter le deuxième problème.

Même en considérant ces deux points dans l'implémentation d'une propriété paresseuse, les points suivants sont des problèmes généraux de ce modèle:

  1. La construction de l'objet peut échouer, ce qui entraîne une exception à partir d'un getter de propriété. Ceci est une autre violation du POLS et doit donc être évité. Même la section sur les propriétés dans les «Directives de conception pour le développement de bibliothèques de classes» indique explicitement que les récupérateurs de propriétés ne doivent pas lever d'exceptions:

    Évitez de lancer des exceptions des getters de propriété.

    Les getters de propriétés doivent être des opérations simples sans aucune condition préalable. Si un getter peut lever une exception, envisagez de redéfinir la propriété en une méthode.

  2. Les optimisations automatiques par le compilateur sont blessées, à savoir la prédiction d'inlining et de branche. Veuillez consulter la réponse de Bill K pour une explication détaillée.

La conclusion de ces points est la suivante:
Pour chaque propriété unique qui est implémentée paresseusement, vous devriez avoir considéré ces points.
Cela signifie qu'il s'agit d'une décision par cas et ne peut pas être considérée comme une meilleure pratique générale.

Ce modèle a sa place, mais ce n'est pas une bonne pratique générale lors de l'implémentation de classes. Il ne doit pas être utilisé sans condition , pour les raisons exposées ci-dessus.


Dans cette section, je veux discuter de certains des points que d'autres ont avancés comme arguments pour l'utilisation inconditionnelle de l'initialisation paresseuse:

  1. Sérialisation:
    EricJ déclare dans un commentaire:

    Un objet qui peut être sérialisé n'aura pas son constructeur invoqué lorsqu'il est désérialisé (cela dépend du sérialiseur, mais beaucoup d'entre eux se comportent comme ceci). Mettre le code d'initialisation dans le constructeur signifie que vous devez fournir une prise en charge supplémentaire pour la désérialisation. Ce modèle évite ce codage spécial.

    Il y a plusieurs problèmes avec cet argument:

    1. La plupart des objets ne seront jamais sérialisés. L'ajout d'une sorte de support quand il n'est pas nécessaire viole YAGNI .
    2. Lorsqu'une classe doit prendre en charge la sérialisation, il existe des moyens de l'activer sans solution de contournement qui n'a rien à voir avec la sérialisation à première vue.
  2. Micro-optimisation: votre argument principal est que vous ne voulez construire les objets que lorsque quelqu'un y accède réellement. Vous parlez donc d’optimisation de l’utilisation de la mémoire.
    Je ne suis pas d'accord avec cet argument pour les raisons suivantes:

    1. Dans la plupart des cas, quelques objets supplémentaires en mémoire n'ont aucun impact sur quoi que ce soit. Les ordinateurs modernes ont bien assez de mémoire. Sans un cas de problèmes réels confirmés par un profileur, il s'agit d' une optimisation prématurée et il y a de bonnes raisons de s'y opposer.
    2. Je reconnais que parfois ce type d'optimisation est justifié. Mais même dans ces cas, l'initialisation paresseuse ne semble pas être la bonne solution. Il y a deux raisons qui s'y opposent:

      1. L'initialisation paresseuse nuit potentiellement aux performances. Peut-être que marginalement, mais comme l'a montré la réponse de Bill, l'impact est plus grand qu'on ne pourrait le penser à première vue. Donc, cette approche négocie essentiellement la performance contre la mémoire.
      2. Si vous avez une conception dans laquelle il est courant de n'utiliser que des parties de la classe, cela indique un problème avec la conception elle-même: la classe en question a très probablement plus d'une responsabilité. La solution serait de diviser la classe en plusieurs classes plus ciblées.
Daniel Hilgarth
la source
4
@JohnWillemse: C'est un problème de votre architecture. Vous devez refactoriser vos classes de manière à ce qu'elles soient plus petites et plus ciblées. Ne créez pas une classe pour 5 choses / tâches différentes. Créez plutôt 5 classes.
Daniel Hilgarth
26
@JohnWillemse Considérons peut-être cela comme un cas d'optimisation prématurée alors. À moins que vous n'ayez un goulot d'étranglement de performance / mémoire mesuré , je vous le déconseille car cela augmente la complexité et introduit des problèmes de threading.
Chris Sinclair
2
+1, ce n'est pas un bon choix de conception pour 95% des classes. L'initialisation paresseuse a ses avantages, mais ne devrait pas être généralisée pour TOUTES les propriétés. Cela ajoute de la complexité, de la difficulté à lire le code, des problèmes de sécurité des threads ... pour aucune optimisation perceptible dans 99% des cas. De plus, comme SolutionYogi l'a dit en commentaire, le code de l'OP est bogué, ce qui prouve que ce modèle n'est pas trivial à implémenter et devrait être évité à moins qu'une initialisation paresseuse ne soit réellement nécessaire.
ken2k
2
@DanielHilgarth Merci d'avoir fait le maximum pour noter (presque) tout ce qui ne va pas avec l'utilisation de ce modèle sans condition. Bon travail!
Alex
1
@DanielHilgarth Eh bien oui et non. La violation est le problème ici, donc oui. Mais aussi «non» car POLS est strictement le principe selon lequel vous ne serez probablement pas surpris par le code . Si Foo n'a pas été exposé en dehors de votre programme, c'est un risque que vous pouvez prendre ou non. Dans ce cas, je peux presque garantir que vous serez finalement surpris , car vous ne contrôlez pas la façon dont les propriétés sont accessibles. Le risque est simplement devenu un bug, et votre argument sur l' nullaffaire est devenu beaucoup plus fort. :-)
atlaste
49

C'est un bon choix de conception. Fortement recommandé pour le code de bibliothèque ou les classes principales.

Il est appelé par une certaine «initialisation paresseuse» ou «initialisation retardée» et il est généralement considéré par tous comme un bon choix de conception.

Premièrement, si vous initialisez dans la déclaration des variables de niveau de classe ou du constructeur, alors lorsque votre objet est construit, vous avez la charge de créer une ressource qui ne peut jamais être utilisée.

Deuxièmement, la ressource n'est créée que si nécessaire.

Troisièmement, vous évitez de garbage collection un objet qui n'a pas été utilisé.

Enfin, il est plus facile de gérer les exceptions d'initialisation qui peuvent survenir dans la propriété que les exceptions qui se produisent lors de l'initialisation des variables de niveau classe ou du constructeur.

Il existe des exceptions à cette règle.

Concernant l'argument de performance de la vérification supplémentaire d'initialisation dans la propriété "get", il est insignifiant. L'initialisation et la suppression d'un objet est un impact de performance plus important qu'une simple vérification de pointeur nul avec un saut.

Directives de conception pour le développement de bibliothèques de classes à l' adresse http://msdn.microsoft.com/en-US/library/vstudio/ms229042.aspx

En ce qui concerne Lazy<T>

La Lazy<T>classe générique a été créée exactement pour ce que l'affiche veut, voir Initialisation différée à http://msdn.microsoft.com/en-us/library/dd997286(v=vs.100).aspx . Si vous disposez d'anciennes versions de .NET, vous devez utiliser le modèle de code illustré dans la question. Ce modèle de code est devenu si courant que Microsoft a jugé bon d'inclure une classe dans les dernières bibliothèques .NET pour faciliter l'implémentation du modèle. De plus, si votre implémentation nécessite la sécurité des threads, vous devez l'ajouter.

Types de données primitifs et classes simples

Évidemment, vous n'allez pas utiliser l'initialisation paresseuse pour un type de données primitif ou une utilisation de classe simple comme List<string>.

Avant de commenter sur Lazy

Lazy<T> a été introduit dans .NET 4.0, veuillez donc ne pas ajouter encore un autre commentaire concernant cette classe.

Avant de commenter les micro-optimisations

Lorsque vous créez des bibliothèques, vous devez prendre en compte toutes les optimisations. Par exemple, dans les classes .NET, vous verrez des tableaux de bits utilisés pour les variables de classe booléenne tout au long du code afin de réduire la consommation de mémoire et la fragmentation de la mémoire, pour ne citer que deux "micro-optimisations".

Concernant les interfaces utilisateur

Vous n'allez pas utiliser l'initialisation différée pour les classes qui sont directement utilisées par l'interface utilisateur. La semaine dernière, j'ai passé la majeure partie de la journée à supprimer le chargement paresseux de huit collections utilisées dans un modèle de vue pour les combo-box. J'ai un LookupManagerqui gère le chargement paresseux et la mise en cache des collections nécessaires à tout élément de l'interface utilisateur.

"Setters"

Je n'ai jamais utilisé une propriété set ("setters") pour une propriété chargée paresseusement. Par conséquent, vous ne permettriez jamais foo.Bar = null;. Si vous devez définir, Barje créerais une méthode appelée SetBar(Bar value)et n'utiliserais pas l'initialisation paresseuse

Les collections

Les propriétés de la collection de classes sont toujours initialisées lorsqu'elles sont déclarées car elles ne doivent jamais être nulles.

Classes complexes

Permettez-moi de répéter cela différemment, vous utilisez l'initialisation paresseuse pour les classes complexes. Qui sont généralement des classes mal conçues.

enfin

Je n'ai jamais dit de faire cela pour toutes les classes ou dans tous les cas. C'est une mauvaise habitude.

AMissico
la source
6
Si vous pouvez appeler foo.Bar plusieurs fois dans différents threads sans aucun paramètre de valeur intermédiaire mais obtenir des valeurs différentes, alors vous avez une classe médiocre.
Lie Ryan
25
Je pense que c'est une mauvaise règle empirique sans beaucoup de considérations. À moins que Bar ne soit un porc de ressources connu, il s'agit d'une micro-optimisation inutile. Et dans le cas où Bar consomme beaucoup de ressources, il existe le thread safe Lazy <T> intégré à .net.
Andrew Hanlon
10
"il est plus facile de gérer les exceptions d'initialisation qui peuvent se produire dans la propriété que les exceptions qui se produisent lors de l'initialisation des variables de niveau de classe ou du constructeur." - d'accord, c'est idiot. Si un objet ne peut pas être initialisé pour une raison quelconque, je veux le savoir dès que possible. Ie immédiatement quand il est construit. Il y a de bons arguments à faire pour utiliser l'initialisation paresseuse, mais je ne pense pas que ce soit une bonne idée de l'utiliser de manière généralisée .
millimoose
20
Je suis vraiment inquiet que d'autres développeurs voient cette réponse et pensent que c'est en effet une bonne pratique (oh boy). C'est une très très mauvaise pratique si vous l'utilisez sans condition. Outre ce qui a déjà été dit, vous rendez la vie de tout le monde tellement plus difficile (pour les développeurs clients et pour les développeurs de maintenance) pour si peu de gain (voire aucun gain). Vous devriez entendre les pros: Donald Knuth, dans la série The Art of Computer Programming, a dit: «l'optimisation prématurée est la racine de tout mal». Ce que vous faites n'est pas seulement mal, c'est diabolique!
Alex
4
Il y a tellement d'indicateurs que vous choisissez la mauvaise réponse (et la mauvaise décision de programmation). Vous avez plus d'inconvénients que d'avantages dans votre liste. Vous avez plus de personnes qui se portent garantes contre cela que pour cela. Les membres les plus expérimentés de ce site (@BillK et @DanielHilgarth) qui ont posté dans cette question s'y opposent. Votre collègue vous a déjà dit que c'était faux. Sérieusement, c'est faux! Si j'attrape un des développeurs de mon équipe (je suis un chef d'équipe) en train de faire cela, il prendra un délai de 5 minutes et ensuite on lui expliquera pourquoi il ne devrait jamais faire cela.
Alex
17

Envisagez-vous de mettre en œuvre un tel modèle en utilisant Lazy<T>?

En plus de la création facile d'objets chargés différés, vous bénéficiez de la sécurité des threads pendant l'initialisation de l'objet:

Comme d'autres l'ont dit, vous chargez paresseusement des objets s'ils sont vraiment gourmands en ressources ou si cela prend du temps pour les charger pendant la construction de l'objet.

Matías Fidemraizer
la source
Merci, je le comprends maintenant et je vais certainement examiner Lazy<T>maintenant, et m'abstenir d'utiliser la manière dont j'ai toujours l'habitude de faire.
John Willemse
1
Vous n'obtenez pas la sécurité des fils magiques ... vous devez toujours y penser. De MSDN:Making the Lazy<T> object thread safe does not protect the lazily initialized object. If multiple threads can access the lazily initialized object, you must make its properties and methods safe for multithreaded access.
Eric J.
@EricJ. Bien sûr bien sûr. Vous n'obtenez la sécurité des threads que lors de l'initialisation de l'objet, mais plus tard, vous devez gérer la synchronisation comme tout autre objet.
Matías Fidemraizer
9

Je pense que cela dépend de ce que vous initialisez. Je ne le ferais probablement pas pour une liste car le coût de construction est assez faible, donc cela peut aller dans le constructeur. Mais s'il s'agissait d'une liste pré-remplie, je ne le ferais probablement pas avant d'en avoir besoin pour la première fois.

Fondamentalement, si le coût de la construction dépasse le coût de la vérification conditionnelle de chaque accès, créez-le paresseusement. Sinon, faites-le dans le constructeur.

Colin Mackay
la source
Je vous remercie! Ça a du sens.
John Willemse
9

L'inconvénient que je peux voir est que si vous voulez demander si Bars est nul, cela ne le serait jamais et vous créeriez la liste là-bas.

Luis Tellez
la source
Je ne pense pas que ce soit un inconvénient.
Peter Porfy
Pourquoi est-ce un inconvénient? Vérifiez simplement avec any plutôt que null. if (! Foo.Bars.Any ())
s.meijer
6
@PeterPorfy: Cela viole POLS . Vous mettez null, mais ne le récupérez pas. Normalement, vous supposez que vous récupérez la même valeur que celle que vous mettez dans une propriété.
Daniel Hilgarth
@DanielHilgarth Merci encore. C'est un argument très valable que je n'avais pas envisagé auparavant.
John Willemse
6
@AMissico: Ce n'est pas un concept inventé. De la même manière que le fait d'appuyer sur un bouton à côté d'une porte d'entrée devrait sonner une sonnette, les choses qui ressemblent à des propriétés devraient se comporter comme des propriétés. Ouvrir une trappe sous vos pieds est un comportement surprenant, surtout si le bouton n'est pas étiqueté comme tel.
Bryan Boettcher
8

L'instanciation / initialisation paresseuse est un modèle parfaitement viable. Gardez à l'esprit, cependant, qu'en règle générale, les consommateurs de votre API ne s'attendent pas à ce que les getters et les setters prennent du temps perceptible par rapport au PDV de l'utilisateur final (ou échouent).

Tormod
la source
1
Je suis d'accord et j'ai un peu modifié ma question. Je m'attends à ce qu'une chaîne complète de constructeurs sous-jacents prenne plus de temps que l'instanciation de classes uniquement lorsqu'elles sont nécessaires.
John Willemse
8

J'allais juste faire un commentaire sur la réponse de Daniel, mais honnêtement, je ne pense pas que cela va assez loin.

Bien que ce soit un très bon modèle à utiliser dans certaines situations (par exemple, lorsque l'objet est initialisé à partir de la base de données), c'est une habitude HORRIBLE à prendre.

L'un des avantages d'un objet est qu'il offre un environnement sécurisé et fiable. Le meilleur cas est si vous créez autant de champs que possible "Final", en les remplissant tous avec le constructeur. Cela rend votre classe assez à l'épreuve des balles. Permettre aux champs d'être modifiés par le biais des setters est un peu moins le cas, mais pas terrible. Par exemple:

classe SafeClass
{
    Nom de la chaîne = "";
    Âge entier = 0;

    public void setName (chaîne newName)
    {
        assert (nouveauNom! = null)
        nom = nouveauNom;
    } // suivez ce modèle pour l'âge
    ...
    public String toString () {
        String s = "Safe Class has name:" + name + "and age:" + age
    }
}

Avec votre modèle, la méthode toString ressemblerait à ceci:

    if (nom == null)
        throw new IllegalStateException ("SafeClass est entré dans un état illégal! Le nom est nul")
    if (age == null)
        throw new IllegalStateException ("SafeClass est entré dans un état illégal! age is null")

    public String toString () {
        String s = "Safe Class has name:" + name + "and age:" + age
    }

Non seulement cela, mais vous avez besoin de vérifications nulles partout où vous pourriez éventuellement utiliser cet objet dans votre classe (en dehors de votre classe, c'est sûr en raison de la vérification nulle dans le getter, mais vous devriez principalement utiliser les membres de vos classes à l'intérieur de la classe)

De plus, votre classe est perpétuellement dans un état incertain - par exemple, si vous décidiez de faire de cette classe une classe d'hibernation en ajoutant quelques annotations, comment le feriez-vous?

Si vous prenez une décision basée sur une micro-optomisation sans exigences ni tests, c'est presque certainement la mauvaise décision. En fait, il y a de très bonnes chances que votre modèle ralentisse réellement le système, même dans les circonstances les plus idéales, car l'instruction if peut provoquer un échec de prédiction de branche sur le processeur, ce qui ralentira les choses beaucoup plus de fois que attribuer simplement une valeur dans le constructeur, sauf si l'objet que vous créez est assez complexe ou provient d'une source de données distante.

Pour un exemple du problème de prédiction de brance (que vous rencontrez à plusieurs reprises, pas une seule fois), voyez la première réponse à cette question géniale: Pourquoi est-il plus rapide de traiter un tableau trié qu'un tableau non trié?

Bill K
la source
Merci pour votre contribution. Dans mon cas, aucune des classes n'a de méthodes qui peuvent avoir besoin de vérifier null, donc ce n'est pas un problème. Je vais prendre en considération vos autres objections.
John Willemse
Je ne comprends pas vraiment ça. Cela implique que vous n'utilisez pas vos membres dans les classes où ils sont stockés - que vous utilisez simplement les classes comme structures de données. Si tel est le cas, vous voudrez peut-être lire javaworld.com/javaworld/jw-01-2004/jw-0102-toolbox.html qui décrit bien comment votre code peut être amélioré en évitant de manipuler l'état d'objet de l'extérieur. Si vous les manipulez en interne, comment le faire sans tout vérifier à plusieurs reprises?
Bill K
Certaines parties de cette réponse sont bonnes, mais certaines parties semblent artificielles. Normalement, lors de l'utilisation de ce modèle, toString()appellerait getName(), ne pas utiliser namedirectement.
Izkata
@BillK Oui, les classes sont une énorme structure de données. Tout le travail est effectué dans des classes statiques. Je vais consulter l'article sur le lien. Merci!
John Willemse
1
@izkata En fait, à l'intérieur d'une classe, il semble que ce soit un tirage au sort pour savoir si vous utilisez un getter ou non, la plupart des endroits où j'ai travaillé utilisaient directement le membre. Cela mis à part, si vous utilisez toujours un getter, la méthode if () est encore plus nuisible car les échecs de prédiction de branche se produiront plus souvent ET à cause du branchement, le runtime aura probablement plus de difficulté à insérer le getter. Cependant, tout est discutable avec la révélation de John selon laquelle ce sont des structures de données et des classes statiques, c'est ce qui m'inquiéterait le plus.
Bill K
4

Permettez-moi d'ajouter encore un point à de nombreux points positifs soulevés par d'autres ...

Le débogueur évaluera ( par défaut ) les propriétés lors de l' Barexécution du code , ce qui pourrait potentiellement instancier le plus tôt possible en exécutant simplement le code. En d'autres termes, le simple fait de déboguer modifie l'exécution du programme.

Cela peut être un problème ou non (en fonction des effets secondaires), mais il faut en être conscient.

Branko Dimitrijevic
la source
2

Êtes-vous sûr que Foo devrait instancier quoi que ce soit?

Pour moi, cela semble malodorant (mais pas nécessairement faux ) de laisser Foo instancier quoi que ce soit. À moins que ce soit le but exprès de Foo d'être une usine, il ne devrait pas instancier ses propres collaborateurs, mais plutôt les injecter dans son constructeur .

Si cependant le but de Foo est de créer des instances de type Bar, alors je ne vois rien de mal à le faire paresseusement.

KaptajnKold
la source
4
@BenjaminGruenbaum Non, pas vraiment. Et respectueusement, même si c'était le cas, quel point essayiez-vous de faire valoir?
KaptajnKold