通过方法链传递上下文的模式


19

这是出现要拿出相当多的设计决策:如何通过上下文通过并不需要它,做的方法等。是否有一个正确的答案,或者它取决于上下文。

需要解决方案的示例代码

// needs the dependency
function baz(session) {
  session('baz');
}

// doesn't care about the dependency
function bar() {
  baz();
}

// needs the dependency
function foo(session) {
   session('foo')
   bar();
}

// creates the dependency
function start() {
  let session = new Session();
  foo(session);
}

可能的解决方案

  • 本地线程
  • 全球
  • 上下文对象
  • 通过依赖
  • 咖喱baz并将其传递给bar,并将依赖项设置为第一个arg
  • 依赖注入

哪里出现的例子

HTTP请求处理

经常使用请求属性形式的上下文对象:请参见expressjs,Java Servlet或.net的owin。

记录中

对于Java日志记录,人们经常使用全局变量/单例。请参阅典型的log4j / commons日志记录/ java日志记录模式。

交易次数

线程局部变量通常用于使事务或会话与方法调用链相关联,以避免需要将它们作为参数传递给所有不需要它们的方法。


请使用更有意义的示例。
图兰斯·科尔多瓦

我添加了一些示例。
Jamie McCrindle 2015年

3
我的意思是一个更有意义的示例代码。
图兰斯·科尔多瓦

Answers:


11

唯一公平的答案是,这取决于您的编程范例习惯用法。如果您使用的是OO,则在方法之间传递依赖关系几乎是不正确的。这是OO中的代码味道。实际上,这是OO解决的问题之一-对象固定上下文。因此,在OO中,一种正确的方法(总是有其他方法)是通过构造函数或属性传递依赖关系。评论者提到“依赖注入”,这是完全合法的,但并非绝对必要。只需提供依赖项,即可将其作为fooand 的成员使用baz

您提到了currying,所以我假设函数式编程不是不可能的。在那种情况下,对象上下文的哲学上的等同就是闭包。再次修复依赖关系以使依赖关系可用的任何方法都可以正常工作。咖喱就是这样一种方法(它使您听起来很聪明)。请记住,还有其他方法可以关闭依赖项。其中有些优雅,有些可怕。

不要忘记面向方面的编程。在过去的几年中,它似乎已不受欢迎,但它的主要目标是完全解决您描述的问题。实际上,经典的Aspect示例是日志记录。在AOP中,依赖关系是在编写其他代码后自动添加的。AOP人们称此为“ 编织 ”。在适当的地方将共同的方面编织到代码中。这使您的代码更易于思考,非常酷,但同时也增加了新的测试负担。您需要一种方法来确定最终的声音是否良好。AOP对此也有答案,所以不要感到害怕。


声称在OO方法中传递参数是一种代码味道,这是一个引起争议的声明。我会提出完全相反的观点:鼓励在类中混合状态和功能是OO范例所犯的最大错误之一,而通过直接将依赖项注入方法而不是通过构造函数来避免它是设计良好的标志代码,无论是否为OO。
David Arno

3
@DavidArno我建议使用不同的范式与结论对象的有状态性是“ OO范式所犯的最大错误之一”,然后规避该范式。我几乎不反对任何方法,但通常不喜欢作者在使用其工具的代码。私有状态是OO的显着特征。如果不使用该功能,则会失去OO的某些功能。
罗杰斯(Roger Roger)2015年

1
@DavidArno一个全状态的类,没有任何功能,没有在状态中强制不变关系的机制。这样的类根本不是OO。
凯文·克鲁姆维德

@KevinKrumwiede,在某种程度上,您已经对我的评论使用了还原词absudium,但您的观点仍然很好。状态不变是“从OO前进”的重要组成部分。因此,要避免混合功能和状态,必须在状态对象中允许足够的功能以实现不变性(构造函数设置并通过getter访问的封装字段)。
David Arno

@ScantRoger,我同意可以采用另一个范式,即功能范式。有趣的是,大多数现代的“ OO”语言具有越来越多的功能,因此有可能坚持使用这些语言并采用功能范式,而无需“奋斗”工具。
David Arno

10

如果bar依赖baz,而这又需要dependency,则bar需要dependency过才能正确使用baz。因此,正确的方法是将依赖项作为参数传递给bar,或将咖喱baz传递给bar

第一种方法更易于实现和阅读,但是会在bar和之间建立耦合baz。第二种方法消除了这种耦合,但是可能导致代码不清晰。因此,哪种方法最好,将取决于两个功能的复杂性和行为。例如,如果bazdependency有副作用,易用性的测试将可能是在其中选择的解决方案的大型驱动程序。

我建议您建议的所有其他选项本质上都是“ hacky”的,并且可能导致测试问题和难以跟踪的错误。


1
我几乎完全同意。依赖注入可能是另一种非“ hacky”方法。
Jonathan van de Veen 2015年

1
@JonathanvandeVeen,dependency通过参数传递的行为肯定是依赖注入?
David Arno

2
@DavidArno依赖注入框架不会摆脱这些依赖,它们只是移动它们。神奇的是,他们将它们移出课程,到了测试是“其他人的问题”的地方。
凯文·克鲁姆维德

@JonathanvandeVeen我同意,依赖注入是一种有效的解决方案。实际上,这是我最常选择的一种。
Jamie McCrindle

1

哲学上讲

我同意戴维·亚诺的关注

我在阅读OP时正在寻找实现解决方案。但是,答案是更改设计。“模式”?OO设计可以说是所有关于上下文的。这是一张巨大的空白纸,充满了可能性。

处理现有代码是一个不同的环境。



我现在正在处理“几乎相同的问题”。好吧,我正在修复成百上千行的代码copy-n-paste,只是为了插入值。

模块化代码

我扔掉了600行重复的代码,然后进行了重构,因此不是“ A调用B调用C调用D ...”,而是“ A调用,返回,B调用,返回,C调用”。现在,我们只需要将值注入这些方法之一即可,例如方法E。

向构造函数添加默认参数。现有呼叫者不会更改-“可选”是此处的有效词。如果未传递参数,则使用默认值。然后只需更改一行即可将变量传递到重构的模块化结构中;和使用方法E的微小变化。


关闭

程序员线程-“为什么程序要使用闭包?”

本质上,您是将值注入到方法中,该方法返回使用值定制的方法。该定制方法随后被执行。

该技术将允许您修改现有方法而无需更改其签名。


这种方法看起来很陌生 ……

关于时间耦合(您的链接)问题的Roger,@ Snowman。封装所需的执行顺序很重要。
Radarbob 2015年
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.