在类构造函数中注入数据(相对于行为)意味着什么,为什么这被认为是不好的做法?


10

我正在阅读Remo Jansen的书“ Learning TypeScript”。在一个部分中,作者描述了如何创建一个非常简单的概念验证MVC框架,包括如何创建Model类,并说了以下几点:

需要向模型提供其使用的Web服务的URL。我们将使用一个名为ModelSettings的类装饰器来设置要使用的服务的URL。我们可以通过其构造函数注入服务URL,但通过类构造函数注入数据(与行为相反)被认为是一种不好的做法

我不明白那句话。特别是,我不明白“注入数据”的含义。在我看来,在几乎所有使用过度简化示例的JavaScript类介绍中,数据都是通过其参数引入(“注入”?)到构造函数中的。例如:

class Person {
  constructor(name) {
    this.name = name;
  }
}

我当然想到 name是数据,而不是行为,并且在此类示例中普遍将其作为构造函数参数包含在内,并且从未有人提到这是不好的做法。因此,我认为我误解了上面引用中的某些内容,无论是“数据”还是“注入”或其他含义。

您的答案可能包括在JavaScript / TypeScript中何时,何地,如何以及为什么使用装饰器的解释,因为我强烈怀疑该概念与我寻求的理解密切相关。但是,更重要的是,我想更广泛地理解通过类构造函数注入数据的含义以及为什么这样做很糟糕。


为了使上面的引用有更多的上下文,这是这种情况:Model创建了一个类,在本示例中,该类将用于创建股票交易模型,一个用于NASDAQ,一个用于NYSE。每个模型都需要提供原始数据的Web服务或静态数据文件的路径。该书指出,应该使用装饰器(而非构造器参数)获取此信息,从而导致以下结果:

@ModelSettings("./data/nasdaq.json")
class NasdaqModel extends Model implements IModel {
  constructor(metiator : IMediator) {
    super(metiator);
  }
...
}

我只是一直不明白为什么我应该通过装饰器而不是简单地作为构造函数的参数来添加服务网址,例如

constructor(metiator : IMediator, serviceUrl : string) {...

我建议您在Google上进行有关依赖项注入的快速搜索。这不是问这个问题的正确论坛。:)
toskv

1
我会全力以赴,但是我已经搜索过google,并遇到了有关依赖项注入的讨论。“依赖注入”和“数据注入”是指同一事物吗?此外,我的印象是“依赖注入”是“好事”(或至少是“另类”),而我提供的报价中对“数据注入”的讨论使它看起来像是“坏事”。 。

依赖注入和数据注入是两件事。第一是设计原则,第二是攻击类型。如果您想要更清晰的搜索词,请尝试“控制权反转”。它的范围更广一些,但确实有助于画出更清晰的画面。
toskv

1
我相信,“数据注入”攻击是一种截然不同的动物,与引用书的作者所说的“注入数据”有关。这就是我对此感到谷歌搜索感到沮丧的原因之一。即使我需要更好地理解例如SOLID原理,也无法理解如何将“名称”作为参数提供给“人”构造函数是正常的并且可以,但是为“模型”提供参数“ serviceUrl”构造函数是不合适的,或者与“名称” /“人”示例有什么不同。

7
我认为雷莫(Remo)弄错了。不管他说什么,参数都是数据。注入的数据始终具有类型, 并且面向对象语言中的所有类型都具有某种行为。
罗伯特·哈维

Answers:


5

我会给作者带来疑问的好处,也许这就是Typescript的方式,但是在其他环境中,这是完全没有根据的主张,不应认真对待。

我不禁想到,在各种情况下,通过构造函数传递数据是好的,有些是中立的,但在坏的情况下没有。

如果特定的类依赖于特定的数据块才能处于有效状态并正确运行,则在构造函数中要求该数据是很有意义的。代表串行端口的类可以使用端口名称,文件对象可能需要文件名,绘图画布需要其分辨率,等等。除非您在构造函数中传递数据,否则可能使对象处于无效状态,从而导致必须注意检查。否则,您只能在对象实例化时检查,然后在大多数情况下假定其工作正常。作者声称,这种有益的情况是不可能的。

另外,决定禁止在构造函数中传递数据实际上也使几乎所有不可变的对象成为不可能。不变的对象在许多情况下具有多种好处,而所有这些好处都会随作者的政策而被丢弃。

即使您想要可变的对象,这种不良做法也会如何:

var blah = new Rectangle(x,y,width,height);

有利于:

var blah = new Rectangle();
blah.X = x;
blah.Y = y;
blah.Width = width;
blah.Height = height;

作者是否真的认为第一种做法是不好的做法,我应该始终选择选项2?我认为这是个疯狂的话题。

因此,由于我没有这本书,即使我也不会阅读,因此在这一点上,我会非常怀疑地查看该声明以及其中的几乎所有常规声明。


非常感谢您的讨论。您对我说的“对”,特别是在您以Rectangle为例时。我仍然想知道作者是否在区分类所需的数据和类的每个实例之间的区别。但是,我认为本书所描述的项目并没有深入到足以澄清这一点的地方。附带说明一下,您的回答使我对对象不变性进行了初步调查,无论它与我的原始问题有多少无关,因此也非常感谢!
Andrew Willems

0

我认为这取决于上下文,这里讨论的是哪种模型。我没有Remo的书,但是我想该模型是一种服务模型,需要从远程Web服务检索数据。在这种情况下,作为Web服务模型,最好将所需的所有数据作为参数传递给Web服务的方法,从而使服务变为无状态。

无状态服务具有几个优点,例如,在构造服务以查找所调用服务的详细信息时,任何阅读服务方法调用的人都无需查找。所有详细信息都显示在方法调用中使用的参数中(远程URL除外)。


是的,该模型需要检索数据(最终如您所愿从远程Web服务检索,但是在书中这只是一个演示,因此最初只是模拟数据直接内联编码)。我不理解您关于“在Web服务的方法中”将数据作为参数传递的建议。我在问如何区分作为构造函数传递数据作为参数(1)与修饰符传递参数(2)之间的区别。您似乎建议使用第三个选项,即将数据作为Web服务方法的参数/参数传递。我想念你的意思吗?
Andrew Willems

0

只是猜测。

如果我听到“注入行为,而不是数据”,我会思考,而不是这样做:

(对不起,该示例使用伪代码):

class NoiseMaker{
  String noise;
  NoiseMaker(String noise){
     this.noise = noise;
  }
  void soNoise(){
    writeToOutput(noise)
  }
}

去做这个:

interface Noise{
  String getAudibleNoise();
}

class PanicYell implements Noise{
   String getAudibleNoise(){
       return generateRandomYell();
   }
   .....
}



class WhiteNoise implements Noise{
   String getAudibleNoise(){
       return generateNoiseAtAllFrequences();
   }
   .....
}

class NoiseMaker{
  Noise noise;
  NoiseMaker(Noise noise){
     this.noise = noise;
  }
  void soNoise(){
    writeToOutput(noise.getAudibleNoise())
  }
}

这样,您可以始终更改噪声的行为,使其随机,并取决于一个内部变量...

我认为这完全是关于“赞成综合胜于继承”的规定。我必须说,这是一个很好的规则。

显然,这并不意味着您不能将名称“注入”到“人”对象,因为该名称纯粹是业务数据。但是在您提供的示例Web服务中,URL是您生成某种以某种方式连接服务所需的内容。这在某种程度上是一种行为:如果你注入的网址,你注入“数据”需要建立一个“行为”,所以在这种情况下,最好使的行为外,并注入它随时可以使用:不是注射的URL注入可用的连接或可用的连接构建器。

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.