【问题标题】:Loose coupling via using only primitive types / delegates通过仅使用原始类型/委托实现松散耦合
【发布时间】:2011-06-24 09:32:14
【问题描述】:

我有一个关于松散耦合和接口的概念/理论问题。

所以使用接口的一种方法可能是封装某个构造函数所需的参数:

class Foo
{
    public Foo(IFooInterface foo)
    {
       // do stuff that depends on the members of IFooInterface
    }
}

所以只要传入的对象实现了契约,一切都会工作。据我了解,这里的主要好处是它支持多态性,但我不确定这是否真的与松散耦合有关。

为了论证,假设 IFooInterface 如下:

interface IFooInterface
{
   string FooString { get; set; }
   int FooInt { get; set; }

   void DoFoo(string _str); 
}

从松耦合的角度来看,不要在上面的构造函数中使用 IFooInterface 会更好,而是像这样设置 Foo:

class Foo
{
    public Foo(string _fooString, int _fooInt, Action<string> _doFoo)
    {
       // do the same operations
    }
}

因为说我想把 Foo 的功能放到另一个项目中。这意味着其他项目也必须引用 IFooInterface,添加另一个依赖项。但是这样我可以把 Foo 放到另一个项目中,它准确地表达了它需要什么才能工作。显然我可以只使用重载的构造函数,但是为了论证的缘故,我不想和/或不能修改 Foo 的构造函数。

最显着的缺点(至少对我而言)是,如果您有一个带有一堆原始参数的方法,它会变得丑陋且难以阅读。所以我有了创建一种包装函数的想法,它允许您仍然传递接口而不是所有原始类型:

    public static Func<T, R> Wrap<T, R>(Func<T, object[]> conversion)
    {
        return new Func<T, R>(t =>
        {
            object[] parameters = conversion(t);

            Type[] args = new Type[parameters.Length];

            for (int i = 0; i < parameters.Length; i++)
            {
                args[i] = parameters[i].GetType();
            }

            ConstructorInfo info = typeof(R).GetConstructor(args);

            return (R)info.Invoke(parameters);
        });
    }

这里的想法是,我可以取回一个函数,该函数采用符合 Foo 要求的某个接口的实例,但 Foo 对那个接口一无所知。可以这样使用:

public Foo MakeFoo(IFooInterface foo)
{
    return Wrap<IFooInterface, Foo>(f => 
       new object[] { f.FooString, f.FooInt, f.DoFoo })(foo);  
}

我听说过关于接口应该如何启用松散耦合的讨论,但对此感到疑惑。

想知道一些有经验的程序员是怎么想的。

【问题讨论】:

  • 我认为你让事情变得不必要地复杂了。
  • 松散耦合的目标是 任何一方都不应该引用另一方。接口是一个(非常)有用的语言特性。没有理由避开它们。
  • 我对松散耦合的印象是,我应该能够接受一个类(或更抽象地说是一组属性/行为)并理解它的目的,而不管某些特定的上下文,或者,就像我说的 drop将它放到另一个项目中,并且看不到一堆消息,例如“找不到类型或命名空间 x”。我的问题不是我们是否应该避开接口,我的问题是它们是否真的旨在促进松散耦合,或者它们是否更意味着多态/继承?
  • 所有代码都存在于特定的上下文中。松散耦合只是意味着一个类不需要知道它所依赖的类的私密细节。接口可以通过明确说明类的哪些公共成员用于公共消费以及哪些是特定于实现的来帮助解决这个问题。

标签: c# interface dependencies primitive-types loose-coupling


【解决方案1】:

在您的初始示例中,您非常接近 Parameter Object 模式,尽管在此处使用简单类(通常具有自动属性)更常见,而无需额外抽象接口。

通常,当您听说将接口传递给构造函数时,它不是替换原语,而是作为依赖注入的一种形式。与其直接依赖MyFooRepository,不如直接依赖IFooRepository,这将消除与特定实现的耦合。

【讨论】:

  • 我想我的意思是,即使你没有编码到一个特定的实现,你正在编码一个句法契约。所以合同仍然存在紧耦合,这当然比实现的紧耦合更好,但仍然引入了耦合。我想在某些情况下可能存在过多的松耦合,但我认为从松耦合到紧耦合更容易,反之亦然。
  • 在某些时候你必须依赖某些东西。关键是尽可能少地依赖。如果接口可以,请不要依赖类。当 2-method 接口可行时,不要依赖 20-method 接口。等等。
  • @Sean,如果车轮没有与您的汽车相连,它就不会去任何地方。
  • 是的,我同意你必须依赖某些东西,但坚持原语当然有资格使用“尽可能少”,你不觉得吗?在我看来,方法参数或构造函数参数的签名本质上是一种契约。尽管使用这种方法是有代价的,但我承认,这可能会损害可读性并可能使事情变得过于复杂。此外,我不确定接口真正最终以我描述的方式使用的频率(保存属性+行为,或者更多只是行为?)。
  • 你说得对——接口最常用于行为,而不是状态。原始参数的集合只是状态,因此为参数对象使用接口可能没有意义。
【解决方案2】:

我的第一个想法是您没有分别为FooStringFooInt 的设置器提供Action&lt;string&gt;Action&lt;int&gt;IFooInterface 的实现可能有关于这些设置器的规则,并且可能需要访问未在接口上公开的其他实现细节。

同样,您也应该接受Func&lt;string&gt;Func&lt;int&gt;IFooInterface 的实现可能会随着时间的推移对FooStringFooInt 的内容有所规定。例如,DoFoo 可能会重新计算这些值;你不能假设它们只是传递到永远不会改变的字段。

更进一步,如果 getter、setter 或 DoFoo 需要访问公共状态,则函数和操作将需要在您创建它们时关闭同一组变量。那时,您将做一些心理操来理解变量的生命周期和代表之间的关系。

这种状态和行为的配对正是类所表达的,而实现细节的隐藏正是接口所提供的。将这些概念分解为它们的组成元素当然是可以实现的,但它也破坏了通过按类型对成员进行分组而获得的连贯性。

换句话说,你可以给我面条、酱汁、蔬菜和汉堡包,但那不是意大利面和肉丸:-)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-06-05
    • 1970-01-01
    • 1970-01-01
    • 2017-04-11
    • 1970-01-01
    • 2011-01-20
    • 2020-10-26
    • 2011-02-22
    相关资源
    最近更新 更多