PHP 中的组合与聚合

Composition vs Aggregation in PHP

我正在阅读一篇关于 OOP 中的关系、关联、组合、聚合等的文章。有些东西令人困惑,我一直在网上找到相互矛盾的信息,所以我希望有人能对此有所了解。

所以在PHP中,我们调用下面的代码composition&很多articles/tutorials指出要使用composition继承。

class A
{
  
}

class B
{
    public function __construct(protected A $a)
    {
    }
}

看了几篇关于组合和聚合的文章后,似乎以上实际上是聚合而不是组合的例子,因为在组合中 class A 的对象没有 class 就不可能存在B,所以当class B的对象被销毁时,class A的对象也应该被销毁。上面的代码显然不是这样,因为classA的对象可以存在于classB之外,所以它的生命周期不依赖于classB。

下面是组成的例子:

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

所以根据我的理解,聚合意味着 class A 的对象可以存在于 class B 之外,而组合意味着 class A 的对象的生命周期取决于 class B & 不能存在于 class B.

之外

我理解正确吗?

请务必记住,这些是关联 类 和对象的模式。

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

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

聚合 通常用于 hierarchy/parent/child 关系。

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

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

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

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

Composition 反映了更严格的 parent/child 关系,其含义是 ObjectA 由 1-N 个 Object B 组成。

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

假设 类 购物车和 Lineitem,购物车将存储一个 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();

然而,从技术上讲,没有什么能阻止你制作 B,因为构造函数总是 public。

$objB = new B($objA);   

对于聚合,这将是更可能的情况:

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);

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

我发现在 PHP 开发中更实际有价值的是依赖注入作为领先 PHP 框架(Symfony,Laravel)以及其他常见 OOP 实现的基础这些天您在 Gang of 4 book, Domain Driven Design, or other books and websites 中找到的设计模式。