The Composite Pattern is a structural design pattern that
composes objects into tree structures to represent part-whole hierarchies.
It allows clients to treat individual objects (leaves) and groups of objects (composites) uniformly.
Implement a Composite class that stores child components
Allow clients to interact with all components through the same interface
classDiagram
class GUIComponent {
<<interface>>
+render(indent: int)
+getWidth(): int
+getHeight(): int
}
class Button {
-label: string
+render(indent: int)
+getWidth(): int
+getHeight(): int
}
class Label {
-text: string
+render(indent: int)
+getWidth(): int
+getHeight(): int
}
class Panel {
-children: GUIComponent[]
+add(GUIComponent)
+remove(GUIComponent)
+render(indent: int)
+getWidth(): int
+getHeight(): int
}
Button <|.. GUIComponent
Label <|.. GUIComponent
Panel <|.. GUIComponent
Panel o--> GUIComponent : contains
The key idea is that both individual objects (like Button, Label) and containers (like Panel) implement the same interface (like GUIComponent),
allowing the client to treat them uniformly.
<?phpdeclare(strict_types=1);namespaceTests\Structural\Composite\GUI;useDesignPattern\Structural\Composite\GUI\GUIComponent;usePHPUnit\Framework\Attributes\CoversClass;usePHPUnit\Framework\TestCase;useDesignPattern\Structural\Composite\GUI\Panel;useDesignPattern\Structural\Composite\GUI\Label;useDesignPattern\Structural\Composite\GUI\Button;#[CoversClass(GUIComponent::class)]finalclassGUIComponentTestextendsTestCase{publicfunctiontestRender():void{$button1=newButton('OK');$button2=newButton('Cancel');$label=newLabel('Username:');$panel=newPanel();$panel->addChild($label);$panel->addChild($button1);$panel->addChild($button2);$expected="Rendering Panel at (0, 0)\n"."Rendering Label: 'Username:' at (0, 0)\n"."Rendering Button: OK at (0, 0)\n"."Rendering Button: Cancel at (0, 0)\n";self::assertEquals($expected,$panel->render());}publicfunctiontestMove():void{$button=newButton('Submit');$label=newLabel('Email:');$panel=newPanel();$panel->addChild($label);$panel->addChild($button);$expectedMove="Moved Panel to (100, 50)\n";$result=$panel->move(100,50);self::assertEquals($expectedMove,$result);$expectedRender="Rendering Panel at (100, 50)\n"."Rendering Label: 'Email:' at (100, 50)\n"."Rendering Button: Submit at (100, 50)\n";self::assertEquals($expectedRender,$panel->render());}}