松耦合与封装.平衡设计的最佳方法


Loose coupling vs Encapsulation. Best approach for a balanced design

根据下面的例子:

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 类。即使这两个类是耦合的,这也是有道理的,因为"发票生成器"在没有发票的情况下不会做任何事情。

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

首先,我认为你混淆了一些东西。看起来您的发票生成器类是工厂类。它的职责是创建并返回发票对象。发票对象不是发票生成器的依赖项(在这种情况下,最好将发票对象作为参数传递,称为依赖项注入(。

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

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

此外,看起来您正在将发票对象的实际初始化委托给对象本身,因为 InvoiceGenerator 的create()方法只不过是调用发票对象的create()方法,其中实际逻辑似乎发生了。这违反了单一责任原则,因为 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()方法。

如果你想遵循SOLID的,则第一个选项最接近满足要求。

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

第二个选项是发票对象硬编码的,并且不开放扩展。它开放修改如果将来发票类别的实现发生变化。此外,无法使用发票类的模拟实例测试 (PHPUnit( 发票生成器。

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

一个选项,用于降低 InvoiceGenerator 类的耦合度,您不必在客户端代码中实例化发票对象,但您为客户端代码提供了设置其自己的发票对象实例的选项。

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

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

我确实更喜欢较少的耦合而不是封装(DRY 不要重复自己(。

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

输入:工作小时数(每小时(已售商品(商品(进入输出发票的发票生成器。

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 Readabilioty和#2 不要重复自己通过封装数据,您可以使其更难测试,并且灵活性降低。

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

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

我希望这个例子能说明为什么不太耦合的方法往往是更好的方法。

您应该考虑使用工厂方法设计模式来解决问题,如下所示:

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