解耦依赖于另一个构造函数需要参数的类的类

3
我一直在练习使用SOLID编写干净的代码,同时还在我的代码中使用DI来消除耦合,但仅通过构造函数注入。 在我使用DI的许多情况下,我只使用它来调用方法。 我尚未理解的是,当您有一个依赖类其构造函数在另一个类中接受参数时,如何解耦。 如果var obj = new A(month)在类B中创建依赖关系和紧密耦合,那么我该如何解耦/抽象? 这是接口属性发挥作用的地方吗? 如果是这样,我应该如何在此处使用它?
public class A 
{
    private string _month;
    public A(string month) 
    {
        _month = month;
    }
}

public class B 
{
    public List<A> ListOfMonths;
    public B() 
    {
        ListOfMonths = new List<A>();
    }

    public List<A> SomeMethod() 
    {
        string[] months = new [] 
        {
            "Jan",
            "Feb",
            "Mar"
        };

        foreach(var month in months) 
        {
            var obj = new A(month); // If this is coupling, how do I remove it?
            ListOfMonths.Add(obj)
        }

        return ListOfMonths;
    }
}

1
你需要使用工厂模式。基本上,注入一个可以创建正确类型对象的接口,所以你可以使用 _AAAFactory.Create(month) 代替 new AAA(month) - Neil
你展示的内容看起来不像是任何一种糟糕的耦合,我会质疑你是否应该尝试“修复”它。你不需要引入一个工厂。这就是工厂。 - Scott Hannen
@ScottHannen 你是在争论 B 在某种程度上充当了一个 A 列表的工厂吗? - StackLloyd
@StackLloyd - 不是在争论。但从某种意义上说,是的。并不是我会称每个创建和返回东西的类为“工厂”。那样会掩盖我们使用工厂的原因。但你可以将B的名称更改为AFactory,并将SomeMethod更改为Create。这就是它所做的。 - Scott Hannen
@ScottHannen 嗯,从某种程度上来说是这样,但我认为,由于它返回预定义对象(月份)的“列表”,它并不真正充当工厂的角色。然而,我同意它看起来并不像一种令人担忧的紧密耦合,因为它只是实例化一些对象并将它们返回。 - StackLloyd
3个回答

5
如果要解耦,必须从B中删除对A的任何引用,并将它们替换为类似于A的IA(接口),这是可以替代A的任何类的占位符。
然后,在B的构造函数中,提供一个能够创建IA实例的工厂。更进一步地,您可以放置一个抽象工厂,这意味着您提供了一个可创建IA实例的工厂的接口。
以下是基于您代码的示例:
    public interface IA
    {
    }

    public interface IAFactory
    {
        IA BuildInstance(string month);
    }

    public class AFactory : IAFactory
    {
        public IA BuildInstance(string month)
        {
            return new A(month);
        }
    }

    public class A : IA
    {
        public A(string month)
        {
        }
    }

    public class B
    {
        private readonly IAFactory factory;
        public List<IA> ListOfMonths;

        public B(IAFactory factory)
        {
            this.factory = factory;
            ListOfMonths = new List<IA>();
        }

        public List<IA> SomeMethod()
        {
            string[] months = new[] {"Jan", "Feb", "Mar"};
            foreach (var month in months)
            {
                var obj = factory.BuildInstance(month);
                ListOfMonths.Add(obj);
            }

            return ListOfMonths;
        }
    }

4
TL;DR - 没有需要修复的明显耦合。依赖抽象很好,但是可能会过度添加不必要的抽象。从将依赖它们的其他类的角度创建抽象,当您知道需要它们时,这样您始终编写自己所需的代码,而不是不需要的代码。
对于您的类来说,创建 A 的实例并不是糟糕的耦合。创建 A 的实例是 B 的明显目的。 B 不依赖于 A,因为它没有以任何方式使用它。它只是创建 A 并返回它,因为这就是他应该做的。
另一种看待这个问题的方式-一个返回 A 的方法如何不以某种方式与 A 相耦合? B 不需要一个工厂。它就是一个工厂。
此外,似乎也没有明显的理由需要一个表示 A 的抽象。如果您需要能够模拟 A,则可以定义一个接口,例如 IA。但是,这里没有显示出任何需要它的迹象。创建一个 "真实 "的 A 实例是如此容易,以至于不需要能够模拟它。 var a = new A("February");。可以模拟什么?
如果您确实需要 A 的抽象(如 IA),那么在 B 中立即需要的唯一更改就是将 SomeMethod 更改为返回 List<IA> 而不是 List<A>。在该方法内,您将创建一个 List<IA>,而不是 List<A>,但是仍将用具体的 A 实例填充它。
这将消除耦合(正如我强调的那样,在您的代码中没有显示出来),那些当前依赖于 A 并依赖于 B 提供它的其他类 将不再依赖于 A,而会依赖于 B 来提供 IA
抽象很好,但它可能成为一个兔子洞,我们开始添加我们实际上不需要的接口和工厂。(当您用 IA 替换 A 时会发生什么?现在,您与 List<T> 相耦合-该怎么办?)如果您不能测试一个类而不测试其依赖关系,那么这是一个好的迹象,表明该依赖关系必须是可以模拟的抽象。

如果您可以测试所有东西,那么引入更多的抽象只会创造出您不需要的工作。这并不是说我从不使用抽象。在许多情况下,很明显我需要一个抽象化。通常情况是我将要对依赖项执行某些操作的情况。而在本例中,您只是返回它。


以下是另一种看待问题的方式:如果您需要一个抽象化,那么它很可能是代表B的抽象化。如果其他未在此处显示的类需要AIA的实例,并且创建它们的逻辑略微复杂,则那些类可以依赖于像以下代码一样的工厂:

interface ISomethingFactory
{
    List<IA> Create();
}

B将是该工厂的实现。

我们应该从依赖它们的类的角度定义抽象。它们如何与该依赖进行交互?我是否需要模拟该依赖,如果需要,如何模拟?当我们将类视为依赖(或需要)其它组件时,我们编写基于实际需求的代码。这可以防止我们进入无用之路,写出不必要的代码(我曾经做过无数次这样的事情)。


一个非常值得思考的观点,特别是关于使用B的问题。感谢您分享您的想法。 - StackLloyd
非常有帮助的解释。我一直以为在另一个类中使用new关键字创建对象会自动创建耦合。 - cmoe
1
使用new创建依赖会产生耦合。我们使用依赖注入来注入依赖项,而不是让类创建它们。但这些不是依赖项。它们只是方法返回的东西,就像List<T>一样。你也可以用new创建它,这也没问题。你不需要引入一个“列表工厂”来避免创建新列表。 - Scott Hannen
1
另一种描述它的方式(这让我感到好奇,因为我理解解释可能不清楚)。如果你想让B能够返回除了A之外的其他东西,那么抽象化可能是有意义的。但是,如果你想要返回除了A之外的其他东西,你可以创建一个不同的类,而不是修改B。这就是为什么问题转向依赖于B的类,而不是B本身。他们应该依赖于可以被替换或模拟的抽象化,而不是依赖于BB只是其中的一种实现。 - Scott Hannen

1

BA 解耦的一种方法是为 A 定义契约。

interface IA { }

并使用A实现它

public class A : IA {
 private string _month;
 public A(string month) {
  _month = month;
 }
}

然后从B中用IA替换所有对A的引用

为了解决实例化依赖关系,可以使用工厂

public class AFactory {
 public IA CreateIA(string month) {
  return new A(month);
 }
}
< p >在需要时(在 SomeMethod 中),可以使用B来实例化 AFavtory

通过为工厂类定义契约,可以将解耦提高到另一个级别。

interface IAFactory {
 IA CreateIA(string month);
}

使用AFactory实现这个合同。

之后,B的实例可以通过构造函数注入IAFactory的实例。

public B(IAFactory aFactory){
 _aFactory = aFactory;
 ...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接