PHP 中的组合与聚合

Fuz*_*uze 3 php oop

我正在阅读一篇关于 OOP、关联、组合、聚合等关系的文章。有些内容令人困惑,而且我不断在网上找到相互矛盾的信息,所以我希望有人能对此有所启发。

因此,在 PHP 中,我们将以下代码称为组合,并且许多文章/教程指出使用组合而不是继承。

class A
{
  
}

class B
{
    public function __construct(protected A $a)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

读了几篇关于组合和聚合的文章后,似乎上面实际上是聚合而不是组合的示例,因为在组合中,类 A 的对象不能离开类 B 而存在,所以当类 B 的对象被销毁时,该对象A类的也应该被销毁。上面代码中的情况显然不是这样,因为类 A 的对象可以存在于类 B 之外,因此它的生命周期不依赖于类 B。

以下是组合的示例:

class B
{
    public A $a;
  
    public function __construct()
    {
        $this->a = new A();
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,根据我的理解,聚合意味着A类的对象可以存在于B类之外,而组合意味着A类的对象的生命周期依赖于B类并且不能存在于B类之外。

我的理解正确吗?

gvi*_*iew 6

重要的是要记住,这些是关联类和对象的模式。

我要说的第一点是,任何像“使用组合而不是继承”这样的一揽子规则都反映了一些缺失的上下文。如果合适的话使用继承。

这些其他模式用于将类关联在一起。

聚合通常用于层次结构/父/子关系。

一个典型的例子是将员工和部门关联起来。

  • 存在 Employee 对象
  • 员工是部门“拥有/成员”
  • 您可以说“Department Foo”聚合了 0-N 名员工。

因此,为了对此进行建模,Department 通常会有一个内部员工[] 数组。

相反,员工可以在内部存储部门对象。这本质上匹配两个类之间的关系,如 Many >-< Many。

组合反映了更严格的父/子关系,这意味着对象 A 由 1-N 个对象 B 组成。

组合关系的典型示例是购物车,其中包含 0-N 个产品系列项目。

假设有 Cart 和 Lineitem 类,Cart 将存储 Lineitem 数组。

聚合和组合之间的主要区别在于,对于组合,如果购物车被销毁,则所有关联的 Lineitem 对象都应该被销毁。

以聚合为例,如果删除一个部门,并不一定意味着所有聚合的员工都会被删除,而是删除一个部门只会破坏员工和部门之间的关系,并且可能需要存储员工的部门对象将为空,直到员工被分配到另一个部门,或者他们的雇佣关系结束。

实际上,由于 PHP 是页面范围的,因此变量和对象组合的生命周期很短,因此您实际上不会在 ORM 之外找到使用这些 UML 模式的许多情况。数据需要持久化,通常这种持久化是通过某种 RDBMS 或文档数据库实现的,其中 ORM 将模拟表之间的关系,或者可能是层次结构。

以你的例子为例:

这些 foo/bar A/B 示例没有语义或价值。示例 #1 中的唯一机制是 ObjB 必须首先是 ObjA,否则无法构造。您提供的语法是 PHP8 中的新语法,我一开始就错过了。

看来这个例子更可能需要是:

class A
{
    private bSet = array();

    public function addB(B $b) {
       $this->bSet[] = new B($this);
    }  
}

class B
{
    private $a;

    public function __construct(A $a)
    {
        $this->a = $a;            
    }
}


// B shouldn't be made without a Parent A
$objA = new A();
$objA->addB();
$objA->addB();
Run Code Online (Sandbox Code Playgroud)

然而,从技术上讲,没有什么可以阻止你获得 B,因为构造函数始终是公共的。

$objB = new B($objA);   
Run Code Online (Sandbox Code Playgroud)

通过聚合,这将是更可能的情况:

class A
{
    private bSet = array();

    public function addB(B $b) {
       $this->bSet[] = $b;
       $b->setA($this);
    }

    public function delB(B $b) {
        for ($i=0; $i < count($this->bSet); $i++) {
            if ($b === $this->bSet[$i]) {
                unset($this->bSet[$i];
                break;
            }
        }
    }  
}

class B
{
    private $a;

    public function setA(A $a) {
        if ($this->a) {
            $this->a->delB($this);
        }
        $this->a = $a;
    }

    public function getA() {
        return $this->a;
    }

}

$objA1 = new A();
$objA2 = new A();

$objB1 = new B();
$objB2 = new B();
$objB3 = new B();

$objA1->addB($objB2);
$objA2->addB($objB1);
$objA2->addB($objB3);
//Change $objB3's parent from A1 to A2.
$objB3->setA($objA1);
Run Code Online (Sandbox Code Playgroud)

A 和 B 彼此独立,但仍拥有所有权。

我发现 PHP 开发中更具有实际价值的是依赖注入作为领先 PHP 框架(Symfony、Laravel)的基础,以及其他常见 OOP 设计模式的实现,您可以在 Gang of 4 书领域驱动设计或其他书籍中找到这些模式。这些天的书籍网站