【问题标题】:Loose coupling vs Encapsulation. Best approach for a balanced design松散耦合与封装。平衡设计的最佳方法
【发布时间】:2014-12-04 18:43:33
【问题描述】:

根据接下来的例子:

class InvoiceGenerator
{
   function create(Invoice $invoice)
   {
      $invoice->create();
   }
}



class InvoiceGenerator
{
   function create($invoiceData)
   {
      $invoice = new Invoice();
      $invoice->create($invoiceData);
   }
}

第一个示例在 InvoiceGenerator 和 Invoice 类之间具有较少的耦合,因为 InvoiceGenerator 不需要 Invoice 类。另外,它不仅可以处理一个类,还可以处理整个接口,只需要很少的修改。我读过很多次这个原则:代码到接口,而不是实现。这种情况的缺点是我不得不在客户端代码中实例化 Invoice 类。

第二个有更多的封装。发票的所有实例化和创建过程都委托给 InvoiceGenerator 类。尽管这两个类是耦合的,但这是有道理的,因为“发票生成器”在没有发票的情况下不会做任何事情。

您认为哪个最合适?或者两者之间平衡设计的关键点是什么?

【问题讨论】:

标签: php encapsulation loose-coupling coupling


【解决方案1】:

首先,我认为你混淆了一些东西。看起来您的 InvoiceGenerator 类是 factory class。它的职责是创建并返回一个 Invoice 对象。 Invoice 对象不是 InvoiceGenerator 的 依赖项(在这种情况下,将 Invoice 对象作为参数传递确实会更好,称为 Dependency Injection)。

但是,正如我所说,InvoiceGenerator 类似乎是一个工厂类,工厂类的整个想法是实例化对象的逻辑封装在该类中(如您的第二个示例中所示)。如果您将 Invoice 对象作为参数传递(如在第一个示例中),则必须在其他地方实例化它,如果您的代码中有多个位置发生这种情况,您最终会复制实例化逻辑。工厂模式的整个想法就是防止这种情况发生。

通过使用工厂类,您可以封装对象实例化逻辑并避免重复。您还可以将一些更高级的逻辑放入 InvoiceGenerator 的create() 方法中,以决定您要返回的对象类型。例如,如果总价为> 0,您可能希望返回一个Invoice 对象,但如果价格为< 0,则返回一个CreditNote 对象。当然它们都应该扩展同一个接口(例如InvoiceInterface)。

此外,看起来您正在将 Invoice 对象的实际初始化委托给对象本身,因为 InvoiceGenerator 的 create() 方法只是调用 Invoice 对象的 create() 方法,其中实际逻辑似乎发生了。这违反了single responsibility principle,因为 Invoice 类现在既负责保存有关发票的数据设置数组中的所有数据。后者应该由工厂类负责。

所以,我会这样创建类:

class InvoiceFactory
{
    public function create(array $data)
    {
        if ($data['total'] >= 0) {
            $invoice = new Invoice();
        } else {
            $invoice = new CreditNote();
        }

        $invoice->setTotal($data['total']);
        // set other data using setters as well

        return $invoice;
    }
}

如您所见,$invoice 对象上没有调用 create() 方法。

【讨论】:

  • 这对我来说更有意义,因为生成器的目的是创建发票。我确实知道工厂模式,但没有关注它的问题。如果我没记错的话,我得到的是:如果一个类的目标是生成其他类,那么使用封装的工厂模式非常适合。否则,如果一个类依赖于另一个类,或者更好的是,如果一个类依赖于几个类遇到的公共接口,那么必须将其作为参数传递。永远不要在其中实例化。这是正确的吗?
  • 另一个问题:一般来说,我们总是需要一个工厂类吗?因为类最终必须在某个地方实例化。
  • 回复您的第一条评论:是的,这基本上是正确的。如果一个类的职责是生成对象,那么它一个工厂类。但是,是的,如果依赖对象使用依赖项来执行某些任务,您应该始终注入它,而不是在依赖类中硬编码类名。这使您的代码保持松散耦合且更加灵活。因此,您应该始终依赖于抽象(接口),而不是具体的类——否则它会破坏依赖注入的目的。
  • 回复您的第二条评论:没有。因为那样你还需要一个工厂来创建工厂等。我只在实例化某个类的逻辑很复杂并且想封装它时才使用工厂。在其他情况下,我将简单地在应用程序的入口点 (index.php) 中实例化类,然后直接使用它们或将它们作为依赖项注入到其他对象中。对于 Symfony 应用程序/捆绑包,您可以将它们添加到服务配置中,以便它们在 Symfony 的服务容器中可用。
  • 太棒了!非常感谢!你得到了赏金。
【解决方案2】:

如果你想关注SOLID ,则第一个选项最符合要求。

第一个选项,InvoiceGenerator 类负责创建发票并且不实例化发票对象。 发票对象可以用子类或实现发票接口的类替换(扩展)。

第二个选项,发票对象是硬编码的,不开放扩展。已开放修改 如果未来发票类的实现发生了变化。此外,无法使用发票类的模拟实例测试 (PHPUnit) InvoiceGenerator。

在我看来,在考虑谁将使用和维护您的代码时,选择适合您的应用程序需要的内容。

降低 InvoiceGenerator 类耦合的选项,您不必在客户端代码中实例化发票对象,但您可以让客户端代码设置其自己的发票对象实例。

class InvoiceGenerator
{
   function create(InvoiceInterface $invoice = null)
   {
      if (null === $invoice) {
         $invoice = new Invoice();
      }
      $invoice->create();
   }
}

【讨论】:

  • 感谢您的意见。我明白你说的。但是,在阅读了 Nic 的答案后,封装实例化的工厂模式更适合此示例。无论如何,当您封装客户端类所依赖的类的实例化时,我明白这是一个糟糕的设计。
【解决方案3】:

考虑到 php 作为语言,这实际上是一个困难的设计问题。

我更喜欢少耦合而不是封装(DRY 不要重复你自己)。

就我自己而言,我已经构建了发票软件,这通常是我的结构化方式。

输入: 工作时间(每小时) 售出物品(物品) 进入输出发票的 InvoiceGenerator。

class Hourly {
}

class Items {
}

Class InvoiceGenerator {
    //Returns Invoice
    //A list of hourly objects and items
    function createInvoice($state, $hourly = array(), $items = array() {
        $invoice =  new Invoice($hourly, $items);
        $invoice->applyTaxes($state):

        //Do Other Things

        return $invoice;
    }
}

我所有项目的关键点是#1 可读性和#2 不重复自己 通过封装您的数据,您会使测试变得更加困难并且灵活性降低。

数组数据可以像对象一样在 PHP 中被抛出,但是你错过了对数据使用其他函数的能力。例如,如果 1 个对象是免税的。现在,您必须在庞大的数组结构中添加进行检查的能力。

或者使用耦合较少的方法,您只有一个返回税值(0 或其他)的方法。

我希望这个例子能说明为什么耦合度较低的方法往往更好。

【讨论】:

  • 您说您希望减少耦合而不是封装,但您的示例将发票实例化封装在生成器中。
【解决方案4】:

您应该考虑使用Factory Method Design Pattern 来解决问题,例如:

class InvoiceFactory
{
   static function create($invoiceData)
   {
      $invoice = new Invoice();
      $invoice->create($invoiceData);
      return $invoice;
   }
}

【讨论】:

    猜你喜欢
    • 2018-07-29
    • 2010-11-28
    • 1970-01-01
    • 2013-01-16
    • 1970-01-01
    • 2017-06-05
    • 2011-06-24
    • 2020-10-26
    • 2011-02-22
    相关资源
    最近更新 更多