Structural — Yapısal Desenler
Flyweight
Sinek Siklet (Hafif Nesne) Deseni
Amaç: Çok sayıda benzer nesnede tekrar eden ortak veriyi paylaşarak bellek kullanımını azaltmak.
Basit Anlatım
Teknik terim yok — günlük hayattan bir benzetme
Bir parkta binlerce ağaç olduğunu düşünün ve bunların hepsini bir resimde çizeceksiniz. Ağaçların çoğu birkaç türden biri: çam, meşe, çınar.
Her ağaç için ayrı ayrı yaprak şekli, dal yapısı, renk paleti tasarlamak saçma olurdu. Bunun yerine her tür için bir kalıp hazırlarsınız. Sonra her ağaç için yalnızca "hangi kalıp, nerede, ne büyüklükte" bilgisini not edersiniz.
Matbaa harfleri
Eski matbaalarda bir kitabı basmak için her harften kalıp kullanılırdı. Kitapta binlerce "a" harfi geçse de "a" kalıbının şekli hep aynıdır. Değişen tek şey harfin sayfadaki konumudur.
Teknik Anlamı
Bir yazılımcının bakış açısıyla
Flyweight, bir nesnenin durumunu ikiye ayırır ve paylaşılabilir kısmı tek bir örnekte toplar:
- Intrinsic (içsel) durum: Bağlamdan bağımsız, değişmeyen, paylaşılabilir veri. Flyweight nesnesinin içinde saklanır. (Ağaç türü, doku, renk)
- Extrinsic (dışsal) durum: Bağlama özgü, her nesnede farklı olan veri. Dışarıda tutulur ve metotlara parametre olarak geçilir. (X, Y koordinatı)
Katılımcılar
- Flyweight: Intrinsic durumu tutan, immutable nesne (
TreeType). - FlyweightFactory: Flyweight'leri önbellekte tutar; varsa mevcut olanı, yoksa yenisini döndürür (
TreeTypeFactory). - Context: Extrinsic durumu ve flyweight referansını tutar (
Tree).
.NET'ten tanıdık örnekler
- String interning: Aynı string literal'leri bellekte tek kopya olarak tutulur (
string.Intern). ArrayPool<T>ve nesne havuzları benzer bir paylaşım fikrine dayanır.
Ne zaman kullanılır?
- Uygulama çok büyük sayıda nesne oluşturuyorsa (oyunlardaki parçacıklar, harita işaretçileri, metin editöründeki karakterler).
- Bu nesnelerin önemli bir kısmı ortak veriye sahipse.
Dezavantajlar
- Kod karmaşıklaşır; durum iki parçaya bölündüğü için anlaşılması zorlaşır.
- Nesne sayısı azsa kazanç ihmal edilebilir; erken optimizasyon olabilir.
C# ile Uygulama
Adım adım, çalışan kod örneği
Senaryo: Bir oyun haritasına 100.000 ağaç yerleştiriyoruz. Yalnızca 3 ağaç türü var ve her türün dokusu (texture) büyük bir veri.
Adım 1 Flyweight: paylaşılan ağaç türü (intrinsic durum)
Doku verisi büyük olduğu için her ağaçta tekrar saklanmamalı.
public sealed class TreeType
{
public string Name { get; }
public string Color { get; }
public byte[] Texture { get; } // büyük veri: ~50 KB
public TreeType(string name, string color)
{
Name = name;
Color = color;
Texture = new byte[50_000];
}
public void Draw(int x, int y) =>
Console.WriteLine($" {Name} ({Color}) çizildi -> konum ({x}, {y})");
}Adım 2 Flyweight Factory: önbellek
public static class TreeTypeFactory
{
private static readonly Dictionary<string, TreeType> _cache = new();
public static TreeType Get(string name, string color)
{
string key = $"{name}_{color}";
if (!_cache.TryGetValue(key, out var type))
{
type = new TreeType(name, color);
_cache[key] = type;
Console.WriteLine($" >> Yeni TreeType oluşturuldu: {name}");
}
return type;
}
public static int CachedCount => _cache.Count;
}Adım 3 Context: her ağaca özel veri (extrinsic durum)
Her ağaç sadece konumunu ve paylaşılan türe bir referansı tutar.
public class Tree
{
public int X { get; }
public int Y { get; }
public TreeType Type { get; }
public Tree(int x, int y, TreeType type) { X = x; Y = y; Type = type; }
public void Draw() => Type.Draw(X, Y);
}Adım 4 Kullanım
Tüm parçaları bir araya getiren istemci (client) kodu:
string[] names = { "Çam", "Meşe", "Çınar" };
string[] colors = { "koyu yeşil", "yeşil", "açık yeşil" };
var forest = new List<Tree>();
var rnd = new Random(42);
for (int i = 0; i < 100_000; i++)
{
int t = rnd.Next(3);
TreeType type = TreeTypeFactory.Get(names[t], colors[t]);
forest.Add(new Tree(rnd.Next(1000), rnd.Next(1000), type));
}
Console.WriteLine("İlk 3 ağaç:");
forest.Take(3).ToList().ForEach(tree => tree.Draw());
long withFlyweight = TreeTypeFactory.CachedCount * 50_000L;
long withoutFlyweight = forest.Count * 50_000L;
Console.WriteLine($"Ağaç sayısı : {forest.Count:N0}");
Console.WriteLine($"TreeType nesne sayısı: {TreeTypeFactory.CachedCount}");
Console.WriteLine($"Doku belleği (Flyweight ile) : {withFlyweight / 1024:N0} KB");
Console.WriteLine($"Doku belleği (Flyweight yoksa): {withoutFlyweight / 1024 / 1024:N0} MB");Konsol çıktısı
>> Yeni TreeType oluşturuldu: Çınar >> Yeni TreeType oluşturuldu: Meşe >> Yeni TreeType oluşturuldu: Çam İlk 3 ağaç: Çınar (açık yeşil) çizildi -> konum (140, 125) Meşe (yeşil) çizildi -> konum (168, 262) Çınar (açık yeşil) çizildi -> konum (512, 173) Ağaç sayısı : 100.000 TreeType nesne sayısı: 3 Doku belleği (Flyweight ile) : 146 KB Doku belleği (Flyweight yoksa): 4.768 MB
Kodda dikkat edilmesi gerekenler
- 100.000 ağaç için yalnızca 3
TreeTypenesnesi oluştu. - Flyweight olmasaydı her ağaç kendi 50 KB'lık dokusunu taşıyacak ve bellek kullanımı gigabayt seviyesine çıkacaktı.
TreeTypeimmutable (sadeceget) tasarlandı. Paylaşılan nesnenin değiştirilebilir olması tüm ağaçları etkileyeceği için tehlikeli olurdu.