~/Ali GÖREN

Creational Design Patterns

Ali Goren · · 7 dk okuma

Creational Design Patterns

https://unsplash.com/photos/man-in-black-jacket-holding-camera-CTflmHHVrBM
Selamlar. Bu yazıda Design Patterns konularından olan Creational Patterns konusu hakkında bilgi vermeye çalışacağım. Türkçede çeviri olarak “Yaratımsal Tasarım Desenleri” olarak biliniyor. Birazcık anlamsal zorluğa sahip gibi geliyor.

Creational Patterns Nedir? Ne İş Yapar?

Aslında bu türden creational design patterns konusundan bahsederken, nesnelerin nasıl oluşturulacağından ve yönetileceğinden de bahsetmiş oluyoruz.

Nesne oluşturma işlemlerinde yaşanan karmaşayı azaltmayı hedefler. Bunun yanında koda esneklik kadar. Yeniden kullanılabilirlik katar.

Bana Creational Patterns Listesini Ver!

Hay hay! Aşağıdaki design pattern konularını mutlaka duymuşsunuzdur. Bunlar creational patterns olarak da bilinir.

Singleton Design Pattern

Singleton design pattern, bir sınıfın sadece bir örneğinin olmasını garanti eden bir patterndir. Tabiiki burada hatalı kodlanmış bir singleton örneğinden bahsetmiyorum. Design pattern buradaki konumuzdur. Bu design pattern’den bahsederken, aklınıza logging, configuration ya da database konuları gelebilir.

Veri tabanı ya da logger örnekleri gerçek hayat senaryoları içerisinde en çok karşılaşacağınız örnekler olabilir.

Factory Method Pattern

Bu design pattern’da nesnelerin oluşturulması alt sınıflarda gerçekleştirilir. Bunu yaparken de bir interface kullanılır. Bu sayede nesnelerin oluşturulması soyut bir hale gelirken, alt sınıflarda da hangi nesnelerin oluşturulacağı belirlenir.

Örneğin “ZubiZaretta” isimli bir e-ticaret sitemiz olsun. Bu sitede ödeme işlemleri yapılıyor. Kredi kartı, havale ya da örneğin yurt dışında da PayPal olsun. Elimizde aşağıdaki ödeme yöntemleri mevcut

  • Kredi kartı
  • PayPal
  • Havale

Factory Method Pattern kullanarak farklı olan bu ödeme yöntemleri yönetilebilir. Kod örneğini burada paylaşmak yazıyı uzatak ama faydasını şöyle gösterebilirim

ProcessPayment(new CreditCardPaymentProcessorFactory(), 200m);

void ProcessPayment(PaymentProcessorFactory factory, decimal amount)
{
    IPaymentProcessor processor = factory.CreatePaymentProcessor();
    processor.ProcessPayment(amount);
}

Yukarıdaki koda bakarak bu kodun logic değiştirmeden, genişletilebilir bir kod olduğunu görmüş oluyoruz. 1 ay sonra Abuzittin isimli ödeme yöntemi gelseydi şöyle bir kod işimizi çözecekti.

ProcessPayment(new CreditCardPaymentProcessorFactory(), 200m);
ProcessPayment(new AbuzittinPaymentProcessorFactory(), 200m);

void ProcessPayment(PaymentProcessorFactory factory, decimal amount)
{
    IPaymentProcessor processor = factory.CreatePaymentProcessor();
    processor.ProcessPayment(amount);
}

Factory sınıflarının kendi içlerindeki davranışları, o sınıfları bağlar. Örneği kredi kartı kuruluşunun API’ları ile Abuzittin’in aynı olmayabilir. Söylemek istediğim bu. Ama siz asla ve asla asıl payment adımında bir değişiklik yapmazsınız.

Abstract Factory Pattern

Bu design pattern Factory Method Design Pattern ile çok karıştırılıyor. Factory Method DP’de ortak bir interface ile işleri halledebilirken, Abstract Factory DP’de ise birden fazla nesnenin arayüzüne göre işleri hallederiz.

Sıkça verilen bir örnek üzerinden gitmek istiyorum. Örneğin mobilya üreten bir fabrikamız var. Bu fabrikada eski tür ve modern mobilyalar üretiliyor. Sandalye ve masalar olsun. Bunları üretirken yazılımsal olarak ortaya çıkan karmaşayı Abstract Factory Pattern ile çözebiliriz. Aslında buna karmaşa demem ama anlaşılması için öyle diyorum. Yaklaşımımız şöyle olur;

  • Abstract Factory
  • Concrete Factory
  • Abstract Product
  • Concrete Product
  • Client

Abstract factory ve abstract product’lar interface’leri içerirken, concrete factory ve product’lar ise implementasyonları içerir. Client ise burada kullanıcıdır.

Mesela sandalyeleri abstract factory olarak ISandalye olarak düşünelim. Yine IMasa da burada. Bunların implementasyonu Concrete Factory’de gerçekleştirilir.

Abstract Product dediğimiz factory interface ise bahsedilen 2 interface’i kullanır. Örneğin IUretimFactory adında bir interface olsun. Bu interface’in memberları ISandalye ve IMasa türünden 2 method olsun.

Concrete Product dediğimiz sınıflar ise bu bahsettiğimiz Abstract Product’ı implement eder.

Örneğin “FuturistikMobilya” adında 3. türden bir mobilya işimiz olsaydı, sadece FuturistikMobilya sınıfını ve bu sınıfı yönetmek için bir de factory sınıfını eklememiz yetecekti. Haliyle kod genişleyebilir durumda olurdu. Mevcut kodumuz değişmeden de yeni özellikler eklenebilir olurdu.

Builder Pattern

Bu design pattern kullanılarak karmaşıklığı yüksek nesnelerin adım adım oluşturulması sağlanır. Bu bize bir işin adımlara bölünerek yapılabileceğini söyler. Yine bu adımların yönetilmesi için de bir Builder sınıf kullanılır. Yapısal olarak şöyledir;

  • Product
  • Builder
  • ConcreteBuilder
  • Director

Gerçek dünya örneği birazcık acıkmamla alakalı olarak peynir üretim sürecini içerecek. Peynirin nasıl üretileceğini bilmesem de içerisindeki temel ürünleri koyduğumuzu söyleyelim. Normalde bu yazıda kod örneği vermeyecektim. Ancak kod örneği bu pattern’i çok iyi açıklar.

  • Süt
  • Yoğurt
  • Tuz

Şimdi sınıfınızın bu 3 property’e sahip olduğunu düşünün. Yani peyniri yaparken 3 farklı ürünü set etmeniz karışıma eklemeniz gerekiyor. Bunun class’ı nasıl olurdu?

internal class Peynir
{
    public double Sut { get; set; }

    public double Yogurt { get; set; }

    public double Tuz { get; set; }
}

Ne demiştik yapılan işi süreçlere bölerek peyniri üreteceğiz. O zaman bu cümlesini kurduğumuz işin bir de arayüzünü kurgulayalım.

internal interface IPeynirBuilder
{
    void SetSut(double oran);

    void SetYogurt(double oran);

    void SetTuz(double oran);

    Peynir GetPeynir();
}

Şimdi elimizde peynir sınıfı ve bir de Builder arayüzü var. Bu bilgilerle beyaz peynir üretelim.

internal class BeyazPeynirBuilder : IPeynirBuilder
{
    private Peynir _peynir = new Peynir();

    public void SetSut(double oran)
    {
        _peynir.Sut = oran;
    }

    public void SetTuz(double oran)
    {
        _peynir.Tuz = oran;
    }

    public void SetYogurt(double oran)
    {
        _peynir.Yogurt = oran;
    }

    public Peynir GetPeynir()
    {
        return _peynir;
    }
}

E beyaz peyniri ürettik, bir de gelin kaşar peynir üretelim. Tabiiki kaşar peynir 2–3 malzeme ile üretilmiyor. Amaç burada süreci anlamak.

internal class KasarPeynirBuilder : IPeynirBuilder
{
    private Peynir _peynir = new Peynir();

    public void SetSut(double oran)
    {
        _peynir.Sut = oran;
    }

    public void SetTuz(double oran)
    {
        _peynir.Tuz = oran;
    }

    public void SetYogurt(double oran)
    {
        _peynir.Yogurt = oran;
    }

    public Peynir GetPeynir()
    {
        return _peynir;
    }
}

Yapısal olarak bahsettiğimiz kısımda Director’den bahsetmiştik. Şimdi bu işi PeynirUretici isimli sınıfta yapalım.

internal class PeynirUretici
{
    private IPeynirBuilder _builder;

    public PeynirUretici(IPeynirBuilder builder)
    {
        _builder = builder;
    }

    public void BeyazPeynirUret()
    {
        _builder.SetSut(5);
        _builder.SetYogurt(2);
        _builder.SetTuz(0.4);
    }

    public void KasarPeynirUret()
    {
        _builder.SetSut(7);
        _builder.SetYogurt(10);
        _builder.SetTuz(2);
    }

    public Peynir GetPeynir()
    {
        return _builder.GetPeynir();
    }
}

Artık kullanımı da şöyle olur :)

IPeynirBuilder beyazPeynirBuilder = new BeyazPeynirBuilder();
PeynirUretici peynirUretici = new PeynirUretici(beyazPeynirBuilder);
peynirUretici.BeyazPeynirUret();
var beyazPeynir = peynirUretici.GetPeynir();

Aynısını kaşar peynir için de yapabilirsiniz.

Prototype Pattern

Son design pattern olan prototype pattern’a geldik. Bu design pattern’da mevcut bir nesnenin klonlarını oluşturarak yeni nesneler oluşturulur. Aslında bu cümle hatalı. Sıfırdan bir nesne oluşturmuyor. Var olan bir nesnenin kopyasını oluşturuyor. Yeni bir nesne oluşturmanın bir hayli maliyetli olduğu süreçlerde prototype pattern kullanılır. Nesnelerin oluşturulma süreçleri hızlanır. Yapısal olarak aşağıdaki gibidir;

  • Prototype
  • ConcretePrototype

Örneğin bir doküman yönetim sisteminde raporların, sözleşmelerin yönetilmesi için bir örneğimiz olsun. Örneğin şöyle bir sınıfımız olsun

internal abstract class Document
{
    public string Id { get; set; }
    public string Title { get; set; }

    public abstract Document Clone();
}

Clone işlemi yapan abstract bir metoda sahibiz. Dokümanın kendisini klonlar.

Sonrasında rapor ve sözleşme için aşağıdaki sınıflarımızın olduğunu hayal edin.

internal class Report : Document
{
    public int PageCount { get; set; }

    public override Document Clone()
    {
        return (Document)MemberwiseClone();
    }

    public override string ToString()
    {
        return $"Report: Id={Id}, Title={Title}, PageCount={PageCount}";
    }
}

internal class Contract : Document
{
    public string PartiesInvolved { get; set; }

    public override Document Clone()
    {
        return (Document)MemberwiseClone();
    }

    public override string ToString()
    {
        return $"Contract: Id={Id}, Title={Title}, PartiesInvolved={PartiesInvolved}";
    }
}

Hem rapor hem de sözleşmelerin cache’lendiği ve klonlandığı işi yapan DocumentCache sınıfı ise şöyle olsun

internal class DocumentCache
{
    private static Dictionary documentMap = new Dictionary();

    public static Document GetDocument(string documentId)
    {
        Document cachedDocument = documentMap[documentId];
        return cachedDocument.Clone();
    }

    public static void LoadCache()
    {
        Report report = new Report { Id = "1", Title = "Annual Report", PageCount = 50 };
        documentMap[report.Id] = report;

        Contract contract = new Contract { Id = "2", Title = "Sales Contract", PartiesInvolved = "Company A & Company B" };
        documentMap[contract.Id] = contract;
    }
}

Bu yapıyı kullanan örnek bir kodumuz şöyle olsun;

DocumentCache.LoadCache();

while (true)
{
    Console.WriteLine("Choose document to clone:");
    Console.WriteLine("1. Report");
    Console.WriteLine("2. Contract");
    Console.WriteLine("0. Exit");
    string choice = Console.ReadLine();

    if (choice == "0")
    break;

    Document clonedDocument = null;
    switch (choice)
    {
        case "1":
        clonedDocument = DocumentCache.GetDocument("1");
        break;
        case "2":
        clonedDocument = DocumentCache.GetDocument("2");
        break;
        default:
        Console.WriteLine("Invalid choice.");
        continue;
    }

    if (clonedDocument != null)
    {
        clonedDocument.Id = Guid.NewGuid().ToString();
        Console.WriteLine("Cloned Document:");
        Console.WriteLine(clonedDocument);
    }
}

Hepsi bu kadar. Umarım sizin için faydalı olmuştur. Okuduğunuz için teşekkür ederim.

Kaynaklar