请注意,这些是用于关联类和对象的模式。
我要提出的第一个观点是,像“使用组合代替继承”这样的概括性规则反映了一些缺失的上下文。如果适当的话,请使用继承。
这些其他模式是用于将类相互关联。
聚合通常用于层次结构/父子关系。
经典的例子是员工和部门之间的关系。
存在Employee对象
Employee是Department的“拥有者/成员”
你可以说“Department Foo”聚合了0-N个员工。
因此,为了建模这种情况,Department通常会有一个内部employees[]数组。
相反,员工可以在内部存储department对象。这本质上与两个类之间的关系匹配为Many >-< Many。
组合反映了更严格的父/子关系,暗示着ObjectA由1-N个Objects B组成。
组合关系的经典例子是购物车,其中包含0-N个产品行项目。
假设Cart和Lineitem的类,Cart将存储Lineitems的数组。
主要区别在于聚合和组合的关系,如果使用组合,当一个购物车被销毁时,所有相关的行项目对象都应该被销毁。
而在聚合示例中,如果一个部门被取消,不一定意味着所有聚合的员工都会被取消,而是删除部门仅会打破员工与部门之间的关系,并可能需要将存储员工部门对象的变量为空,直到员工被分配到另一个部门或者他们的雇佣结束。
实际上,由于PHP是页面作用域,变量和对象组成的生命周期很短,因此除了ORM之外,你不会真正发现许多情况下使用这些UML模式。数据需要持久化,通常通过某种类型的RDBMS或文档数据库来进行持久化,其中ORM将模拟表之间的关系,或者可能是层次结构。
对于您的示例:
这些类似foo/bar A/B的示例没有语义意义或价值。示例#1中唯一的机制是,必须先有ObjA才能构造ObjB。您提供的语法是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;
}
}
$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);
$objB3->setA($objA1);
A和B彼此独立,但仍然拥有所有权。
在PHP开发中,我发现更实用的是依赖注入作为主要PHP框架(Symfony,Laravel)的基础,以及其他常见的面向对象设计模式的实现,这些模式可以在
Gang of 4 book,
Domain Driven Design或其他
books和
websites中找到。