Et si les mondiaux avaient du sens?

10

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.

Greg
la source
2
Le taux d'intérêt est-il fixe pour la durée de vos calculs ou faites-vous quelque chose comme une simulation où il peut varier entre les pas de temps?
James
Cela ressemble plus à une simulation - elle peut changer pendant la course.
Greg
Dans ce cas, chaque investissement a-t-il vraiment besoin d'économiser le taux d'intérêt ou peut-il le recevoir via un paramètre vers une updatefonction appelée à chaque pas de temps? Pouvez-vous publier en pseudocode comment fonctionne votre simulation?
James
3
Vous êtes une bonne piste, un Singletonest 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!
Le taux d'intérêt est comme une série chronologique qui est une fonction qui prend DateTimeen entrée et renvoie un nombre en sortie.
rwong

Réponses:

14

J'ai une valeur dont de nombreux objets ont besoin.

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».

Alors, comment partager des valeurs entre de nombreux objets sans trop coupler ma conception?

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.

Telastyn
la source
17
Le problème avec des circonstances exceptionnelles est que dans les logiciels du monde réel, ils ne sont pas exceptionnels.
mattnz
Je pense que c'est un grave malentendu. Nos algorithmes existent simultanément dans différents contextes. Le premier est le contexte mondial. Puis "ce" contexte pour les langages orientés objet. Contexte de session dans un service Web, contexte de transaction dans un environnement de base de données, fenêtre principale pour une interface graphique, ... et il y a des informations qui appartiennent à ces contextes. Ils doivent "traîner" et être disponibles, le même ensemble (objets) pour n'importe qui dans le même contexte. C'est d'accord. Le problème est de résoudre ce problème pour chaque objet, pas en créant un service de contexte ou en utilisant un framework, comme Spring en Java.
Lorand Kedves
3
Le taux d'intérêt actuel n'a rien d'exceptionnel. Il existe de nombreux exemples d'articles du monde réel ayant une valeur, - Limite de vitesse sur route ouverte, taux d'alcoolémie acceptable, taux de taxe sur la TPS (ou TVA) pour n'en nommer que quelques-uns. C'est la différence entre la science et l'ingénierie - les ingénieurs résolvent les problèmes du monde réel d'aujourd'hui, les scientifiques rêvent du jour où le monde réel s'intégrera dans de belles boîtes de perfection et résoudra ces problèmes.
mattnz
1
J'ai choisi cela comme réponse parce que c'est simple et ne dépend pas d'une décennie d'expérience OOP pour grok. BEAUCOUP merci à tous les répondants. J'ai eu une journée complète de lecture grâce aux nombreuses références mais reste encore un peu perplexe. Pour une question aussi simple, j'ai été surpris par la variété et l'émotion derrière les réponses. Je reste convaincu qu'il existe parfois une source centrale de données globales mais variables mieux servies par un Singleton. Je ne pense pas que l'on devrait passer des pointeurs de haut en bas d'une hiérarchie d'objets juste pour éviter un singleton. Merci encore à tous.
Greg
@mattnz, le problème est que chacun de vos exemples est variable dans les cas où vous distribuez votre programme à plusieurs bases d'utilisateurs qui peuvent s'étendre à des entreprises, des États ou des pays. Tous peuvent également être variables dans le temps.
Dan Lyons
10

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:

  • Service Layer (bibliothèque de classes) - Instancie l'objet FinancialEnvironment via un singleton
  • Business Logic Layer (bibliothèque de classes) - Accepte l'objet FinancialEnvironment à partir de la couche de service
  • Couche d'accès aux données (bibliothèque de classes) - Accepte l'objet FinancialEnvironment à partir de la couche de service (ou, en fonction de votre architecture, la couche de logique métier). Ou peut-être que le Singleton invoque la couche d'accès aux données pour obtenir des informations, telles que le taux d'intérêt, à partir d'un référentiel (base de données / service Web / service WCF).
  • Bibliothèque de classes d'entités (ou DTO si vous voulez l'appeler ainsi) - où réside l'objet FinancialEnvironment. Toutes les autres bibliothèques de classes ont une référence à la bibliothèque de classes Entities.

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.

bwalk2895
la source
8
Pouah. Faire du monde un singleton ne le rend pas moins malodorant. Si vous vous êtes restreint encore plus.
Telastyn
3
@DavidCowden peu importe s'ils ne sont pas complexes à mettre en œuvre; ils fubar votre conception pire que les autres. Ils sont globaux et imposent une restriction (inutile) selon laquelle vous n'en avez qu'un.
Telastyn
4
J'allais publier un commentaire sarcastique disant "faites-en un singleton et cela passera des mauvaises pratiques aux meilleures pratiques", mais j'ai ensuite vu que c'était déjà une réponse acceptée et votée. Très agréable!
Kevin
4
Singletonest un monde avec du sucre syntaxique OO et une béquille pour les esprits paresseux et faibles. Singleton/globalest 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!
4
@Telastyn: C'est une triste réalité que la plupart des conceptions parfaites une fois qu'elles quittent le monde parfaitement ordonné de la conception de logiciels théoriques et rejoignent le monde réel, fubar'd.
mattnz
4

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.

Nuit noire
la source
Vous souhaitez donc que l'utilisateur mette à jour un fichier de configuration à chaque fois que le taux d'intérêt change?
Caleb
2
Pourquoi pas? dépend de la "variable" bien sûr, les choses qui changent fréquemment doivent être placées dans une banque de données CENTRALISÉE.
Darknight
1

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 IInterestRateConsumerou quelque chose. À l'intérieur de cette interface, vous aurez un SetCurrentInterestRate(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.

Wayne Werner
la source
Faire passer un taux d'intérêt est un couplage, ce n'est tout simplement pas un mauvais couplage.
vaughandroid
1

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.

psr
la source
0

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.

James Anderson
la source
0

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:

  • strictement typé: vous incluez les en-têtes pour tous les types servis et vous pouvez donc créer des accesseurs typés, comme InterestRate getCurrentInterestRate ();
  • ou générique: Object getObject (enum obType); et étendre uniquement l'énumération obType avec les nouveaux types (obtypeCurrentInterestRate).

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.

Lorand Kedves
la source
0

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:

  • pour calculer ce qui se produirait si le taux d'intérêt était différent
  • que certaines composantes dépendent du taux d'intérêt américain et que certaines composantes dépendent du taux d'intérêt britannique

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).

Jack V.
la source
0

Les choses devraient être passées dans des messages, pas lues à partir d'un truc global.

Tulains Córdova
la source