【问题标题】:Composition vs Aggregation in PHPPHP 中的组合与聚合
【发布时间】:2022-01-20 03:41:43
【问题描述】:

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

所以在 PHP 中,我们将下面的代码称为 composition 并且很多文章/教程都指出要使用 composition 而不是继承。

class A
{
  
}

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

看了几篇关于组合&聚合的文章,好像上面其实是聚合&非组合的例子,因为在组合中A类的对象没有B类就不能存在,所以当B类的对象是销毁A类的对象也应该被销毁。上面的代码显然不是这样,因为 A 类的对象可以存在于 B 类之外,所以它的生命周期不依赖于 B 类。

下面是合成的例子:

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

所以,从我的理解来看,聚合意味着 A 类的对象可以存在于 B 类之外,而组合意味着 A 类的对象的生命周期取决于 B 类,并且不能存在于 B 类之外。

我理解正确吗?

【问题讨论】:

    标签: php oop


    【解决方案1】:

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

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

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

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

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

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

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

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

    组合反映了更严格的父/子关系,暗示 ObjectA 由 1-N 个 Object B 组成。

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

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

    Aggregation 和 Composition 之间的主要区别在于,Composition if 销毁了 Cart,那么所有关联的 Lineitem 对象都应该被销毁。

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

    实际上,由于 PHP 是页面范围的,因此变量和对象组合的寿命很短,因此在使用这些 UML 模式的 ORM 之外,您真的找不到很多情况。数据需要持久化,并且通常通过某种 RDBMS 或 Document 数据库进行持久化,其中 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,因为构造函数始终是公开的。

    $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)的基础,以及您在 Gang of 4 bookDomain Driven Design 中找到的其他常见 OOP 设计模式的实现。或其他 bookswebsites 这些天。

    【讨论】:

    • 感谢您的详细解释。这一切都是有道理的,但是根据您的回答,如果我们遵循正式定义,那么我的第一个示例是聚合示例而不是组合示例。我知道这取决于上下文并且示例非常薄且不实用,但实际上,只要您通过构造函数接受依赖项,几乎 99% 的教程和文章在 PHP 中都将其称为组合,这就是我感到困惑的原因组合意味着该类负责创建其依赖项的对象(组成)
    • 是的,您的第一个示例将指示组合,但同样,您提供的语法不是有效的 PHP。
    • 什么意思?就语法而言,它是一个有效的 PHP。至少在 PHP 8.0+ 中有效。另外,我认为我的第一个示例将指示根据正式定义的聚合,而不是组合。除非我又误会了。
    • 我的错,我掩盖了参数声明的 php 8 简写。第一个示例表示 A -> B 的组合,因为没有 A 就无法创建 B,因为 B 的构造函数中需要 A 对象。当你有这样的骨架类时,你可以随心所欲地解释事物。 PHP 无法强制执行组合规则,因为您不能嵌套类。您可以使用匿名类来更严格地约束子对象。
    • 知道了,谢谢
    猜你喜欢
    • 1970-01-01
    • 2018-06-29
    • 2010-10-18
    • 2017-04-18
    • 1970-01-01
    • 1970-01-01
    • 2011-12-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多