Derya Dok Kişisel Blog

Tag: oop

SOLID Yazılım Prensipleri – Dependency Inversion Principle

SOLID yazı dizisinin sonuncusu olan Dependency Inversion yazısından merhabalar! Bu yazıda sıklıkla Dependency Injection (DI) ile karıştırılan Dependency Inversion (DIP) prensibini inceleyeceğiz. Temelde “soyutlamaya bağlı olma” felsefesine dayanan bu prensip, sınıflar arasındaki sıkı bağımlılıkları (tight coupling) kırmayı hedefler. Yüksek bağımlılık; kodun bir parçasını diğerlerinden izole edememek ve değişime karşı direnç gösteremeyen bir yapı kurmak demektir.

Dependency Inversion Nedir?

Bu prensibi daha somut bir örnekle ele alalım: Bir lambayı evin elektrik tesisatına bağlamak istediğinizi hayal edin. Lambanın kablolarını duvarın içindeki elektrik hattına doğrudan bağlarsak, lambayı değiştirmek istediğimizde tüm duvarı kırmamız, kabloları kesmemiz ve yeni lambayı tekrar bağlamamız gerekir. Bu durum da aslında ‘Sıkı Bağımlılık’ (Tight Coupling) dediğimiz durumdur.

Oysa gerçek hayatta duvara bir priz (interface) koyuyoruz. Lamba, elektriğin kaynağına değil, o prize uyumlu olan fişe (abstraction) bağımlı oluyor. Dependency Inversion tam olarak budur:

  • Üst Seviye Modül (Lamba): Alt seviye modülün detaylarıyla (duvarın içindeki kablo tipi) ilgilenmez.
  • Alt Seviye Modül (Elektrik Tesisatı): Doğrudan lambaya bağlı değildir.
  • Soyutlama (Priz/Fiş): Her iki tarafın da üzerinde anlaştığı ortak standarttır.

Böylece priz standart kaldığı sürece, istersek lamba veya bilgisayarı şarja takabiliriz. Aynı şekilde kodunuzda bir değişikliğe gittiğinizde tüm sistemi bağımlılıklarından sökmenize gerek kalmaz.

Örnek olarak DIP kurallarına uymayan şu yapıyı inceleyelim;

internal class Order
{
    public Guid Id { get; set; }
}

internal class FileLogger
{
    public void Log(string message)
    {
        // Simulate logging to a file
        StreamWriter writer = new StreamWriter("log.txt");
        writer.WriteLine(message);
        writer.Flush();
        writer.Close();
    }
}

internal class DBLogger
{
    public void Log(string message)
    {
        SqlConnection conn = new SqlConnection();
        SqlCommand cmd = new SqlCommand("Insert", conn);
        conn.Open();
        cmd.ExecuteNonQuery();
        conn.Close();
    }
}

internal class OrderManager
{
    FileLogger fileLogger;
    DBLogger dbLogger;

    public OrderManager()
    {
        fileLogger = new FileLogger();
        dbLogger = new DBLogger();
    }

    public void Add(Order order)
    {
        // Add the order
        fileLogger.Log($"Order {order.Id} added.");
        dbLogger.Log($"Order {order.Id} added.");
    }

    public void Update(Order order)
    {
        // Update the order
        fileLogger.Log($"Order {order.Id} updated.");
        dbLogger.Log($"Order {order.Id} updated.");
    }

    public void Delete(Order order)
    {
        // Delete the order
        fileLogger.Log($"Order {order.Id} deleted.");
        dbLogger.Log($"Order {order.Id} deleted.");
    }
}

Örneğin bir sipariş yönetim sistemimiz olsun. Bu sistemde loglama işlemini iki farklı seviyede yapıyor olalım. Burada yaşayacağımız en büyük sorun, soyutlama (interface) olmadığı için OrderManager sınıfının loglama araçlarının somut detaylarına göbekten bağlı olmasıdır. Bu durum loglama kısmında yapılacak ufacık bir değişikliğin bile sistemi bozmasına neden olabilir. Öte yandan projenin ilerleyen fazlarında ADO.NET yerine Entity Framework’e geçiş yapılmak istediğimizde tüm DBLogger referanslarının düzenlenmesi gerekebilir.

Oysa ILogger şeklinde bir interface’e sahip olsaydık, bu interface’e loglama görevinin verildiği net bir şekilde anlardık. Loglama işleminin nasıl olacağına FileLogger ve DBLogger sınıfları içerisinde detaylandırabilirdik.

Dependency Inversion Nasıl Çalışır?

DIP’nin temel fikri; karmaşık bir mantık içeren üst seviye modülleri kolayca yeniden kullanılabilir olması ve alt seviyedeki modüllerde olabilecek değişikliklerden etkilenmemesidir. Bunu oluşturmak için üst ve alt seviye modülleri arasında bağımlılığı azaltacak bir soyutlama gerekir. Buna bağlı olarak DIP 2 aşamadan oluşur.

  1. Üst seviyede modüller alt seviye modüllere bağlı olmamalıdır; her ikisi de soyutlamalara bağlı olmalıdır.
  2. Soyutlamalar detaylara bağımlı olmamalıdır; detaylar soyutlamaya bağımlı olmalıdır.
internal class Order
{
    public Guid Id { get; set; }
}

internal interface ILogger
{
    void Log(string message);
}

internal class FileLogger : ILogger
{
    public void Log(string message)
    {
        StreamWriter writer = new StreamWriter("log.txt");
        writer.WriteLine(message);
        writer.Flush();
        writer.Close();
    }
}

internal class DBLogger : ILogger
{
    public void Log(string message)
    {
        SqlConnection conn = new SqlConnection();
        SqlCommand cmd = new SqlCommand("Insert", conn);
        conn.Open();
        cmd.ExecuteNonQuery();
        conn.Close();
    }
}

internal class OrderManager
{
    public ILogger _logger;

    public OrderManager(ILogger logger)
    {
        _logger = logger;
    }

    public void Add(Order order)
    {
        // Add the order
        _logger.Log($"Order {order.Id} added.");
    }

    public void Update(Order order)
    {
        // Update the order
        _logger.Log($"Order {order.Id} updated.");
    }

    public void Delete(Order order)
    {
        // Delete the order
        _logger.Log($"Order {order.Id} deleted.");
    }
}


OrderManager artık hangi loglama metodunun çağırıldığını ve o metodun nasıl çalıştığını bilmiyor sadece bir ILogger istiyor. Bu sayede hem manager sınıf içerisindeki tekrar eden kod yapısını azalttık, hem de ileride yeni bir loglama metodu eklenir veya varolan metot güncellenirse bu sınıf içerisinde bir değişiklik yapılmasını engelledik.

Bu yaklaşımla 3 önemli kazanım elde etmiş olduk.

  • Esneklik: İlerleyen fazlarda farklı bir loglama metodu eklemek istersek OrderManager’a dokunmamıza gerek kalmayacak. (Open/Closed)
  • Test Edilebilirlik: Unit test yazarken gerçek bir veri tabanı yerine kolayca mock bir logger entegre edebiliriz.
  • Güven: Tüm logger sınıflarımız ILogger interface’ine bağlı kaldığı için birbirlerinin yerine kullanılabilirler. (Liskov Substition)

SOLID serimin bu son yazısıyla, daha sürdürülebilir ve esnek kod temelleri atmanın yollarını incelemiş olduk. SOLID’in diğer kurallarını blog yazılarımdan okuyabilir, kullandığım örnekleri Github hesabımdan inceleyebilirsiniz. Bir sonraki yazıya kadar kendinize çok iyi bakın! 😊

SOLID Yazılım Prensipleri – Interface Segregation Principle

Herkese merhaba, SOLID yazılarıma dördüncü eleman Interface Segregation Prensibi ile devam ediyorum. Interface Segregation Principle (ISP), ‘bir sınıf kullanmadığı metotlara bağımlı olmaya zorlanmamalıdır’ der. Özetle; tek bir genel amaçlı interface (arayüz) yerine, istemcinin sadece ihtiyacı olan metotlara ve özelliklere erişebileceği interface (arayüz)’lere bölünmesidir. Bu sayede kullandığımız yapıya esneklik, clean code ve low coupling (düşük bağımlılık) kazandırmış oluruz.

Interface Segregation Nedir?

Bazen sistemler o kadar farklı seviyelerde birbirine bağımlı olur ki ufak bir değişiklik bütün kod içerisinde değişiklik yapılmasına neden olur. Interface segregation bu bağımlılığın azaltılmasına ve yapıya esneklik kazandırılmasına yardımcı olur.

Yukarıdaki örnekteki gibi bir dönüştürücümüz olduğunu varsayalım. Yeni tipte bir dönüştürücü eklememiz gerektiğinde, ana dönüştürücüde değişiklik yapmamız gerekecek. Bu değişiklik mevcuttaki dönüştürücüleri de etkileyeceği için yüksek bağımlılığa (coupling) neden olabilir. Bu yüzden dönüştürücülerin spesifik ve küçük parçalara bölünmüş olması kodun bakımını kolaylaştırdığı gibi okunabilir olmasını da sağlar.

Ayrıca kullandığımız bu yapıyı 3rd party bir uygulama olarak dışarıya verdiğimizi düşünelim. Telefonunu şarj etmek isteyen birisi neden yanında USB kablosu taşıdığını düşünebilir. Kullanıcı ihtiyacı olmayan bir özelliğe zorlanmamalı. Daha uç bir senaryoda bu şekilde bir kullanım güvenlik açığı da oluşturabilir.

public interface IChargeAdaptor
{
    void ChargePhone();
    void TransferData();
    void WirelessCharge();
}

public class Devices
{
    public class Iphone : IChargeAdaptor
    {
        public void ChargePhone()
        {
            Console.WriteLine("Charging iPhone...");
        }

        public void TransferData()
        {
            Console.WriteLine("Transferring data from iPhone...");
        }

        public void WirelessCharge()
        {
            Console.WriteLine("Wireless charging iPhone...");
        }
    }

    public class Mp3Player : IChargeAdaptor
    {
        public void ChargePhone()
        {
            Console.WriteLine("Charging MP3 Player...");
        }

        public void TransferData()
        {
            Console.WriteLine("Transferring data from MP3 Player...");
        }

        public void WirelessCharge()
        {
            //Bir sınıfın implement etmek zorunda olduğu ama desteklemediği metotlarda NotImplementedException fırlatması, tasarımın yanlış olduğuna işarettir.
            throw new NotImplementedException("This device does not support wireless charging.");
        }
    }
}

IChargeAdaptor interface’ini iki cihaza da sorunsuz bir şekilde implement ettik. Ancak Mp3Player sınıfı hiç kullanmadığı bir yeteneğe bağımlı hale geldi. İleride kablosuz şarj metodu üzerinde bir değişiklik yapılırsa, MP3 çalar kodunu da güncellememiz gerekecek. Daha iyi bir tasarımda bu özelliklerin hepsini bir yetenek olarak ele almalıyız.

public interface IChargeable
{
    void Charge();
}

public interface IDataTransferable
{
    void TransferData();
}

public interface IWirelessChargeable
{
    void WirelessCharge();
}

public class Iphone : IChargeable, IDataTransferable, IWirelessChargeable
{
    public void Charge()
    {
        Console.WriteLine("Charging iPhone...");
    }
    public void TransferData()
    {
        Console.WriteLine("Transferring data from iPhone...");
    }
    public void WirelessCharge()
    {
        Console.WriteLine("Wireless charging iPhone...");
    }
}
public class Mp3Player : IChargeable, IDataTransferable
{
    public void Charge()
    {
        Console.WriteLine("Charging MP3 Player...");
    }
    public void TransferData()
    {
        Console.WriteLine("Transferring data from MP3 Player...");
    }
}

Interface’i mantıklı parçalara bölerek, her cihazın ihtiyacı olan yeteneği kullanmasını sağlıyoruz. Bu sayede projemize esneklik, okunabilirlik ve bakım kolaylığı sağladık. Bir sonraki fazlarda hızlı şarj özelliği eklenmesi gerekirse IFastCharge interface’ini oluşturmamız yeterli olacaktır.

Interface ile kazandırdığımız özellikleri abstract class ile de kazandırabilirdik. C#’ta bir sınıf yalnızca tek bir base class’tan türeyebilir ancak birden fazla interface implement edebilir. OOP içerisinde interface yapısı katmanlar sağladığı için kodu basitleştirir. Aynı zamanda bağımlılıkların azaltılması için bariyer oluşturur.

Son olarak ISP’yi 3 özellik ile özetlememiz gerekirse;

  • Unused dependency’yi engellemek
  • Coupling’i azaltmak
  • Test edilebilirliği artırmak

Bu yazıdaki örneklere github repomdan erişim sağlayabilirsiniz. Bir sonraki yazımda Dependency Inversion Principle inceleyeceğim. Bir sonraki yazıya kadar kendinize çok iyi bakın! 😊

SOLID Yazılım Prensipleri – Liskov Substitution Principle

Herkese merhaba. Uzun bir aradan sonra SOLID ilkesinin üçüncü prensibi ile bu yazı dizisine devam ediyorum. Liskov Substitution prensibi özetle kodlarımızda herhangi bir değişiklik yapmaya gerek duyulmadan türetilen sınıfların yerine kullanabilmesini ele alır. Bu sayede codebase içerisinde sağlam bir inheritance yapısı oluşur. Yanlış soyutlamaların önüne geçildiği için geliştirme yapılırken kodun tahmin edilebilirliği artar. Ayrıca bir önceki yazımda bahsettiğim Open Closed ilkesinin de öncül şartıdır.

SOLID Yazılım Prensipleri – Open Closed Principle

Herkese merhaba. Bir önceki yazımda SOLID prensiplerinden ve Single Responsibility ilkesinden bahsetmiştim. Bu yazıda ise Open Closed ilkesini inceleyeceğiz. Bu ilke, uygulamanın mevcut kodlarını değiştirmeden sistemin özelliklerinin genişletilmesini önerir. Bu sayede yeni bir özellik eklendiğinde var olan kodda değişiklik yapılmadığı için bug oluşma riskini de azaltmış oluruz. Bu yazıdaki örneklere github repomdan erişim sağlayabilirsiniz.

SOLID Yazılım Prensipleri – Single Responsibility Principle

Herkese merhaba. Bu yazıda en temel yazılım geliştirme ilkesi olan SOLID’den bahsedeceğim. S.O.L.I.D aslında 5 temel ilkenin bir araya getirilerek kısaltılmış halidir. İlk olarak 2000 yılında Robert C. Martin tarafından “Design Principles and Design Pattern” makalesinde ortaya atılmıştır. Prensiplerin isim kısaltmasını ise Michael Feathers yapmıştır. Martin makalesinde başarılı bir yazılımın değişmesi ve gelişmesi gerektiğine değinmektedir. Ancak, yazılım değişip geliştikçe daha karmaşık bir hale gelir. Bu nedenle iyi tasarım ilkelerine sahip olmadan geliştirilen bir yazılım katı, kırılgan, hareketsiz ve zararlı bir hale dönüşür. Bu ilkeleri de sorunlu tasarım modelleriyle başa çıkmak için tasarlamıştır. SOLID yazılım ilkelerini kullanarak daha esnek ve geliştirmeye açık uygulamalar yazabiliriz. Bu yazıda kullandığım örnek kodlara buradaki github repomdan ulaşabilirsiniz.

C# Notlarim 25: Delegate ve Event Yapısı

Herkese merhaba. Bu yazıda kullanıcı etkileşimli programlama yaparken oldukça sık karşımıza çıkan delegate ve event konularını inceleyeceğiz. Bu ders için hazırladığım örnekleri Windows Forms projesi ile hazırladım. Daha öncesinde LINQ kullandıysanız Where() ya da Select() gibi metotların içerisine yazdığımız lambda expressionların da delegate yapısını kullandığını görmüşsünüzdür. İlerleyen yazılarda o konulara da değineceğiz. Daha fazla kafa karıştırmadan konuyu incelemeye başlayalım. Yazıda bulunan örneklere github hesabımdan ulaşabilirsiniz.

C# Notlarım 20: Soyutlama (Abstraction)

Herkese merhaba. Nesne yönelimli programlamanın son konusu olan soyutlama (abstraction) konusunu inceleyeceğiz. Soyutlama projedeki karmaşıklığı azaltmayı sağlar. Birden fazla sınıfa kalıtım verecek olan en temel nesneler soyutlama yöntemiyle oluştulur. Üretimde kullanılmayacak ama diğer tasarımların kalıtım aldığı bir temel kurulur. Ancak kendisi somut olarak proje içerisinde yer almaz. Örneklere github reposundan ulaşabilirsiniz.

C# Notlarım 19: Çok Biçimlilik (Polymorphism)

Herkese merhaba. Nesne yönelimli programlamanın ana başlıklarından biri olan polymorphism konusunu inceleyeceğiz. Bazen içeride farklı işlemler yapılsa bile aynı sonucu döndüren metotlara ihtiyacımız olur. Aynı kodu tekrar tekrar yazmak bir süre sonra projenin inanılmaz boyutlara ulaşmasına neden olur. Nesne yönelimli programa metodolojisinde de bu sorunu çözmek için çok biçimlilikten faydalanırız. Çok biçimlilik, temelde aynı tür olan metotların farklı davranması gerektiğinde kullanıldığımız bir yöntemdir. Örneklere github reposundan ulaşabilirsiniz.

C# Notlarım 17: Property ve Access Modifier

Herkese merhaba. Kapsülleme yazımdan sonra property ve access modifier (erişim belirleyiciler) konusunun daha iyi anlaşılması için bir örnek yazısı hazırladım. Yazının sonlarına doğru Type Initializer, Anonim Tipler (Anonymous Types) ve Index Property (Indexer) konularına da değineceğim. İlk konu kapsamda bir matematik kütüphanesi hazırlayacağım. Bu matematik kütüphanesinin içerisinde üs alma metodunun farklı erişim belirleyici versiyonları yer alacak. Bu projeyi github repomda bulabilirsiniz. Dilerseniz siz de kendi projenize entegre ederek bu kütüphaneyi kullanabilirsiniz. Property ve Access Modifiers (erişim belirleyiciler) yapılarının ne olduğunu hatırlamak için C# Notlarım 16: Kapsülleme (Encapsulation) yazımı okuyabilirsiniz.

Nesne Yönelimli Programlama Nedir? (Object Oriented Programming)

Herkese merhaba. 😊 Bugün yazılımcılığa girişin ilk adımı olarak nitelendirebileceğim Nesne Yönelimli Programlama (Object Oriented Programming) konusuna giriş yapacağım. Bu konu tamamen dil bağımsız bir konu olduğu için C# dışında istediğiniz bir programlama dilinde uyarlayabilirsiniz. Bu yazıya kadar anlatmaya çalıştığım yazılarımla bir ürün ortaya çıkartabilirsiniz. Örnek olarak bir konsol uygulaması yaptığımızı düşünelim. Yapılan işleri Program.cs dosyasının içerisinde metotlara bölerek modüler bir şekilde yazdık. Daha sonra bu ürünü başka birisi geliştirmek için aldı ve o da bir takım metotlar ekledi. En sonunda ürün doğru bir şekilde çalışıyor olsa bile ortaya spagetti kod çıkmış oldu. Bu projenin bakım maliyeti de artmış oldu çünkü 3. bir kişi geldiğinde belli bir kalıp yapı üzerinden ilerlenmediği için uyum sağlama süreci daha uzun olacaktır. Projemizi en başından OOP kullanarak yapmış olsaydık hem spagetti koddan kurtulmuş hem de bakım maliyetini azaltmış olacaktık. Bu işin biraz hikaye kısmı şimdi biraz daha konuya girerek daha yakından bakalım. 😊

Powered by WordPress & Theme by Anders Norén