J'ai une valeur dont de nombreux objets ont besoin. Par exemple, une application financière avec différents investissements en tant qu'objets, et la plupart d'entre eux ont besoin du taux d'intérêt actuel.
J'espérais encapsuler mon «environnement financier» comme un objet, avec le taux d'intérêt comme une propriété. Mais, les objets frères qui ont besoin de cette valeur ne peuvent pas y accéder.
Alors, comment partager des valeurs entre de nombreux objets sans trop coupler ma conception? De toute évidence, je pense à ce mal.
object-oriented
Greg
la source
la source
update
fonction appelée à chaque pas de temps? Pouvez-vous publier en pseudocode comment fonctionne votre simulation?Singleton
est un global avec du sucre syntaxique OO et c'est une solution terrible qui couple étroitement votre code de certaines des pires façons possibles. Lisez cet article encore et encore jusqu'à ce que vous le compreniez!DateTime
en entrée et renvoie un nombre en sortie.Réponses:
C'est une odeur de design. Il est rare que de nombreux objets aient besoin de savoir quelque chose. Cela dit, le taux d'intérêt actuel est un assez bon exemple de circonstances exceptionnelles. Une chose à craindre est qu'il y a rarement le taux d'intérêt. Différents instruments financiers utilisent des taux différents. À tout le moins, différents paramètres régionaux utilisent des taux «standard» différents. De plus, pour faciliter les tests et les rapports, vous souhaiterez généralement transmettre un taux car vous ne souhaitez pas utiliser le taux actuel . Vous souhaitez utiliser le taux «et si» ou «à la date de déclaration».
En les partageant, en ne les faisant pas tous référence à une seule instance. Faire passer la même chose est toujours un couplage à un degré, mais pas un couplage excessif, car quelque chose comme le taux d'intérêt actuel est nécessaire comme entrée pour une variété de calculs.
la source
Dans ce cas particulier, j'utiliserais le modèle Singleton . Le FinancialEnvironment serait l'objet que toutes les autres bibliothèques de classes connaissent, mais serait instancié par le Singleton. Idéalement, vous enverriez ensuite cet objet instancié aux différentes bibliothèques de classes.
Par exemple:
Les autres classes sont uniquement liées entre elles via la bibliothèque de classes Entities, elles acceptent un objet FinancialEnvironment instancié. Ils ne se soucient pas de la façon dont il a été créé, seule la couche de service le fait, tout ce qu'ils veulent, ce sont les informations. Le singleton pourrait également être suffisamment intelligent pour stocker plusieurs objets FinancialEnvironment, en fonction des règles pour le local, comme l'a souligné @Telastyn.
Soit dit en passant, je ne suis pas un grand fan du motif Singleton, je le considère comme une odeur de code, car il peut être mal utilisé très facilement. Mais dans certains cas, vous en avez besoin.
Mise à jour:
Si vous devez absolument avoir une variable globale, alors l'implémentation du modèle Singleton comme décrit ci-dessus fonctionnerait. Cependant, je ne suis pas un grand fan de cela, et sur la base des commentaires de mon post d'origine, plusieurs autres personnes ne le sont pas non plus. Aussi volatile qu'un taux d'intérêt, un Singleton n'est peut-être pas la meilleure solution. Les singletons fonctionnent mieux lorsque les informations ne changent pas. Par exemple, j'ai utilisé un Singleton dans l'une de mes applications pour instancier des compteurs de performances. Parce que s'ils changent, vous devez avoir une logique en place pour gérer les données mises à jour.
Si j'étais un parieur, je parierais que le taux d'intérêt a été stocké quelque part dans une base de données ou récupéré via un service Web. Dans ce cas, un référentiel (couche d'accès aux données) serait recommandé pour récupérer ces informations. Pour éviter les déplacements inutiles dans la base de données (je ne sais pas à quelle fréquence les taux d'intérêt changent ou d'autres informations dans la classe FinancialInformation), la mise en cache peut être utilisée. Dans le monde C #, la bibliothèque Caching Application Block de Microsoft fonctionne très bien.
La seule chose qui changerait par rapport à l'exemple ci-dessus serait que les différentes classes de la couche de service qui ont besoin de FinancialInformation récupéreraient de la couche d'accès aux données au lieu du Singleton instanciant l'objet.
la source
Singleton
est un monde avec du sucre syntaxique OO et une béquille pour les esprits paresseux et faibles.Singleton/global
est le pire moyen absolu de coupler étroitement votre code à quelque chose qui sera un cancer plus tard lorsque vous réaliserez à quel point c'était une mauvaise idée colossale et pourquoi tout le monde le dit!Fichiers de configuration?
Si vous avez des valeurs qui sont utilisées "globalement", veuillez les mettre dans un fichier de configuration. Ensuite, chaque système et sous-système peut référencer cela et tirer les clés nécessaires, les rendre en lecture seule.
la source
Je parle de l'expérience de celui qui a environ un mois de maintenance sur un projet de bonne taille (~ 50k LOC) que nous venons de publier.
Je peux vous dire que vous ne voulez probablement pas vraiment un objet global. L'introduction de ce type de cruauté offre beaucoup plus de possibilités d'abus qu'elle n'aide.
Ma suggestion initiale est que si vous avez plusieurs classes différentes qui ont besoin d'un taux d'intérêt actuel, vous voudrez probablement simplement leur demander de mettre en œuvre un
IInterestRateConsumer
ou quelque chose. À l'intérieur de cette interface, vous aurez unSetCurrentInterestRate(double rate)
(ou tout ce qui a du sens), ou peut-être juste une propriété.Faire passer un taux d'intérêt n'est pas un couplage - Si votre classe a besoin d'un taux d'intérêt, cela fait partie de son API. Ce n'est que du couplage si l'une de vos classes commence à s'inquiéter de la manière exacte dont l'autre classe utilise ce taux d'intérêt.
la source
Martin Fowler a un article qui explique brièvement comment transformer un global statique en quelque chose de plus flexible. Fondamentalement, vous en faites un singleton, puis modifiez le singleton afin qu'il prenne en charge la substitution de la classe de l'instance avec une sous-classe (et si nécessaire, déplacez la logique qui crée l'instance vers une classe distincte qui peut être sous-classée, ce que vous feriez si la création de l'instance de super-classe, le remplacement ultérieur est un problème).
Bien sûr, vous devez peser les problèmes avec les singletons (même les singletons substituables) par rapport à la douleur de passer partout le même objet.
En ce qui concerne l'objet "environnement financier" - il est pratique de programmer lors de la première passe, mais lorsque vous avez terminé, vous avez ajouté quelques dépendances supplémentaires. Les classes qui ont juste besoin d'un taux d'intérêt ne fonctionnent désormais que lorsqu'elles sont passées un objet d'environnement financier, ce qui les rendra difficiles à réutiliser lorsque vous n'avez pas d'objet d'environnement financier qui traîne. Je découragerais donc de le diffuser largement.
la source
Pourquoi ne pas mettre les données de taux d'intérêt dans une cache centrale?
Vous pouvez utiliser l'une des nombreuses bibliothèques de cache, selon celle qui vous convient le mieux, quelque chose comme memcached résout tous vos problèmes de concurrence et de gestion de code et permettra à votre application de s'adapter à plusieurs processus.
Ou allez tout le porc et stockez-les dans une base de données, ce qui vous permettra de vous adapter à plusieurs serveurs.
la source
Dans de telles situations, j'ai réussi à introduire (réutiliser) le terme "contexte" avec parfois plusieurs couches.
Cela signifie un magasin d'objets unique, donc "global", à partir duquel ce type d'objets peut être demandé. Les codes qui les nécessitent, incluent l'en-tête du magasin et utilisent les fonctions globales pour obtenir leurs instances d'objet (comme maintenant, le fournisseur de taux d'intérêt).
Le magasin peut être soit:
Plus le système est grand, plus cette dernière solution est utilisable, pour un risque assez faible d'utiliser la mauvaise énumération. D'un autre côté, avec les langages qui autorisent les déclarations de type direct, je pense que vous pouvez utiliser des accesseurs typés sans inclure tous les en-têtes dans le magasin.
Encore une remarque: vous pouvez avoir plusieurs instances du même type d'objet pour différentes utilisations, comme parfois une valeur de langue différente pour l'interface graphique et pour l'impression, les journaux de niveau global et de session, etc., donc le nom de l'énumération / accesseur ne doit PAS refléter le type réel , mais le rôle de l'instance demandée (CurrentInterestRate).
Dans l'implémentation du magasin, vous devez gérer les niveaux de contexte et les collections d'instances de contexte. Un exemple simple est le service Web, où vous avez le contexte global (une instance pour toutes les demandes pour cet objet - problématique lors de la création d'une batterie de serveurs) et un contexte pour chaque session Web. Vous pouvez également avoir des contextes pour chaque utilisateur, qui peut avoir plusieurs sessions parallèles, etc. Avec plusieurs serveurs, vous devez utiliser une sorte de cache distribué pour de telles choses.
Lorsque la demande arrive, vous décidez du niveau de contexte de l'objet demandé, obtenez ce contexte pour l'appel. Si l'objet est là, vous le renvoyez; sinon, vous le créez et le stockez à ce niveau de contexte et le renvoyez. Bien sûr, synchronisez la section de création (et publiez-la dans le cache distribué). La création peut être configurable comme un plugin, mieux avec des langages permettant de créer des instances d'objet par leur nom de classe (Java, Objective C, ...), mais vous pouvez le faire en C également avec des bibliothèques pluggables ayant des fonctions d'usine.
Remarque: l'appelant ne doit PAS en savoir trop sur ses propres contextes et sur le niveau de contexte de l'objet demandé. Raisons: 1: il est facile de se tromper (ou "astuces astucieuses") en jouant avec ces paramètres; 2: le niveau de contexte du demandé pourrait changer ultérieurement. Je connecte principalement les informations de contexte au thread, de sorte que le magasin d'objets a les informations sans paramètres supplémentaires de la demande.
D'un autre côté, la demande peut contenir un indice pour l'instance: comme obtenir le taux d'intérêt pour une date spécifique. Il doit s'agir du même accès "global", mais de plusieurs instances en fonction de la date (et en menant différentes valeurs de date dans la même instance entre les changements de taux), il est donc conseillé d'ajouter un objet "hint" à la demande, utilisé par le par exemple l'usine et non le magasin; et un keyForHint à l'usine, utilisé par le magasin. Vous pouvez ajouter ces fonctions plus tard, je viens de le mentionner.
Dans votre cas, c'est une sorte de surpuissance (un seul objet est servi au niveau global), mais pour un code supplémentaire assez petit et simple en ce moment, vous obtenez un mécanisme pour des exigences supplémentaires, peut-être plus complexes.
Autre bonne nouvelle: si vous êtes en Java, vous obtenez ce service de Spring sans trop réfléchir, je voulais juste l'expliquer en détails.
la source
La raison de NE PAS utiliser un global (ou un singleton) est que même si vous vous attendez initialement à n'avoir qu'une seule valeur, il est souvent étonnamment utile de pouvoir utiliser le même code plusieurs fois dans le même programme, par exemple:
Je ferais du taux d'intérêt un membre de la classe des "instruments financiers" et accepterais que vous le transmettiez à toutes les classes de membres (soit par calcul, soit en leur donnant un pointeur / crochet sur la construction).
la source
Les choses devraient être passées dans des messages, pas lues à partir d'un truc global.
la source