如果全局变量有意义?


10

我拥有许多对象所需的价值。例如,一个以不同投资为对象的金融应用程序,其中大多数需要当前利率。

我希望将我的“金融环境”封装为一个对象,而利率作为一种属性。但是,需要该值的同级对象无法实现。

那么,如何在多个对象之间共享价值却又不会过度耦合我的设计?显然我在想这个错误。


2
利率在计算期间是固定的吗?还是您在做模拟之类的事情,利率可能会随时间而变化?
詹姆斯

它更像是模拟-在运行过程中可以更改。
格雷格

在那种情况下,每项投资是否真的需要节省利率,或者是否可以通过update每个时间步调用的函数的参数来接收利率?您可以用伪代码发布模拟的运行方式吗?
詹姆斯

3
您是正确的方向,a Singleton是一个带有OO语法糖的全局变量,并且是一种可怕的解决方案,它以某些最坏的方式将您的代码紧密耦合在一起。一遍又一遍地阅读本文,直到您理解为止!

利率就像一个时间序列,它是一个函数,将DateTime输入作为输入并返回一个数字作为输出。
rwong 2012年

Answers:


14

我拥有许多对象所需的价值。

这是设计气味。许多对象需要了解某些事物是很罕见的。也就是说,当前利率是例外情况的一个很好的例子。有一点需要担心的是,有很少利率。不同的金融工具使用不同的汇率。至少,不同的区域使用不同的“标准”费率。此外,为了帮助测试和报告,您通常希望传递一个费率,因为您不想在那里使用当前费率。您要使用“假设情况”或“截至报告日期”的费率。

那么,如何在不过度耦合设计的情况下在许多对象之间共享价值呢?

通过共享它们,并不是让它们全部都引用一个实例。传递相同的东西仍在一定程度耦合,但不会过度耦合,因为需要诸如当前利率之类的东西作为各种计算的输入。


17
特殊情况下的问题是,在现实世界中,它们并不是那么特殊。
mattnz

我认为这是一个严重的误会。我们的算法同时存在于不同的上下文中。首先是全球背景。然后是面向对象语言的“ this”上下文。Web服务中的会话上下文,数据库环境中的事务上下文,gui的主窗口,...,以及属于这些上下文的一些信息。它们必须“在周围徘徊”并且对于相同上下文中的任何人都可用相同的集合(对象)。没关系 问题是没有为每个对象解决此问题,而不是通过创建上下文服务或使用框架(如Java中的Spring)来解决。
罗兰·基德维斯(Lorand Kedves)2012年

3
当前的利率没有什么例外。现实世界中有许多具有一个值的示例,例如-开路速度限制,可接受的血液酒精水平,GST(或增值税)税率等等。那就是科学与工程之间的区别-工程师解决了当今的现实世界中的问题,科学家们梦想着有一天现实世界将融入完美的完美盒子中并解决这些问题。
mattnz

1
我选择这个作为答案,是因为它很简单并且不需要依靠十年的OOP经验。非常感谢所有受访者。由于阅读了大量参考文献,我整天都在读书,但仍然有些困惑。对于这样一个简单的问题,我对回应的多样性和背后的情感感到惊讶。我仍然坚信,有时候会有一个集中的全球性但变化的数据源,最好由Singleton提供。我认为不应为了避免单例而在对象的层次结构中上下传递指针。再次感谢大家。
格雷格

@mattnz,问题在于,如果您将程序分发给可以跨越公司,州或国家/地区的多个用户群,则示例中的每个示例都是可变的。所有这些都可以随时间变化。
丹·里昂斯

10

在这种情况下,我将使用Singleton模式。FinancialEnvironment将是所有其他类库都知道的对象,但是将由Singleton实例化。理想的情况是,然后将实例化的对象发送到各个类库。

例如:

  • 服务层(类库)-通过单例实例化FinancialEnvironment对象
  • 业务逻辑层(类库)-从服务层接受FinancialEnvironment对象
  • 数据访问层(类库)-接受来自服务层的FinancialEnvironment对象(或取决于您的体系结构的业务逻辑层)。也许Singleton调用数据访问层以从存储库(数据库/ Web服务/ WCF服务)中获取信息,例如利率。
  • 实体(或DTO,如果您要称呼它)类库-FinancialEnvironment对象所在的位置。所有其他类库都有对Entities类库的引用。

其他类仅通过Entities类库绑定在一起,它们接受实例化的FinancialEnvironment对象。他们不在乎它是如何创建的,只有服务层在做,他们想要的只是信息。正如@Telastyn指出的那样,根据本地的规则,单例也可以足够聪明地存储几个FinancialEnvironment对象。

附带说明一下,我不是Singleton模式的忠实拥护者,我认为它是一种代码气味,因为它很容易被滥用。但是在某些情况下,您需要它。

更新:

如果绝对要肯定要有一个全局变量,则可以按上述方法实现Singleton Pattern。但是,我不是对此的忠实拥护者,并且根据我原始帖子的评论,其他几个人也不是。就像InterestRate一样易变,Singleton可能不是最佳解决方案。当信息不变时,单例最有效。例如,我在我的一个应用程序中使用了Singleton来实例化性能计数器。因为如果它们确实发生更改,那么您必须具有适当的逻辑来处理要更新的数据。

如果我是一个博彩业者,我敢打赌,利率存储在数据库中的某个位置,或者通过Web服务检索。在这种情况下,建议使用存储库(数据访问层)来检索该信息。为了避免不必要的数据库访问(我不确定利率变化的频率或FinancialInformation类中的其他信息),可以使用缓存。在C#世界中,Microsoft的缓存应用程序块库运行良好。

与上面的示例唯一不同的是,服务层中需要FinancialInformation的各种类将从数据访问层检索,而不是用Singleton实例化对象。


8
啊。使整体成为单例并不会使它变得更难闻。如果有什么限制您自己的话,那就更多了。
Telastyn

3
@DavidCowden实施起来并不复杂,这并不重要。它们使您的设计比全局设计更糟糕。它们是全球性的并且强制执行(不必要的)限制,使您只有一个限制。
Telastyn 2012年

4
我本来要发表讽刺的评论,说“将其变成一个单例,它将从不良实践变为最佳实践”,但后来我看到它已经是一个已被接受并获得投票支持的答案。非常好!
凯文

4
Singleton是具有OO语法糖和懒惰和弱智的拐杖的全球性公司。当您意识到这是一个非常糟糕的主意以及为什么每个人都说自己是错的时候,将代码紧密耦合到可能会致癌的东西Singleton/global绝对是最糟糕的方法!

4
@Telastyn:这是一个不幸的现实,即大多数完美的设计一旦离开了理论软件设计的完美有序世界,便加入了现实世界。
mattnz

4

配置文件?

如果您具有“全局”使用的值,请将它们放在配置文件中。然后,每个系统和子系统都可以引用它并提取所需的密钥,使其成为只读。


因此,您希望用户每次利率变化时都更新配置文件吗?
Caleb

2
为什么不?取决于“变量”,当然应该将经常更改的内容放置在CENTRALIZED数据存储区中。
黑暗之夜

1

我是根据一个人的经验说的,这个人对我们刚刚发布的一个大型(约50k LOC)项目进行了大约一个月的维护。

我可以告诉你,您可能真的不想要全局对象。引入这种残酷行为提供的滥用机会多于它的实际帮助。

我最初的建议是,如果您有几个需要当前利率的不同类,那么您可能只想让它们实现IInterestRateConsumer或即可。在该界面内,您将拥有一个SetCurrentInterestRate(double rate)(或任何有意义的东西),或者仅仅是一个属性。

传递利率实际上并没有耦合-如果您的班级需要利率,那是其API的一部分。仅当您的一个班级开始担心另一个班级如何使用该利率时,这才是耦合。


传递利率就是耦合,这并不是耦合。
vaughandroid 2012年

1

马丁·福勒(Martin Fowler)的文章简短地谈到了如何将静态全局重构为更灵活的东西。基本上,您将其制作为一个单例,然后修改该单例,以便它支持使用子类覆盖实例的类(并且如果需要,可以将创建实例的逻辑移到可以进行子类化的单独类中,您可以这样做)如果创建超类实例,则稍后将其替换是一个问题)。

当然,您必须权衡单例(甚至是可替代的单例)的问题与将相同对象传递到各处的痛苦。

至于“财务环境”对象-第一次编程很方便,但是完成后,您已经添加了一些额外的依赖项。现在,仅需要利率的类仅在通过金融环境对象时才起作用,这将使它们在您没有碰巧躺在金融环境对象附近时难以重用。因此,我不鼓励广泛通过它。


0

为什么不将利率数据放在中央缓存中?

您可以使用最适合您需求的几个缓存库之一,诸如memcached之类的东西可以解决所有并发和代码管理问题,并使您的应用程序可以扩展到多个进程。

或全力以赴,并将它们存储在数据库中,这将使您可以扩展到多个服务器。


0

在这种情况下,我已经成功引入(重复使用)有时会多层的“上下文”一词。

这意味着一个单例,即“全局”对象存储,可以从中请求这些类型的对象。需要它们的代码包括商店的标题,并使用全局函数获取其对象实例(例如现在的利率提供者)。

商店可能是:

  • 严格类型化:包括所有服务类型的标头,因此您可以创建类型化访问器,例如InterestRate getCurrentInterestRate();。
  • 或通用:Object getObject(enum obType); 并且仅使用新种类(obtypeCurrentInterestRate)扩展obType枚举。

系统越大,后一种解决方案就越有用,因为使用错误枚举的风险很小。另一方面,对于允许前向类型声明的语言,我认为您可以使用类型化访问器而无需在商店中包含所有标头。

还有一点需要注意:您可能具有相同对象类型的多个实例,以用于不同用途,例如有时GUI和打印输出,全局和会话级日志等使用不同的Language值,因此枚举/访问者名称不应反映实际类型,但是所请求实例的角色(CurrentInterestRate)。

在商店实现中,您必须管理上下文级别和上下文实例集合。一个简单的示例是Web服务,其中具有全局上下文(该对象的所有请求的一个实例-在拥有服务器场时有问题),以及每个Web会话的上下文。您还可以为每个用户提供上下文,这些用户可能具有多个并行的会话等。对于多台服务器,您应该为此类事情使用一种分布式缓存。

当请求进入时,您可以确定请求的对象位于哪个上下文级别,并获取该调用的上下文。如果对象在那里,则将其发送回;如果不是,则在该上下文级别创建并存储它,然后将其返回。当然,同步创建部分(并将其发布到分布式缓存)。创建可以是可配置的类似于插件的方式,最好使用允许通过其类名称(Java,Objective C,...)创建对象实例的语言,但是您也可以使用具有工厂功能的可插入库在C中执行此操作。

旁注:调用者不应对自己的上下文以及所请求对象的上下文级别了解太多。原因:1:通过使用这些参数很容易犯错误(或“聪明的把戏”);2:所请求的上下文级别以后可能会更改。我主要将上下文信息连接到线程,因此对象存储库中的信息没有请求中的额外参数。

另一方面,请求可能包含实例的提示:例如获取特定日期的利率。它应具有相同的“全局”访问权限,但要根据日期使用多个实例(并在费率变化之间将不同的日期值引入同一实例),因此建议向请求添加“提示”对象,实例工厂而不是商店;以及工厂使用的keyForHint。我刚才提到,您可以稍后添加这些功能。

就您的情况而言,这是一种矫(过正(在全局级别上仅提供一个对象),但是对于现在很小而又简单的额外代码,您将获得一种机制,可以满足进一步甚至更复杂的需求。

另一个好消息:如果您使用Java,则无需过多考虑即可从Spring获得此服务,我只是想详细解释一下。


0

不使用全局(或单例)的原因是,即使您最初希望只具有一个值,但是在同一程序中多次使用相同的代码通常非常有用,例如:

  • 计算如果利率不同会发生什么
  • 某些成分取决于美国利率,而某些成分取决于英国利率

我将使利率成为“金融工具”类的成员,并接受您必须将其传递给任何成员类(按计算或在构建时为它们指定一个指针/挂钩)。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.