Composite Tasarım Deseni Nedir?

Sayfayı kopyala
Bu yazı Design Patterns/Tasarım Desenleri Nedir? başlıklı yazı dizisinin bir parçasıdır.
Bu içerik ağırlıklı olarak refactoring.guru sitesindeki içeriğin tercümesi ve derlenmesinden oluşturulmuştur.
Tüm tasarım desenleri ya da diğer adıyla tasarım kalıplarına yönelik ayrıntılı içeriklere yazının sonundaki bağlantılardan ulaşabilirsiniz.
💡 Özet (TL;DR):
- Amaç: Nesneleri ağaç yapıları (part-whole hierarchies) şeklinde gruplamayı sağlar. Hem tekil nesneleri (yapraklar) hem de nesne gruplarını (dallar/composite) aynı ortak arayüz üzerinden tek bir nesneymiş gibi yönetebilmenize olanak tanır.
- Kilit Yapılar: Ortak Bileşen Arayüzü (
Component), Tekil Nesne (Leaf) ve Bileşik Nesne (Composite).- Motto: Treat individual and composite objects uniformly (Tekil nesnelerle grup nesnelerini aynı şekilde ele al).
Composite Tasarım Deseninin Amacı
Composite (Kompozit / Bileşik), nesneleri ağaç yapıları halinde oluşturmanıza ve bu ağacın dalları ile tek tek nesnelermiş gibi çalışmanıza olanak veren yapısal (structural) bir tasarım desenidir.
Sorun
Uygulamanızın veri modeli hiyerarşik bir ağaç şeklinde gösterilebiliyorsa Composite deseni çok işe yarar.
Örneğin, Products (Ürünler) ve Boxes (Kutular) şeklinde iki nesnemiz olduğunu düşünelim. Bir kutu birden fazla ürün içerebileceği gibi, daha küçük kutuları da barındırabilir. Bu küçük kutuların içinden de ürünler veya başka kutular çıkabilir (bu böyle devam eder).
Siparişlerin toplam fiyatını hesaplayan bir sistem tasarlayacağınızı varsayalım. Sipariş sadece basit ürünlerden oluşabileceği gibi, içi farklı ürünler ve kutularla dolu devasa bir kutudan da oluşabilir. Böyle bir hiyerarşide toplam fiyatı nasıl hesaplarsınız?

Klasik yöntemle tüm kutuları döngülerle açıp ürünleri tek tek bulmaya çalışabilirsiniz. Ancak kod seviyesinde bu o kadar kolay değildir. Çünkü kaç katman alta ineceğinizi, hangi kutunun içinde ne tür nesneler olduğunu önceden bilmeniz gerekir. Bu durum kodu karmaşıklaştırır ve bakımı imkansız hale getirir.
Çözüm
Composite deseni, ürünler ve kutularla çalışırken fiyat hesaplaması için ortak bir arayüz (örneğin getPrice() metodu içeren Component sınıfı) kullanılmasını önerir.
Peki bu metot nasıl çalışır?
- Ürün (Leaf): Basitçe kendi fiyatını döndürür.
- Kutu (Composite): Kendi içindeki tüm öğeleri (dosya, ürün veya diğer kutular) tarar, her birine
getPrice()metodunu çağırır ve gelen değerleri toplayıp sonucu üst katmana döndürür.
Eğer alt öğelerden biri de bir kutuysa, o da kendi altındakileri toplayacaktır. Böylece en üstteki kutu, alt katmanların detaylarını hiç bilmeden tek bir metot çağrısıyla toplam fiyatı öğrenmiş olur.

İstemci kodun nesnelerin somut tiplerini (basit ürün mü yoksa kutu mu olduğunu) bilmesine gerek kalmaz. Hepsiyle aynı ortak arayüz üzerinden çalışabilir.
Gerçek Hayat Senaryosu: Dosya ve Klasör Hiyerarşisi (File System)
Dosya sistemlerindeki klasörler ve dosyalar Composite deseninin en net örneğidir. Klasörler içinde dosyalar barındırabilirken, dosyalar alt eleman içeremez:
// 1. Ortak Arayüz (Component)
abstract class FileSystemItem {
protected string $name;
public function __construct(string $name) {
$this->name = $name;
}
abstract public function getSize(): int;
}
// 2. Yaprak Nesne (Leaf)
class File extends FileSystemItem {
private int $size;
public function __construct(string $name, int $size) {
parent::__construct($name);
$this->size = $size;
}
public function getSize(): int {
return $this->size;
}
}
// 3. Bileşik Nesne (Composite)
class Directory extends FileSystemItem {
private array $items = [];
public function add(FileSystemItem $item): void {
$this->items[] = $item;
}
public function getSize(): int {
$totalSize = 0;
foreach ($this->items as $item) {
// Özyinelemeli (recursive) olarak tüm boyutlar toplanıyor
$totalSize += $item->getSize();
}
return $totalSize;
}
}
Composite vs Decorator vs Chain of Responsibility
| Desen | Alt Nesne Yapısı | İstek/İşlem Akışı |
|---|---|---|
| Composite | Çoklu (Ağaç yapısında yaprak ve dallar). | İşlemleri özyinelemeli (recursive) olarak tüm dallara dağıtır. |
| Decorator | Tekil (Sadece sarmalanan tek bir nesne). | Nesneye yeni özellikler ekler, akışı kesmez. |
| Chain of Responsibility | Tekil (Zincir şeklinde ardışık halkalar). | İsteği sırayla sonraki halkaya iletir, akış kesilebilir. |
Uygulanabilirlik
- Hiyerarşik Ağaç Yapıları: Uygulamanızda dosya sistemleri, GUI menüleri veya organizasyon şemaları gibi ağaç benzeri bir nesne hiyerarşisi kurmanız gerektiğinde kullanın.
- Tek Tip Ele Alma (Uniformity): İstemci kodun hem tekil nesneleri (yaprak düğümleri) hem de bileşik nesneleri (dalları) ayırt etmeden, tamamen aynı şekilde değerlendirmesini istediğinizde kullanın.
Diğer Tasarım Desenleri ile İlişkisi
- Kompleks Composite ağaçları oluştururken nesne inşa aşamalarını yönetmek için Builder deseninden faydalanabilirsiniz.
- Chain of Responsibility deseni Composite ile birlikte sıkça kullanılır. Bir yaprak bileşen istek aldığında, isteği tüm üst dallar boyunca ağacın köküne kadar iletebilir.
- Composite ağaç düğümlerini ziyaret etmek ve üzerlerinde işlemler gerçekleştirmek için Visitor deseni kullanılabilir.
- Decorator ve Composite benzer yapılara sahiptir ancak Decorator sadece tek bir nesneyi sarmalayıp ona ek sorumluluk yüklerken, Composite altındaki nesnelerin sonuçlarını toplar.
Composite Tasarım Deseni Kod Örnekleri
Örnek PHP Kodu
<?php
namespace RefactoringGuru\Composite\Conceptual;
abstract class Component
{
protected ?Component $parent = null;
public function setParent(?Component $parent)
{
$this->parent = $parent;
}
public function getParent(): ?Component
{
return $this->parent;
}
public function add(Component $component): void { }
public function remove(Component $component): void { }
public function isComposite(): bool
{
return false;
}
abstract public function operation(): string;
}
class Leaf extends Component
{
public function operation(): string
{
return "Leaf";
}
}
class Composite extends Component
{
protected \SplObjectStorage $children;
public function __construct()
{
$this->children = new \SplObjectStorage();
}
public function add(Component $component): void
{
$this->children->attach($component);
$component->setParent($this);
}
public function remove(Component $component): void
{
$this->children->detach($component);
$component->setParent(null);
}
public function isComposite(): bool
{
return true;
}
public function operation(): string
{
$results = [];
foreach ($this->children as $child) {
$results[] = $child->operation();
}
return "Branch(" . implode("+", $results) . ")";
}
}
function clientCode(Component $component)
{
echo "RESULT: " . $component->operation();
}
$simple = new Leaf();
echo "Client: Basit bir bileşen çalıştırılıyor:\n";
clientCode($simple);
echo "\n\n";
$tree = new Composite();
$branch1 = new Composite();
$branch1->add(new Leaf());
$branch1->add(new Leaf());
$branch2 = new Composite();
$branch2->add(new Leaf());
$tree->add($branch1);
$tree->add($branch2);
echo "Client: Karmaşık bir kompozit ağaç çalıştırılıyor:\n";
clientCode($tree);
echo "\n\n";
function clientCode2(Component $component1, Component $component2)
{
if ($component1->isComposite()) {
$component1->add($component2);
}
echo "RESULT: " . $component1->operation();
}
echo "Client: Sınıf kontrolü yapmadan ağaca yeni eleman ekleniyor:\n";
clientCode2($tree, $simple);
Örnek Python Kodu
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List
class Component(ABC):
@property
def parent(self) -> Component:
return self._parent
@parent.setter
def parent(self, parent: Component):
self._parent = parent
def add(self, component: Component) -> None:
pass
def remove(self, component: Component) -> None:
pass
def is_composite(self) -> bool:
return False
@abstractmethod
def operation(self) -> str:
pass
class Leaf(Component):
def operation(self) -> str:
return "Leaf"
class Composite(Component):
def __init__(self) -> None:
self._children: List[Component] = []
def add(self, component: Component) -> None:
self._children.append(component)
component.parent = self
def remove(self, component: Component) -> None:
self._children.remove(component)
component.parent = None
def is_composite(self) -> bool:
return True
def operation(self) -> str:
results = []
for child in self._children:
results.append(child.operation())
return f"Branch({'+'.join(results)})"
def client_code(component: Component) -> None:
print(f"RESULT: {component.operation()}", end="")
def client_code2(component1: Component, component2: Component) -> None:
if component1.is_composite():
component1.add(component2)
print(f"RESULT: {component1.operation()}", end="")
if __name__ == "__main__":
simple = Leaf()
print("Client: Basit bir bileşen çalıştırılıyor:")
client_code(simple)
print("\n")
tree = Composite()
branch1 = Composite()
branch1.add(Leaf())
branch1.add(Leaf())
branch2 = Composite()
branch2.add(Leaf())
tree.add(branch1)
tree.add(branch2)
print("Client: Karmaşık bir kompozit ağaç çalıştırılıyor:")
client_code(tree)
print("\n")
print("Client: Sınıf kontrolü yapmadan ağaca yeni eleman ekleniyor:")
client_code2(tree, simple)
Sıkça Sorulan Sorular (FAQ)
Bileşen yönetimi (add, remove) metotlarının ortak Component sınıfında tanımlanması neden bir tasarım çelişkisidir (Design Trade-off)?
Bu durum Arayüz Ayrımı Prensibi (Interface Segregation Principle - ISP) ile polimorfizm arasındaki bir trade-off'tur:
- Eğer bu metotları
Componentsınıfına koyarsanız, istemci kod yaprak veya dal ayrımı yapmadan tüm nesnelerdeadd()çağırabilir. Bu durum tek tip kullanımı (uniformity) maksimuma çıkarır. Ancak yaprak sınıflarında bu metotlar anlamsız/boş kalır. - Eğer bu metotları sadece
Compositesınıfına koyarsanız, istemci kodun nesneleri eklemeden önceisComposite()veyainstanceofile kontrol etmesi gerekir. Bu da güvenliği (safety) artırır ancak kodun polimorfik esnekliğini azaltır.
Derin ağaç yapılarında özyinelemeli (recursive) hesaplamaların getireceği performans yükü nasıl optimize edilir?
Çok derin ağaçlarda operation() çağrısı tüm alt dallara gideceği için ciddi bir CPU maliyeti oluşturabilir. Bunu engellemek için önbellekleme (caching) uygulanabilir. Ağaçtaki her düğüm alt elemanlarının hesaplanmış toplam değerini hafızada tutar. Alt elemanlarda bir değişiklik (add / remove / update) olduğunda, ilgili düğüm kendi üst düğümüne (parent) haber vererek önbelleğin temizlenmesini (cache invalidation) tetikler.
HTML DOM yapısı ile Composite deseni arasındaki ilişki nedir?
Modern web tarayıcılarındaki HTML DOM (Document Object Model) yapısı tamamen Composite desenine dayanır. document, div, section gibi elemanlar birer Composite nesnesiyken; img, input veya düz metin (TextNode) düğümleri birer Leaf nesnesidir. Hepsi ortak bir Node veya Element sınıfından türediği için tarayıcı tüm ağacı tek elden parse edip render edebilir.
Diğer Tasarım Kalıpları/Design Patterns
Oluşumsal Kalıplar (Creational Patterns)
Factory Method, Abstract Factory, Builder, Prototype, Singleton
Yapısal Kalıplar (Structural Patterns)
Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
Davranışsal Kalıplar (Behavioral Patterns)
Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor
