Takip et

Java’da Prototype Tasarım Deseni: Derinlemesine Bir İnceleme

Java’da Prototype Tasarım Deseni: Derinlemesine Bir İnceleme Giriş: Tasarım Desenlerine Genel Bakış ve Prototype’ın Yeri Yazılım geliştirme

Java’da Prototype Tasarım Deseni: Derinlemesine Bir İnceleme

Giriş: Tasarım Desenlerine Genel Bakış ve Prototype’ın Yeri

Yazılım geliştirme dünyasında, karşılaşılan ortak sorunlara kanıtlanmış ve tekrar kullanılabilir çözümler sunan tasarım desenleri, mimari kalitenin ve kod sürdürülebilirliğinin temel taşlarından biridir. Bu desenler, yazılım mühendislerinin deneyimlerinden damıtılmış en iyi uygulamalar olup, karmaşık sistemlerin anlaşılabilir, esnek ve genişletilebilir olmasını sağlar. “Gang of Four” (GoF) olarak bilinen Erich Gamma, Richard Helm, Ralph Johnson ve John Vlissides tarafından 1994 yılında yayımlanan “Design Patterns: Elements of Reusable Object-Oriented Software” kitabı, bu kavramları sistematik bir şekilde sunarak modern yazılım geliştirmede devrim yaratmıştır.

Tasarım desenleri üç ana kategoriye ayrılır: Yaratımsal (Creational), Yapısal (Structural) ve Davranışsal (Behavioral). Yaratımsal desenler, nesnelerin nasıl oluşturulduğuyla ilgilenir ve bu süreci esnek, bağımsız ve verimli hale getirmeyi amaçlar. Bu kategoride Singleton, Factory Method, Abstract Factory, Builder ve Prototype gibi desenler bulunur. Her biri, nesne oluşturma mekanizmalarını belirli senaryolara göre optimize eder.

Bu makalede odaklanacağımız Prototype tasarım deseni, yaratımsal desenler arasında özel bir yere sahiptir. Yeni nesnelerin sıfırdan oluşturulması yerine, mevcut bir nesnenin kopyalanması (klonlanması) yoluyla üretilmesini sağlar. Bu yaklaşım, özellikle karmaşık veya maliyetli nesnelerin oluşturulması gerektiğinde önemli performans ve esneklik avantajları sunar. Prototype deseni, istemcinin somut sınıfların detaylarından izole edilmesine olanak tanıyarak, sistemin daha gevşek bağlı (loosely coupled) ve genişletilebilir olmasını destekler.

Prototype Tasarım Deseni Nedir?

Prototype tasarım deseni, nesne oluşturma maliyetinin yüksek olduğu durumlarda, mevcut bir nesnenin kopyalanarak yeni nesnelerin üretilmesini sağlayan bir yaratımsal desendir. Bu desen, yeni nesneleri doğrudan new anahtar kelimesiyle oluşturmak yerine, “prototip” adı verilen bir şablon nesneyi klonlayarak çoğaltma prensibine dayanır. Bu sayede, istemci, oluşturulacak nesnenin somut sınıfından bağımsız hale gelir ve çalışma zamanında hangi nesnenin kopyalanacağına karar verebilir.

Desenin temel amacı, nesne oluşturma sürecini soyutlamak ve nesnelerin dinamik olarak kopyalanabilmesini sağlamaktır. Bir nesnenin kopyalanması işlemi genellikle “klonlama” olarak adlandırılır. Java dilinde bu klonlama yeteneği, java.lang.Object sınıfında tanımlanmış olan clone() metodu ve java.lang.Cloneable arayüzü aracılığıyla sağlanır.

Prototype deseni, özellikle aşağıdaki durumlarda faydalıdır:
* Bir nesnenin oluşturulması karmaşık ve maliyetli olduğunda (örneğin, veritabanı sorguları, ağır hesaplamalar veya ağ çağrıları gerektiren nesneler).
* Bir sistemin, farklı türdeki nesneleri önceden tanımlamadan, çalışma zamanında dinamik olarak oluşturması gerektiğinde.
* Bir nesnenin birçok varyantı olduğunda ve her varyant için ayrı bir sınıf hiyerarşisi oluşturmak pratik olmadığında.

Temel olarak, Prototype deseni, “kendini kopyala” (self-replication) ilkesini izler. Her prototip nesne, kendisinin bir kopyasını oluşturma yeteneğine sahip olmalıdır.

Neden Prototype Kullanmalıyız? Avantajları ve Dezavantajları

Prototype deseni, belirli senaryolarda önemli avantajlar sunarken, bazı dezavantajları da beraberinde getirir. Bu dengeyi anlamak, deseni doğru yerde ve doğru şekilde kullanmak için kritik öneme sahiptir.

Avantajları

* Performans İyileştirmesi: Yeni nesneleri sıfırdan oluşturmak yerine mevcut bir nesneyi kopyalamak, özellikle karmaşık başlatma süreçlerine sahip nesneler için önemli ölçüde daha hızlı olabilir. new operatörüyle bir nesne oluşturmak, yapıcı metodun çağrılması, bellek tahsisi ve potansiyel olarak pahalı kaynak başlatma işlemleri gerektirebilir. Klonlama, bu maliyetlerin bir kısmından kaçınabilir.
* Somut Sınıflardan Bağımsızlık: İstemci kod, prototip nesnenin somut sınıfına bağımlı olmak zorunda kalmaz. Yalnızca prototip arayüzü veya soyut sınıfı ile etkileşime girer. Bu, sistemin daha gevşek bağlı olmasını sağlar ve gelecekteki değişikliklere karşı daha dirençli hale getirir.
* Esneklik: Çalışma zamanında hangi nesnenin kopyalanacağına dinamik olarak karar verilebilir. Bu, uygulamanın davranışını çalışma zamanında değiştirmesine olanak tanır.
* Karmaşık Başlatma Mantığını Ortadan Kaldırma: Eğer bir nesnenin başlatılması çok sayıda parametre veya karmaşık bir yapılandırma gerektiriyorsa, Prototype deseni bu karmaşıklığı prototip nesnenin içine hapseder. İstemci sadece prototipi klonlar ve gerektiğinde küçük ayarlamalar yapar.
* Kaynak Tüketimini Azaltma: Veritabanı bağlantısı, ağ soketi veya büyük bir dosya okuma gibi maliyetli kaynakların tekrar tekrar başlatılmasını önleyerek, bu kaynakların bir kez oluşturulup sonra kopyalanmasına olanak tanır.

Dezavantajları

* Klonlama Karmaşıklığı (Özellikle Derin Kopyalama): Bir nesne, referans tiplerini (diğer nesneleri) içeriyorsa, sığ kopyalama (shallow copy) sorunlara yol açabilir. Derin kopyalama (deep copy) uygulamak, her bir alt nesnenin de klonlanmasını gerektirdiğinden karmaşık ve hataya açık olabilir. Nesne grafikleri karmaşıklaştıkça, derin kopyalama mantığını yönetmek zorlaşır.
* Cloneable Arayüzü ve clone() Metodunun Zorlukları: Java’daki Cloneable arayüzü bir işaretleyici arayüzdür (marker interface) ve clone() metodu Object sınıfında protected olarak tanımlanmıştır. Bu durum, clone() metodunun override edilmesini ve CloneNotSupportedException‘ın ele alınmasını gerektirir. Ayrıca, clone() metodunun doğru bir şekilde uygulanmaması (özellikle alt sınıflarda super.clone() çağrısının yapılmaması veya derin kopyalamanın atlanması), beklenmedik davranışlara yol açabilir.
* Mevcut Hiyerarşilere Uyum Sağlama Zorluğu: Eğer mevcut bir sınıf hiyerarşisi Cloneable arayüzünü uygulamıyorsa veya clone() metodunu doğru şekilde override etmiyorsa, Prototype desenini bu hiyerarşiye entegre etmek zor olabilir.
* Immutable (Değişmez) Nesneler İçin Gereksizlik: Eğer bir nesne tamamen değişmez (immutable) ise, klonlamanın genellikle bir anlamı yoktur, çünkü orijinal nesne ile kopyası arasında hiçbir fark olmayacaktır.

Prototype Tasarım Deseni Nasıl Uygulanır? (Java Örneği)

Prototype deseni, Java’da Cloneable arayüzü ve Object sınıfının clone() metodu kullanılarak uygulanır. İşte temel adımlar ve bir örnek:

Temel Yapı Taşları

1. Prototype Arayüzü/Soyut Sınıfı: Tüm klonlanabilir nesnelerin implement etmesi gereken bir arayüz veya soyut sınıf. Genellikle java.lang.Cloneable arayüzünü implement eder ve bir clone() metodu tanımlar.
2. ConcretePrototype Sınıfları: Prototype arayüzünü implement eden ve kendi clone() metodunu override ederek nesnenin bir kopyasını döndüren somut sınıflar.
3. Client (İstemci) Sınıfı: Bir prototip nesneyi elde eden ve onu klonlayarak yeni nesneler oluşturan sınıf.

Adım Adım Uygulama ve Kod Örneği

Örnek olarak, farklı şekiller (daire, dikdörtgen) oluşturabilen bir grafik düzenleyici senaryosunu ele alalım. Her şekil nesnesinin oluşturulması maliyetli olabilir ve biz mevcut bir şekli kopyalayarak yeni şekiller oluşturmak istiyoruz.

1. Prototype Arayüzünü Tanımlama (veya Cloneable Kullanma)
Java’da doğrudan bir Prototype arayüzü tanımlamak yerine, genellikle java.lang.Cloneable arayüzünü kullanırız. Ancak, clone() metodunun public bir versiyonunu sağlamak için kendi arayüzümüzü tanımlamak da iyi bir pratiktir.

// Prototype arayüzü
interface Shape extends Cloneable {
    void draw();
    Shape clone(); // clone() metodunu public olarak tanımlıyoruz
}

2. ConcretePrototype Sınıfları Oluşturma
Şimdi Shape arayüzünü implement eden somut sınıfları oluşturalım. Bu sınıflar, clone() metodunu override etmeli ve super.clone() çağrısı yaparak nesnenin bir kopyasını döndürmelidir.

// ConcretePrototype sınıfı: Circle
class Circle implements Shape {
    private String color;
    private int radius;

    public Circle(String color, int radius) {
        this.color = color;
        this.radius = radius;
        System.out.println("Circle nesnesi oluşturuldu: " + color + ", " + radius); // Maliyetli işlem simülasyonu
    }

    @Override
    public void draw() {
        System.out.println("Bir " + color + " renkli " + radius + " yarıçaplı daire çiziliyor.");
    }

    @Override
    public Shape clone() {
        try {
            // Object sınıfının sığ kopyalama yapan clone() metodunu çağırıyoruz.
            // Bu, ilkel tipleri (color, radius) doğrudan kopyalar.
            // Eğer Circle içinde referans tipli alanlar olsaydı, derin kopyalama gerekebilirdi.
            return (Shape) super.clone();
        } catch (CloneNotSupportedException e) {
            System.err.println("Klonlama desteklenmiyor: " + e.getMessage());
            return null;
        }
    }

    // Getter ve Setter metotları (örnek için kısaltıldı)
    public String getColor() { return color; }
    public void setColor(String color) { this.color = color; }
    public int getRadius() { return radius; }
    public void setRadius(int radius) { this.radius = radius; }
}

// ConcretePrototype sınıfı: Rectangle
class Rectangle implements Shape {
    private String color;
    private int width;
    private int height;

    public Rectangle(String color, int width, int height) {
        this.color = color;
        this.width = width;
        this.height = height;
        System.out.println("Rectangle nesnesi oluşturuldu: " + color + ", " + width + "x" + height); // Maliyetli işlem simülasyonu
    }

    @Override
    public void draw() {
        System.out.println("Bir " + color + " renkli " + width + "x" + height + " boyutunda dikdörtgen çiziliyor.");
    }

    @Override
    public Shape clone() {
        try {
            return (Shape) super.clone();
        } catch (CloneNotSupportedException e) {
            System.err.println("Klonlama desteklenmiyor: " + e.getMessage());
            return null;
        }
    }

    // Getter ve Setter metotları
    public String getColor() { return color; }
    public void setColor(String color) { this.color = color; }
    public int getWidth() { return width; }
    public void setWidth(int width) { this.width = width; }
    public int getHeight() { return height; }
    public void setHeight(int height) { this.height = height; }
}

3. Client Sınıfı
İstemci, prototipleri kullanarak yeni nesneler oluşturur.

// Client sınıfı
class ShapeClient {
    public static void main(String[] args) {
        // Prototip nesnelerini oluşturuyoruz (bu maliyetli olabilir)
        // Sadece bir kez oluşturulurlar.
        System.out.println("--- Prototip Nesneleri Oluşturuluyor ---");
        Circle redCircle = new Circle("Kırmızı", 10);
        Rectangle blueRectangle = new Rectangle("Mavi", 20, 30);
        System.out.println("--- Prototip Nesneleri Oluşturuldu ---");

        System.out.println("\n--- Prototip Nesneleri Klonlanıyor ---");
        // Klonlama ile yeni nesneler oluşturma
        Shape clonedRedCircle = redCircle.clone();
        if (clonedRedCircle instanceof Circle) {
            Circle newCircle = (Circle) clonedRedCircle;
            newCircle.setColor("Yeşil"); // Klonlanmış nesnenin rengini değiştiriyoruz
            newCircle.setRadius(15);
            newCircle.draw();
            System.out.println("Orijinal Kırmızı Daire: " + redCircle.getColor() + ", " + redCircle.getRadius());
            System.out.println("Klonlanmış Yeşil Daire: " + newCircle.getColor() + ", " + newCircle.getRadius());
            System.out.println("Orijinal ve klonlanmış nesneler aynı mı? " + (redCircle == newCircle)); // false olmalı
        }

        Shape clonedBlueRectangle = blueRectangle.clone();
        if (clonedBlueRectangle instanceof Rectangle) {
            Rectangle newRectangle = (Rectangle) clonedBlueRectangle;
            newRectangle.setColor("Sarı"); // Klonlanmış nesnenin rengini değiştiriyoruz
            newRectangle.setWidth(25);
            newRectangle.setHeight(35);
            newRectangle.draw();
            System.out.println("Orijinal Mavi Dikdörtgen: " + blueRectangle.getColor() + ", " + blueRectangle.getWidth() + "x" + blueRectangle.getHeight());
            System.out.println("Klonlanmış Sarı Dikdörtgen: " + newRectangle.getColor() + ", " + newRectangle.getWidth() + "x" + newRectangle.getHeight());
            System.out.println("Orijinal ve klonlanmış nesneler aynı mı? " + (blueRectangle == newRectangle)); // false olmalı
        }
        System.out.println("--- Prototip Nesneleri Klonlandı ---");
    }
}

Yukarıdaki örnekte, Circle ve Rectangle nesneleri sadece bir kez, prototip olarak oluşturulur. Daha sonra, bu prototipler clone() metodu kullanılarak çoğaltılır. Circle ve Rectangle yapıcı metotlarının yalnızca prototipler oluşturulurken çağrıldığına dikkat edin. Klonlama işlemi sırasında yapıcı metotlar çağrılmaz, bu da performans avantajı sağlar.

Sığ Kopyalama (Shallow Copy) ve Derin Kopyalama (Deep Copy)

Object.clone() metodu varsayılan olarak sığ kopyalama yapar. Bu, Prototype desenini uygularken en önemli noktalardan biridir.

Sığ Kopyalama (Shallow Copy)

* Tanım: Bir nesnenin sığ kopyası oluşturulduğunda, ilkel veri tiplerinin (int, boolean, char vb.) değerleri doğrudan kopyalanır. Ancak, referans veri tiplerinin (diğer nesneler, diziler) kendileri kopyalanmaz; sadece bu referansların adresleri kopyalanır. Yani, hem orijinal nesne hem de kopya nesne, aynı alt nesneleri işaret eder.
* Sorunlar: Eğer orijinal veya kopya nesne, paylaşılan bir alt nesnenin durumunu değiştirirse, bu değişiklik diğer nesneyi de etkiler. Bu, beklenmedik yan etkilere ve hatalara yol açabilir.
* Ne Zaman Kullanılır: Bir nesne sadece ilkel veri tipleri veya değişmez (immutable) referans tipleri içeriyorsa sığ kopyalama yeterli olabilir.

Örnek olarak, bir Person sınıfının Address nesnesi içerdiğini düşünelim:

class Address {
    String street;
    String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address{" + "street='" + street + '\'' + ", city='" + city + '\'' + '}';
    }
}

class Person implements Cloneable {
    String name;
    int age;
    Address address; // Referans tip

    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone(); // Sığ kopyalama
    }

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}';
    }
}

public class ShallowCopyDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address originalAddress = new Address("Ana Cadde 10", "İstanbul");
        Person originalPerson = new Person("Ali", 30, originalAddress);

        Person clonedPerson = (Person) originalPerson.clone();

        System.out.println("Orijinal Kişi: " + originalPerson);
        System.out.println("Klonlanmış Kişi: " + clonedPerson);

        // Klonlanmış kişinin adresini değiştiriyoruz
        clonedPerson.address.street = "Yeni Sokak 5";
        clonedPerson.address.city = "Ankara";

        System.out.println("\nAdres değişikliğinden sonra:");
        System.out.println("Orijinal Kişi: " + originalPerson); // Orijinal kişinin adresi de değişti!
        System.out.println("Klonlanmış Kişi: " + clonedPerson);

        System.out.println("Orijinal ve klonlanmış adres nesneleri aynı mı? " + (originalPerson.address == clonedPerson.address)); // true
    }
}

Yukarıdaki çıktıdan da anlaşılacağı gibi, clonedPerson.address üzerinde yapılan değişiklik, originalPerson.address‘ı da etkilemiştir, çünkü her ikisi de bellekteki aynı Address nesnesini işaret etmektedir.

Derin Kopyalama (Deep Copy)

* Tanım: Bir nesnenin derin kopyası oluşturulduğunda, orijinal nesnenin kendisiyle birlikte içerdiği tüm referans tipli alt nesneler de ayrı ayrı kopyalanır. Bu, orijinal ve kopya nesnelerin tamamen bağımsız olmasını sağlar; birindeki değişiklik diğerini etkilemez.
* Yöntemler:
* Manuel Klonlama: Her bir referans tipli alanın clone() metodunu manuel olarak çağırarak (eğer alt nesneler de Cloneable ise).
* Serileştirme (Serialization): Nesneyi bir bayt akışına serileştirip ardından bu akışı tekrar bir nesneye dönüştürerek. Bu yöntem, nesne grafiği karmaşık olduğunda oldukça kullanışlıdır, ancak Serializable arayüzünü implement etmeyi gerektirir ve performans maliyeti olabilir.

Manuel Derin Kopyalama Örneği:
Person sınıfını derin kopyalama yapacak şekilde güncelleyelim:

// Address sınıfı da klonlanabilir olmalı
class Address implements Cloneable {
    String street;
    String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone(); // Address için sığ kopyalama yeterli olabilir, ilkel tipler içeriyor.
                               // Ancak Person'dan çağrıldığında yeni bir Address nesnesi yaratacak.
    }

    @Override
    public String toString() {
        return "Address{" + "street='" + street + '\'' + ", city='" + city + '\'' + '}';
    }
}

class PersonDeepCopy implements Cloneable {
    String name;
    int age;
    Address address;

    public PersonDeepCopy(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        PersonDeepCopy clonedPerson = (PersonDeepCopy) super.clone(); // Önce Person nesnesini sığ kopyala
        clonedPerson.address = (Address) address.clone(); // Sonra Address nesnesini derin kopyala
        return clonedPerson;
    }

    @Override
    public String toString() {
        return "PersonDeepCopy{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}';
    }
}

public class DeepCopyDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address originalAddress = new Address("Ana Cadde 10", "İstanbul");
        PersonDeepCopy originalPerson = new PersonDeepCopy("Ayşe", 25, originalAddress);

        PersonDeepCopy clonedPerson = (PersonDeepCopy) originalPerson.clone();

        System.out.println("Orijinal Kişi: " + originalPerson);
        System.out.println("Klonlanmış Kişi: " + clonedPerson);

        // Klonlanmış kişinin adresini değiştiriyoruz
        clonedPerson.address.street = "Yeni Sokak 5";
        clonedPerson.address.city = "Ankara";

        System.out.println("\nAdres değişikliğinden sonra:");
        System.out.println("Orijinal Kişi: " + originalPerson); // Orijinal kişinin adresi değişmedi!
        System.out.println("Klonlanmış Kişi: " + clonedPerson);

        System.out.println("Orijinal ve klonlanmış adres nesneleri aynı mı? " + (originalPerson.address == clonedPerson.address)); // false
    }
}

Bu örnekte, PersonDeepCopy sınıfının clone() metodu, önce PersonDeepCopy nesnesini sığ bir şekilde kopyalar, ardından address alanını da kendi clone() metodu aracılığıyla derinlemesine kopyalar. Böylece, orijinal ve klonlanmış PersonDeepCopy nesneleri tamamen bağımsız Address nesnelerine sahip olur.

Serileştirme ile Derin Kopyalama:
Bu yöntem, nesne grafiği çok karmaşık olduğunda ve her bir alt nesne için manuel clone() çağrısı yazmak yorucu hale geldiğinde tercih edilebilir.

import java.io.*;

// Tüm sınıfların Serializable arayüzünü implement etmesi gerekir
class AddressSerializable implements Serializable {
    String street;
    String city;

    public AddressSerializable(String street, String city) {
        this.street = street;
        this.city = city;
    }

    @Override
    public String toString() {
        return "AddressSerializable{" + "street='" + street + '\'' + ", city='" + city + '\'' + '}';
    }
}

class PersonSerializable implements Serializable {
    String name;
    int age;
    AddressSerializable address;

    public PersonSerializable(String name, int age, AddressSerializable address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    // Serileştirme yoluyla derin kopyalama yapan bir yardımcı metot
    public PersonSerializable deepCopy() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        oos.flush();

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (PersonSerializable) ois.readObject();
    }

    @Override
    public String toString() {
        return "PersonSerializable{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}';
    }
}

public class DeepCopySerializationDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        AddressSerializable originalAddress = new AddressSerializable("Liman Yolu 1", "İzmir");
        PersonSerializable originalPerson = new PersonSerializable("Can", 40, originalAddress);

        PersonSerializable clonedPerson = originalPerson.deepCopy();

        System.out.println("Orijinal Kişi: " + originalPerson);
        System.out.println("Klonlanmış Kişi: " + clonedPerson);

        // Klonlanmış kişinin adresini değiştiriyoruz
        clonedPerson.address.street = "Kordon Sahil 20";
        clonedPerson.address.city = "Muğla";

        System.out.println("\nAdres değişikliğinden sonra:");
        System.out.println("Orijinal Kişi: " + originalPerson); // Orijinal kişinin adresi değişmedi!
        System.out.println("Klonlanmış Kişi: " + clonedPerson);

        System.out.println("Orijinal ve klonlanmış adres nesneleri aynı mı? " + (originalPerson.address == clonedPerson.address)); // false
    }
}

Serileştirme tabanlı derin kopyalama, esnek olmasına rağmen, performans açısından manuel klonlamadan daha yavaş olabilir ve tüm nesnelerin Serializable arayüzünü implement etmesini gerektirir.

Prototype Manager (Kayıt Defteri)

Bazen, uygulamanın farklı türlerde birçok prototip nesneye ihtiyacı olabilir ve bu prototipleri yönetmek için merkezi bir mekanizma kullanmak faydalı olur. Prototype Manager (veya Prototype Registry), prototip nesneleri depolayan, belirli bir anahtar aracılığıyla erişim sağlayan ve istemcinin bu prototipleri klonlamasına olanak tanıyan bir yapıdır. Bu, istemcinin somut prototip sınıflarından daha da izole olmasını sağlar.

import java.util.HashMap;
import java.util.Map;

// Daha önceki Shape arayüzü ve Circle/Rectangle sınıflarını kullanıyoruz.
// Shape interface ve Circle, Rectangle sınıfları yukarıdaki örneklerden alınabilir.

class ShapeCache {
    private static Map shapeMap = new HashMap<>();

    public static void loadCache() {
        // Uygulama başlangıcında prototip nesnelerini önbelleğe yüklüyoruz
        Circle circle = new Circle("Kırmızı", 10);
        shapeMap.put("Kırmızı Daire", circle);

        Rectangle rectangle = new Rectangle("Mavi", 20, 30);
        shapeMap.put("Mavi Dikdörtgen", rectangle);

        System.out.println("Prototip önbelleği yüklendi.");
    }

    public static Shape getShape(String shapeName) {
        Shape cachedShape = shapeMap.get(shapeName);
        // Önbellekten alınan prototipin bir kopyasını döndürüyoruz
        return cachedShape != null ? cachedShape.clone() : null;
    }
}

public class PrototypeManagerDemo {
    public static void main(String[] args) {
        ShapeCache.loadCache(); // Prototip önbelleğini yüklüyoruz

        System.out.println("\n--- Prototip Yöneticisinden Nesneler Alınıyor ---");

        Shape shape1 = ShapeCache.getShape("Kırmızı Daire");
        if (shape1 != null) {
            shape1.draw();
            // Klonlanmış nesne üzerinde değişiklik yapabiliriz
            ((Circle) shape1).setColor("Turuncu");
            ((Circle) shape1).setRadius(12);
            shape1.draw();
        }

        Shape shape2 = ShapeCache.getShape("Mavi Dikdörtgen");
        if (shape2 != null) {
            shape2.draw();
            ((Rectangle) shape2).setColor("Yeşil");
            ((Rectangle) shape2).setWidth(22);
            shape2.draw();
        }

        Shape shape3 = ShapeCache.getShape("Kırmızı Daire"); // Yeni bir kopya
        if (shape3 != null) {
            shape3.draw(); // Orijinal Kırmızı Daire'nin bir kopyası
        }

        System.out.println("\n--- Orijinal Prototip Nesneleri ---");
        // Orijinal prototiplerin değişmediğini gösterelim
        ShapeCache.getShape("Kırmızı Daire").draw(); // Aslında bu da bir klon, ama orijinal prototipin durumunu yansıtır
        ShapeCache.getShape("Mavi Dikdörtgen").draw();
    }
}

ShapeCache sınıfı, prototip Shape nesnelerini bir HashMap içinde saklar. loadCache() metodu, prototipleri başlangıçta oluşturur ve önbelleğe alır. getShape() metodu ise, istenen prototipin bir kopyasını döndürür. Bu yapı, istemcinin new anahtar kelimesiyle somut sınıfları çağırmasına gerek kalmamasını sağlar.

Prototype Tasarım Deseni Ne Zaman Kullanılmalı?

Prototype desenini kullanmak için uygun senaryoları anlamak, bu desenin potansiyelini en üst düzeye çıkarmak için önemlidir:
* Karmaşık ve Maliyetli Nesne Oluşturma: Bir nesnenin başlatılması çok fazla kaynak tüketiyorsa (CPU, bellek, disk I/O, ağ çağrıları), mevcut bir nesneyi kopyalamak, sıfırdan oluşturmaktan daha verimli olabilir.
* Somut Sınıflardan Bağımsızlık İhtiyacı: Uygulama, hangi somut sınıfların kullanılacağını derleme zamanında bilmek istemediğinde veya bilmediğinde. İstemci, sadece prototip arayüzü üzerinden nesneleri klonlar.
* Dinamik Nesne Türleri: Çalışma zamanında yeni nesnelerin türünün dinamik olarak belirlenmesi gerektiğinde. Örneğin, bir oyun motorunda farklı düşman türleri veya mermi varyantları dinamik olarak oluşturulabilir.
* Çok Sayıda Benzer Nesne: Bir nesnenin birçok farklı varyantı olduğunda ve her varyant için yeni bir sınıf oluşturmak veya karmaşık bir yapıcı metot kullanmak pratik olmadığında. Prototip, bir şablon görevi görür ve varyantlar klonlanarak üzerinde küçük değişiklikler yapılarak oluşturulur.
* Veritabanı veya Ağ Kaynakları: Veritabanı bağlantıları veya ağ soketleri gibi pahalı kaynakları içeren nesnelerin tekrar tekrar oluşturulmasını engellemek. Bir kez oluşturulmuş bir bağlantı nesnesi klonlanarak yeniden kullanılabilir (ancak bu senaryoda havuzlama desenleri daha yaygın olabilir).

Diğer Yaratımsal Desenlerle Karşılaştırma

Prototype deseni, diğer yaratımsal desenlerle benzer amaçlara hizmet eder, ancak farklı mekanizmalar kullanır:

* Singleton: Yalnızca bir sınıfın tek bir örneğinin olmasını sağlar. Prototype ise birçok kopya üretir. Amaçları tamamen farklıdır.
* Factory Method / Abstract Factory: Bu desenler, nesne oluşturma mantığını bir fabrika sınıfına veya metoduna delege eder. İstemci, nesneleri doğrudan new anahtar kelimesiyle oluşturmak yerine fabrika metodunu çağırır. Prototype ise mevcut bir nesneyi klonlayarak yeni nesneler üretir. Fabrikalar genellikle nesneleri sıfırdan oluştururken, Prototype mevcut nesnelerden yararlanır. Bazı durumlarda, bir fabrika deseni, prototipleri depolamak ve klonlamak için bir Prototype Manager kullanabilir.
* Builder: Karmaşık nesnelerin adım adım, esnek bir şekilde oluşturulmasını sağlar. Builder, bir nesnenin farklı parçalarını bir araya getirerek nihai nesneyi oluşturur. Prototype ise zaten tam olarak yapılandırılmış bir nesnenin kopyasını oluşturur. Eğer bir nesnenin birçok farklı yapılandırması varsa ve her biri için bir prototip oluşturmak maliyetliyse, Builder daha uygun olabilir. Ancak, mevcut bir nesnenin durumunu koruyarak yeni nesneler oluşturmak isteniyorsa Prototype tercih edilir.

Ne Zaman Hangisi Tercih Edilmeli?
* Nesne oluşturma maliyeti yüksekse ve mevcut bir nesnenin kopyalanması daha hızlıysa: Prototype.
* Tek bir örnek yeterliyse: Singleton.
* Nesne oluşturma mantığını soyutlamak ve somut sınıflardan bağımsız olmak isteniyorsa: Factory Method / Abstract Factory (ancak Prototype da bu bağımsızlığı sağlar).
* Karmaşık bir nesnenin farklı kombinasyonlarda veya adım adım oluşturulması gerekiyorsa: Builder.

Gerçek Dünya Kullanım Alanları ve Örnekler

Prototype deseni, yazılımın çeşitli alanlarında pratik uygulamalar bulur:

* Oyun Geliştirme: Oyunlarda birçok benzer nesne (düşmanlar, mermiler, engeller) bulunur. Her birini new anahtar kelimesiyle oluşturmak performans düşüşüne neden olabilir. Bir prototip düşman nesnesi oluşturulup, gerektiğinde klonlanarak sahneye eklenebilir.
* Grafik Düzenleyiciler: Bir grafik düzenleyicide (örneğin, bir çizim programı), kullanıcı bir şekli (daire, kare) seçip kopyaladığında, orijinal şeklin bir klonu oluşturulur. Bu klon daha sonra düzenlenebilir.
* Veritabanı Bağlantı Havuzları: Bazı durumlarda, bir veritabanı bağlantı nesnesi klonlanarak havuzdaki diğer kullanıcılara sunulabilir (ancak genellikle bu senaryo için bağlantı havuzu deseni daha özelleşmiştir).
* Belge Düzenleyiciler: Bir belge şablonu (template document) oluşturulduğunda, yeni bir belge oluşturmak için bu şablon klonlanabilir.
* Java Kütüphaneleri: Java’nın kendi standart kütüphanesinde Prototype deseninin izleri bulunur. Örneğin, ArrayList.clone(), HashMap.clone() gibi metotlar, koleksiyonların sığ kopyalarını oluşturur. Calendar sınıfı da clone() metodunu kullanarak tarih nesnelerinin kopyalarını oluşturur.

Sık Karşılaşılan Hatalar ve Dikkat Edilmesi Gerekenler

Prototype desenini Java’da uygularken bazı yaygın hatalar ve dikkat edilmesi gereken noktalar vardır:

* Cloneable Arayüzünü Implement Etmeyi Unutmak: Bir sınıf Cloneable arayüzünü implement etmeden Object.clone() metodunu çağırırsa, CloneNotSupportedException fırlatılır. Bu arayüz, JVM’e bu sınıfın klonlanabilir olduğunu bildiren bir işaretçidir.
* clone() Metodunu public Yapmamak: Object.clone() metodu protected olarak tanımlandığı için, alt sınıflarda override edildiğinde genellikle public olarak tanımlanır. Aksi takdirde, dışarıdan erişilemez ve desenin amacı bozulur.
* super.clone() Çağrısını Yapmayı Unutmak: Bir sınıfın clone() metodunu override ederken, super.clone() çağrısını yapmak çok önemlidir. Bu, nesnenin bellek tahsisini ve ilkel alanların kopyalanmasını sağlar. Bu çağrı yapılmazsa, tamamen yeni bir nesne oluşturulur ve klonlamanın amacı ortadan kalkar.
* Derin Kopyalama Gerektiğinde Sığ Kopyalama Yapmak: Bu, Prototype deseninde en sık yapılan ve en tehlikeli hatalardan biridir. Nesne referansları içeren bir sınıfın clone() metodu sığ kopyalama yaptığında, orijinal ve klonlanmış nesneler aynı alt nesneleri paylaşır. Bu, durum tutarsızlıklarına ve hatalara yol açar. Her zaman nesnenizin yapısını analiz edin ve derin kopyalama gerekip gerekmediğine karar verin.
* Immutable (Değişmez) Nesnelerde Klonlamanın Gereksizliği: Eğer bir nesne tamamen değişmezse (yani oluşturulduktan sonra durumu asla değişmezse), onu klonlamanın genellikle bir faydası yoktur. Orijinal nesneyi doğrudan kullanmak, klonlamadan daha verimli ve güvenlidir.

Sonuç ve Özet

Prototype tasarım deseni, nesne oluşturma sürecini soyutlayarak ve mevcut nesneleri kopyalayarak yeni nesneler üretme yeteneği sunan güçlü bir yaratımsal desendir. Özellikle karmaşık ve maliyetli nesnelerin sıkça oluşturulması gereken senaryolarda, performans iyileştirmesi ve sistemin esnekliğini artırma potansiyeli taşır.

Java’da Cloneable arayüzü ve Object.clone() metodu aracılığıyla uygulanan Prototype deseni, sığ ve derin kopyalama kavramlarını anlamayı gerektirir. Sığ kopyalama, referans tiplerini paylaşırken, derin kopyalama alt nesnelerin de kopyalanmasını sağlayarak nesnelerin tamamen bağımsız olmasını garanti eder. Derin kopyalama, manuel olarak veya serileştirme gibi tekniklerle uygulanabilir.

Prototype Manager gibi yapılar, prototiplerin merkezi bir şekilde yönetilmesini sağlayarak istemcinin somut sınıflardan bağımsızlığını daha da artırır. Oyun geliştirme, grafik düzenleyiciler ve belge şablonları gibi çeşitli alanlarda pratik uygulamaları bulunmaktadır.

Ancak, deseni uygularken Cloneable arayüzünün doğru kullanımı, clone() metodunun override edilmesi ve özellikle derin kopyalama ihtiyacının doğru tespiti gibi noktalara dikkat etmek, potansiyel hataları önlemek için hayati öneme sahiptir. Doğru kullanıldığında, Prototype deseni, daha verimli, esnek ve sürdürülebilir yazılım sistemleri oluşturmanıza yardımcı olan değerli bir araçtır.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık