使用接口抽象相关功能与紧耦合

Abstracting related functionality using interfaces vs tight coupling

我想了解真正定义紧耦合的是什么。我对这个主题有 read a number of posts,但有一件事仍然不适合我。

我知道 classes 应该使用它们的接口而不是它们的具体实现注入到其他 classes 中。我也明白,如果 class 遵循一个接口,那么任何使用注入接口的 class 都可以调用接口中定义的 public 函数,并期待类似的功能。

interface iFormatter()
{
    public function format(array $order): array
}

public class OrderFormatter implements iFormatter
{
    public function format(array $order): array
    {
        // ...

        return $formattedArray;
    }
}

public class OrderGenerator implements iGenerator
{
     private $formatter;

     public function __construct(iFormatter $formatter) 
     {
         $this->formatter = $formatter;
     }

     public function generate()
     {
          // ...

          return $this->formatter->format($order);
     }
}

所以我认为在 只有 格式化程序发生变化的情况下,这将被定义为松散耦合;

$example = new OrderGenerator(new CarOrderFormatter);
$example->generate();

$example = new OrderGenerator(new VanOrderFormatter);
$example->generate();

我不太明白的是,当您将责任彼此抽象开时,但那些 class 仍然彼此紧密相关。像

$example = new CarOrderGenerator(new CarOrderFormatter);
$example->generate();

$example = new VanOrderGenerator(new VanOrderFormatter);
$example->generate();

是的,您可以将不同的格式化程序传递给这些生成器,但肯定更常见的是,如果 format 函数期望来自 generate 函数的某些数据,则不会出现某些错误XXXOrderGenerator 具体 class?

所以我 相信 我已经在上面的最后一个例子中将责任抽象为他们自己的 classes 并且尽管已经使用了接口我不确定这是否仍然紧耦合还是技术上松耦合?或者如果我完全错过了重点...

当一个 class 依赖于另一个或依赖于另一个的内部或细节时,

类 被称为紧密耦合。

在您提供的第一个示例中,OrderGenerator 和 OrderFormatter 并没有紧密耦合,因为两者都不依赖于另一个:它们都依赖于 iFormatter。另一种说法是 OrderFormatter 对 OrderGenerator 一无所知,而 OrderGenerator 对 OrderFormatter 一无所知,它只知道传递给它的对象实现了函数 'format'。当您将 CarOrderFormatter 或 VanOrderFormatter 传递给 OrderGenerator 而没有产生不利结果时,您已经突出显示了这一点。

在将 CarOrderFormatter 传递给 CarOrderGenerator 的第二个示例中,您提到如果将 VanOrderFormatter 传递给 CarOrderGenerator,肯定会发生错误。如果 CarOrderGenerator 中的代码依赖于 CarOrderFormatter 的实现细节,那么 classes 是紧密耦合的,即使 iFormatter 接口是 CarOrderGenerator 看到的。这段代码会令人困惑,因为 CarOrderGenerator 用它的 'contract' 表示它不关心传递的是什么格式化程序,但显然它关心。因此,如果 CarOrderGenerator 明确表示您只能将 CarOrderFormatter 传递给它的构造函数,就代码的清晰度而言会更好。然而,抽象(即接口的使用)并不是一件坏事,需要考虑应该如何准确定义接口。举个例子,你有 iCarOrderFormatter 和 iVanOrderFormatter 而不是只有 iFormatter 接口,它们的定义相同,但 CarOrderGenerator 需要 iCarOrderFormatter 而 VanOrderGenerator 需要 iVanOrderFormatter。您的示例可能会变成:

$example = new CarOrderGenerator(new CarOrderFormatter
$example->generate();
$example = new CarOrderGenerator(new SportsCarOrderFormatter)
$example->generate();

$example = new VanOrderGenerator(new PassengerVanOrderFormatter
$example->generate();
$example = new VanOrderGenerator(new CargoVanOrderFormatter)
$example->generate();

要认识到的关键是,引入接口是为了在传递给生成器的内容方面创造灵活性,而不会引起问题。

你是对的,如果你 运行 进入 iGenerator 和 iFormatter 的 car/van 具体,你可能会很难混合依赖关系。但问题是你不应该 运行 进入那种情况,如果你这样做了,你应该回到绘图板,因为你转错了方向。

我不知道您的示例是人为设计的还是紧密(或不)基于您的问题域。在任何一种情况下,问题都是糟糕的设计。您的界面应该代表单一的职责或目的。为此iOrderGenerator和iFormatter的目的是什么?您的示例 iOrderGenerator 所做的只是包装 iFormatter。我可以从设计中重构 iOrderGenerator,因为它毫无用处。问题。已解决。

好吧,我们不要走捷径,说 iOrderGenerator 确实有目的。那么这个目的应该不同于 iFormatter。如果您的接口实现跨越了其单一用途的边界或复制了另一个接口的用途,则您的设计可能很糟糕。例如,假设 iFormatter 的目的是根据订单类型(货车或汽车)以不同方式打印订单。然后 iOrderGenerator 负责创建订单(任何类型)。但我可能有不止一种创建订单的方法。 CsvOrderGenerator 或 XmlOrderGenerator 或 BulkOrderGenerator(但绝不是 CarOrderGen 或 VanOrderGen )。

不要挑剔你的例子,我们可以指出设计问题,这些问题可以解释你在尝试演示一个好的代码概念时如何以令人不安的情况结束。

你是 iFormatter 是一个糟糕的接口,因为它 return 是一个模糊的值(数组)。任何使用 return 值的东西都需要深入了解具体的工作原理,才能知道数组中包含或不包含哪些项目。即我如何在不阅读代码或查看结果的情况下知道有一个 orderId 键值 (debug/print)。相反,iFormatter 应该 return 一个固定的、描述良好的类型,即 CarFormat 或 VanFormat,它们中的每一个都可以实现一个 iFormat,为订单号和价格提供通用访问器。

iOrderGenerator 应该可以生成一个 iOrder。其中可能有一个方法 getFormatter : iFormatter.

这些更改可能会清理设计并为每个混凝土指明更清晰的目的,而不会引导您进入最初的假设。