1. Adapter (Uyarlayıcı) Tasarım Kalıbı
İki uyumsuz arayüzün birlikte çalışmasını sağlar. Var olan bir sınıfın arayüzünü, beklenen başka bir arayüze çevirir. Yeni bir kodu eski sisteme entegre etmek için idealdir.
-> Ne Zaman Kullanılır
- Halihazırdaki bir sınıf, ihtiyacımız olan işlevselliğe sahiptir ama beklediğimiz arayüze uymuyorsa.
- Üçüncü parti kütüphaneleri kendi sistemimize entegre etmek istiyorsak.
- Geriye dönük uyumluluk (backward compatibility) istiyorsak.
-> Kod Örneği
Diyelim ki bir uygulamamız Target arayüzünü kullanan sınıflarla çalışıyor. Ancak elimizde Adaptee adında farklı bir arayüze sahip bir sınıf var. Bu iki sınıf uyumsuz. Adapter, Adaptee sınıfını Target arayüzüne uydurur.
// Beklenen arayüz (client sadece bunu tanır) interface Target { void request(); } // Uyumsuz sınıf (adaptee) class Adaptee { public void specificRequest() { System.out.println("Adaptee: Özgün özel işlev çalıştı."); } } // Adaptörü oluşturan sınıf class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void request() { // Adaptee'nin methodunu çağırır ama Target arayüzü gibi davranır adaptee.specificRequest(); } } // Client (sadece Target arayüzü ile çalışır) public class Client { public static void main(String[] args) { Adaptee adaptee = new Adaptee(); Target adapter = new Adapter(adaptee); // Adaptee'yi adapte ediyoruz adapter.request(); // => "Adaptee: Özgün özel işlev çalıştı." } }
- Target: Sistemimizin beklediği arayüz.
- Adaptee: Farklı arayüze sahip ama işlevsel olarak ihtiyacımızı karşılayan sınıf.
- Adapter: Adaptee'yi alır ve Target arayüzüne çevirir.
- Client: Sadece Target arayüzüyle ilgilenir.
->Avantajları
- Var olan kodu değiştirmeden yeni sistemle uyumlu hale getirmeni sağlar.
- Eski sistemlerin yeniden kullanılmasını kolaylaştırır.
- Farklı arayüzler arasında geçiş köprüsü oluşturur.
->Dezavantajları
- Kodun karmaşıklığını artırabilir.
- Gereğinden fazla adapter kullanımı bakımı zorlaştırabilir.
2. Bridge (Köprü) Tasarım Kalıbı
Soyutlama (abstraction) ile implementasyon (uygulama) arasındaki bağımlılığı ayırarak, her ikisinin de bağımsız olarak geliştirilebilmesini sağlar.
->Ne Zaman Kullanılır
- Soyutlama ve onun implementasyonunu ayrı ayrı değiştirmek istiyorsan.
- Sınıf hiyerarşisinin patlamasını (class explosion) önlemek istiyorsan.
- Birden fazla boyutta değişkenliğe sahipsen (örneğin Cihaz Türü ve Marka gibi iki eksen varsa).
->Gerçek Hayat Analojisi
TV'yi uzaktan kumandayla kontrol ettiğimizi düşün. TV farklı markalarda olabilir (Sony, Samsung...), kumandalar ise farklı türlerde olabilir (BasicRemote, AdvancedRemote...). Kumanda ile TV arasında bir köprü kurarız. TV’yi soyutlarız ve farklı kumandaları onun üzerinde çalıştırabiliriz.
->Kod Örneği
Uygulanacak yapılar:
- Abstraction: Kumanda (Remote)
- Implementor: Cihaz (Device)
- RefinedAbstraction: Gelişmiş kumanda (AdvancedRemote)
- ConcreteImplementor: Somut cihazlar (TV, Radio)
// Device arayüzü - Implementor
interface Device {
boolean isEnabled();
void enable();
void disable();
void setVolume(int percent);
int getVolume();
}
// ConcreteImplementor - TV
class Tv implements Device {
private boolean on = false;
private int volume = 30;
public boolean isEnabled() {
return on;
}
public void enable() {
on = true;
System.out.println("TV açıldı.");
}
public void disable() {
on = false;
System.out.println("TV kapatıldı.");
}
public void setVolume(int percent) {
volume = percent;
System.out.println("TV sesi: " + volume);
}
public int getVolume() {
return volume;
}
}
// Abstraction - Kumanda
class Remote {
protected Device device;
public Remote(Device device) {
this.device = device;
}
public void togglePower() {
if (device.isEnabled()) {
device.disable();
} else {
device.enable();
}
}
public void volumeDown() {
device.setVolume(device.getVolume() - 10);
}
public void volumeUp() {
device.setVolume(device.getVolume() + 10);
}
}
// Refined Abstraction - Gelişmiş kumanda
class AdvancedRemote extends Remote {
public AdvancedRemote(Device device) {
super(device);
}
public void mute() {
device.setVolume(0);
System.out.println("Ses kapatıldı.");
}
}
// Client
public class BridgeDemo {
public static void main(String[] args) {
Device tv = new Tv();
Remote remote = new AdvancedRemote(tv);
remote.togglePower(); // TV açıldı
remote.volumeUp(); // TV sesi: 40
remote.volumeDown(); // TV sesi: 30
((AdvancedRemote) remote).mute(); // Ses kapatıldı
}
}
- Device: Uygulanacak işlevlerin soyut arayüzü.
- Tv: Bu arayüzün bir uygulaması (başka cihazlar da olabilir).
- Remote: Soyut bir kumanda.
- AdvancedRemote: Kumandanın gelişmiş versiyonu.
- Remote ile Device arasında bir "köprü" vardır. Yeni cihazlar eklediğinde kumandayı değiştirmene gerek kalmaz; yeni kumandalar eklediğinde de cihazı değiştirmezsin.
->Avantajları
- Soyutlama ile implementasyonu ayırır.
- Her iki taraf bağımsız olarak geliştirilebilir.
- Kod tekrarını azaltır, genişletilebilirliği artırır.
->Dezavantajları
- Ekstra katmanlar sebebiyle yapı biraz daha karmaşık olabilir.
3. Composite Design Pattern (Bileşik Tasarım Deseni)
Nesneleri ağaç yapısı (tree structure) şeklinde organize edip, bireysel nesnelerle nesne gruplarını aynı şekilde işlemeni sağlar.
->Ne zaman Kullanılır
- Nesnelerin hiyerarşik yapıda olduğu durumlarda.
- Müşterinin (client) hem tekil nesneler hem de nesne grupları ile aynı şekilde işlem yapmasını istediğinde.
->Gerçek Hayat Analojisi
Bir şirketin yapısını düşün: CEO, yöneticiler ve çalışanlar. Bir yöneticinin altında başka yöneticiler ve çalışanlar olabilir. Ama hepsi birer “çalışan”dır ve hepsi için getSalary() gibi işlemler yapılabilir. Yani alt elemanları olan bir grup elemanı da, sıradan bir çalışan gibi ele alabiliriz.
->Kod Örneği
Uygulanacak Yapılar:
- Component: Ortak arayüz (Graphic)
- Leaf: Yaprak nesne (Dot, Circle)
- Composite: Grup nesnesi (CompoundGraphic)
// Ortak arayüz - Component interface Graphic { void draw(); } // Leaf sınıfı class Dot implements Graphic { protected int x, y; public Dot(int x, int y) { this.x = x; this.y = y; } public void draw() { System.out.println("Nokta çizildi: (" + x + ", " + y + ")"); } } // Leaf sınıfı class Circle extends Dot { private int radius; public Circle(int x, int y, int radius) { super(x, y); this.radius = radius; } public void draw() { System.out.println("Çember çizildi: Merkez (" + x + ", " + y + "), Yarıçap: " + radius); } } // Composite sınıfı class CompoundGraphic implements Graphic { private Listchildren = new ArrayList<>(); public void add(Graphic child) { children.add(child); } public void remove(Graphic child) { children.remove(child); } public void draw() { for (Graphic child : children) { child.draw(); } } } // Client public class CompositeDemo { public static void main(String[] args) { CompoundGraphic graphic = new CompoundGraphic(); graphic.add(new Dot(1, 2)); graphic.add(new Circle(5, 3, 10)); CompoundGraphic group = new CompoundGraphic(); group.add(new Dot(7, 8)); group.add(new Circle(2, 3, 5)); graphic.add(group); graphic.draw(); } }
- Graphic: Ortak arayüz, hem Dot hem de CompoundGraphic bunu uygular.
- Dot, Circle: Basit grafik öğeleri.
- CompoundGraphic: Birden fazla grafik öğesini veya diğer CompoundGraphic nesnelerini tutabilen grup nesnesi.
- graphic.draw() çağrısı, hem tekil hem de bileşik nesneleri çizer.
->Avantajları
- Karmaşık ağaç yapılar basitçe temsil edilir.
- Müşteri kodu, bireysel nesneler ile grupları ayırt etmeden kullanabilir.
- Kolay genişletilebilirlik.
->Dezavantajları
- Tasarımı anlamak ve uygulamak karmaşık olabilir.
- Ağaç yapısının yönetimi dikkat gerektirir.
4.Decorator Design Pattern (Süsleyici Tasarım Deseni)
Bir nesnenin davranışını, mevcut sınıfı değiştirmeden dinamik olarak (çalışma zamanında) genişletmek için kullanılır.
->Ne Zaman Kullanılır
- Sınıfın işlevselliğini alt sınıf oluşturmadan değiştirmek istiyorsan.
- Nesneye yeni işlevler eklemek istiyorsan ama kalıtım zinciri karmaşıklaşmasın istiyorsan.
->Gerçek Hayat Analojisi
Kahve sipariş sistemini düşün. Bir temel kahven var (Coffee), ve üstüne süt, şeker, çikolata gibi süslemeler (MilkDecorator, SugarDecorator) ekleyebilirsin. Her dekorasyon, yeni özellikler ekler ama kahvenin kendisi değişmez.
->Kod Örneği
- Component: Ortak arayüz (DataSource)
- ConcreteComponent: Temel nesne (FileDataSource)
- Decorator: Soyut süsleyici (DataSourceDecorator)
- ConcreteDecorator: Özelleştirilmiş süsleyici (EncryptionDecorator, CompressionDecorator)
// Ortak arayüz
interface DataSource {
void writeData(String data);
String readData();
}
// Temel bileşen (ConcreteComponent)
class FileDataSource implements DataSource {
private String filename;
public FileDataSource(String filename) {
this.filename = filename;
}
public void writeData(String data) {
System.out.println("Veri dosyaya yazıldı: " + data);
}
public String readData() {
return "Dosyadan okunan veri";
}
}
// Soyut dekoratör
class DataSourceDecorator implements DataSource {
protected DataSource wrappee;
public DataSourceDecorator(DataSource source) {
this.wrappee = source;
}
public void writeData(String data) {
wrappee.writeData(data);
}
public String readData() {
return wrappee.readData();
}
}
// ConcreteDecorator – Şifreleme
class EncryptionDecorator extends DataSourceDecorator {
public EncryptionDecorator(DataSource source) {
super(source);
}
public void writeData(String data) {
String encrypted = "ŞİFRELİ[" + data + "]";
super.writeData(encrypted);
}
public String readData() {
String data = super.readData();
return "ŞİFRE ÇÖZÜLDÜ: " + data;
}
}
// ConcreteDecorator – Sıkıştırma
class CompressionDecorator extends DataSourceDecorator {
public CompressionDecorator(DataSource source) {
super(source);
}
public void writeData(String data) {
String compressed = "SIKIŞTIRILDI[" + data + "]";
super.writeData(compressed);
}
public String readData() {
String data = super.readData();
return "SIKIŞTIRMA AÇILDI: " + data;
}
}
// Client
public class DecoratorDemo {
public static void main(String[] args) {
DataSource plain = new FileDataSource("data.txt");
// Sıralı dekoratörler
DataSource encrypted = new EncryptionDecorator(plain);
DataSource compressedEncrypted = new CompressionDecorator(encrypted);
compressedEncrypted.writeData("Merhaba dünya!");
System.out.println(compressedEncrypted.readData());
}
}
- FileDataSource: Temel veri kaynağı.
- EncryptionDecorator ve CompressionDecorator: Ek özellikler (şifreleme, sıkıştırma) kazandırır.
- writeData() sırasıyla şifreleyip sıkıştırarak yazar.
- readData() sırasıyla sıkıştırmayı açıp şifreyi çözer.
->Avantajları
- Yeni işlevler alt sınıf oluşturmadan eklenebilir.
- Dinamik olarak davranış değiştirilebilir.
- Birden fazla dekoratör üst üste uygulanabilir.
->Dezavantajları
- Çok sayıda küçük sınıf oluşturulabilir.
- Hangi sırayla dekoratörlerin uygulanacağına dikkat etmek gerekir.
5. Facade Design Pattern (Cephe Tasarım Deseni)
Karmaşık bir sistemi basitleştirmek için kullanılan bu desen, birden fazla sınıf veya alt sistemin karmaşık işlevlerini tek bir arayüzde birleştirerek dış dünyaya sade bir görünüm sunar.
->Ne Zaman Kullanılır
- Alt sistem çok karmaşıksa ve kullanıcılar yalnızca belirli işlemleri basitçe yapmak istiyorsa.
- Kodun okunabilirliğini ve kullanılabilirliğini artırmak istiyorsan.
- Sisteme daha az bağımlılık ile erişmek istiyorsan.
->Gerçek Hayat Analojisi
Bilgisayarı açmak istiyorsun. Sen sadece "Güç" butonuna basarsın. O buton aslında birçok bileşeni çalıştırır: CPU, RAM, SSD vs. Seninle bu bileşenler arasında bir "cephe" (facade) vardır.
->Kod Örneği
// Alt sistem bileşenleri class CPU { public void freeze() { System.out.println("CPU: Dondu."); } public void jump(long position) { System.out.println("CPU: Konuma atladı -> " + position); } public void execute() { System.out.println("CPU: Komut çalıştırılıyor."); } } class Memory { public void load(long position, String data) { System.out.println("RAM: Konuma veri yüklendi -> " + position + ": " + data); } } class HardDrive { public String read(long lba, int size) { return "Sabit diskteki veri"; } } // Facade sınıfı class ComputerFacade { private CPU cpu; private Memory memory; private HardDrive hardDrive; public ComputerFacade() { this.cpu = new CPU(); this.memory = new Memory(); this.hardDrive = new HardDrive(); } public void start() { System.out.println("Bilgisayar başlatılıyor..."); cpu.freeze(); String bootData = hardDrive.read(0, 1024); memory.load(0, bootData); cpu.jump(0); cpu.execute(); } } // Client public class FacadeDemo { public static void main(String[] args) { ComputerFacade computer = new ComputerFacade(); computer.start(); } }
- CPU, Memory, HardDrive: Alt sistemler.
- ComputerFacade: Bu sistemleri dış dünyaya basit bir start() fonksiyonu ile sunar.
- Client: Yalnızca start() çağırarak karmaşık işlemler zincirini başlatır.
->Avantajları
- Karmaşık sistemler basitleştirilir.
- Bağımlılıklar azaltılır.
- Kodun okunabilirliği ve bakımı kolaylaşır.
->Dezavantajları
- Fazla soyutlama bazı gelişmiş kullanıcılar için sınırlayıcı olabilir.
- Facade sınıfı zamanla aşırı büyüyebilir ve God Object’e dönüşebilir.
6. Flyweight Design Pattern (Ağırlıksız Nesne Tasarım Deseni)
Flyweight tasarım deseni, büyük sayıda nesnenin bellekte verimli bir şekilde saklanmasını sağlamak için paylaşılan durumları (state) kullanır. Bu sayede, aynı veriyi birden fazla nesne için yeniden saklamak yerine paylaşımlı hale getirir ve belleği optimize eder.
->Ne Zaman Kullanılır
- Çok sayıda benzer nesne yaratılacaksa ve bellek kullanımı önemliyse.
- Nesnelerin çoğu aynı veriyle çalışıyorsa ve sadece küçük bir kısmı farklıysa.
- Bellek optimizasyonu sağlamak istiyorsan.
->Gerçek Hayat Analojisi
Birden fazla kâğıt uçak yapıyorsun. Her uçak, aynı formda olacak (aynı yapı, renkler vs.). Ancak her uçakta uçuş için farklı bir yön veya hız olabilir. Yani, sadece uçuş yönü ve hızı gibi değişkenler farklı olur, geri kalan tüm şeyler paylaşılabilir.
->Kod Örneği
// Flyweight Interface
interface Flyweight {
void draw(int x, int y); // Paylaşılan davranış
}
// Concrete Flyweight
class TreeType implements Flyweight {
private String name; // Paylaşılan nesne durumu
private String color;
public TreeType(String name, String color) {
this.name = name;
this.color = color;
}
public void draw(int x, int y) {
System.out.println("Ağaç tipi: " + name + " Renk: " + color + " Konum: (" + x + ", " + y + ")");
}
}
// Flyweight Factory
class TreeFactory {
private Map treeTypes = new HashMap<>();
public Flyweight getTreeType(String name, String color) {
String key = name + ":" + color;
if (!treeTypes.containsKey(key)) {
treeTypes.put(key, new TreeType(name, color));
}
return treeTypes.get(key);
}
}
// Client
public class FlyweightDemo {
public static void main(String[] args) {
TreeFactory factory = new TreeFactory();
// Ağaçları yaratırken paylaşılan türler kullanılır.
Flyweight oak = factory.getTreeType("Meşe", "Yeşil");
oak.draw(10, 20); // Ağaç konumu belirlenir.
Flyweight pine = factory.getTreeType("Çam", "Koyu Yeşil");
pine.draw(30, 40); // Çam ağacının farklı bir konumu.
// Tekrar bir meşe ağacı kullanılıyor, ancak aynı tür paylaşılıyor.
oak.draw(50, 60);
}
}
- TreeType: Ağaç türlerini temsil eder ve nesnelerin paylaşılan özelliklerini (isim, renk) içerir.
- TreeFactory: Nesne yaratımı ile ilgilenir. Aynı ağaç türüne sahip nesneler için paylaşımı sağlar.
- Client: Aynı ağaç türünü farklı konumlarda çizerek belleği verimli kullanır.
->Avantajları
- Bellek kullanımı optimize edilir, çünkü paylaşılan nesneler sadece bir kez yaratılır.
- Çok sayıda nesneyle çalışırken performans artışı sağlar.
- Paylaşılan nesneler, bellekte gereksiz yere tekrar tutulmaz.
->Dezavantajları
- Yönetilmesi karmaşık olabilir, çünkü nesneler paylaşılır.
- Nesnelerin paylaşılması, bazı durumlarda esneklik kaybına neden olabilir.
7. Proxy Design Pattern (Proxy Tasarım Deseni)
Proxy tasarım deseni, başka bir nesnenin yerine geçerek onunla olan etkileşimleri kontrol eder. Proxy, genellikle bir nesneye erişimi kontrol etmek, ertelemek veya yönlendirmek için kullanılır.
->Ne Zaman Kullanılır
- Nesneye gerçekten ihtiyaç duyulmadan önce ona erişimi kontrol etmek istiyorsan.
- Büyük nesnelerin yükleme maliyetlerini önlemek veya veritabanı erişimlerini sınırlamak istiyorsan.
- Güvenlik, erişim kontrolü veya özelleştirilmiş işleme ihtiyaç duyuyorsan.
->Gerçek Hayat Analojisi
Bir araba kiralama şirketini düşün. Araba gerçekten sadece kullanıcı kiralama işlemi gerçekleştiğinde teslim edilir. Öncesinde arabanın fiziksel olarak verilmesi gerekmez. Proxy, bir araba temin etme servisi gibi çalışır; ancak arabanın kendisi yalnızca gerektiğinde sağlanır.
->Kod Örneği
// Subject Interface
interface RealSubject {
void request();
}
// RealSubject Class
class RealSubjectImpl implements RealSubject {
public void request() {
System.out.println("Gerçek nesne isteği işliyor...");
}
}
// Proxy Class
class Proxy implements RealSubject {
private RealSubjectImpl realSubject;
public void request() {
if (realSubject == null) {
realSubject = new RealSubjectImpl(); // Gerçek nesne yalnızca ihtiyaç duyulduğunda yaratılır
}
System.out.println("Proxy: Gerçek nesneye yönlendiriliyor...");
realSubject.request();
}
}
// Client
public class ProxyDemo {
public static void main(String[] args) {
RealSubject proxy = new Proxy();
proxy.request(); // Proxy, gerçek nesneye erişimi kontrol eder.
}
}
- RealSubject: Gerçek nesnenin işlevselliğini barındıran sınıftır.
- Proxy: Gerçek nesneye erişimi kontrol eden sınıftır. Gerçek nesne yalnızca gerektiğinde proxy tarafından yüklenir.
- Client: Proxy sınıfını kullanarak gerçek nesneye erişir. Proxy, gerçek nesneye erişim sağlamak için gerekli işlemleri yapar.
->Avantajları
- Gecikmeli yükleme sağlar, yani sadece gerçekten gerekli olduğunda nesne yaratılır.
- Erişim kontrolü ve güvenlik için yararlıdır. Proxy, gerçek nesnenin erişimini kontrol edebilir.
- Performans optimizasyonu sağlar, özellikle büyük nesnelerle çalışırken.
->Dezavantajları
- Ekstra karmaşıklık ekler, çünkü her nesneye erişim proxy üzerinden yapılır.
- Proxy ile gerçek nesne arasındaki etkileşimler bazen karışık hale gelebilir.
->Kullanım Senaryoları
- Sanat galerisi: Gerçek resimler büyük ve pahalıdır. Proxy, galerideki resimleri sadece gerektiğinde yükler.
- Veritabanı bağlantıları: Veritabanına bağlanmak yüksek maliyetli olabilir, bu yüzden proxy, veritabanına erişimi gerektiğinde sağlar.

