class Servis { void IslemYap(int p) { if(p == 1) Console.WriteLine("1. hal için birtakım işlemler..."); else if(p == 2) Console.WriteLine("2. hal için birtakım işlemler..."); } } class Client { static void Main(string[] args) { Servis s = new Servis(); s.IslemYap(2); } } |
abstract class Servis { abstract void IslemYap() { } } class Servis1 : Servis { void IslemYap() { Console.WriteLine("1. hal için birtakım işlemler..."); } } class Servis2 : Servis { void IslemYap() { Console.WriteLine("2. hal için birtakım işlemler..."); } } class Client { static void Main(string[] args) { Servis s = new Servis2(); s.IslemYap(); } } |
class Servis3 : Servis { void IslemYap() { Console.WriteLine("3. hal için birtakım işlemler..."); } } |
Şimdi de nesneye yönelik tasarımın en önemli ikinci prensibini ele alalım. DIP - Dependency Inversion Principle (Bağımlılığı Tersine Çevirme Prensibi) Aslında DIP (Dependency Inversion Principle yani Bağımlılığı Tersine Çevirme Prensibi), OCPnin (Açık Kapalı Prensibinin) direk bir sonucudur. DIP prensibine göre, ileride değişmesi muhtemel somut sınıflara direkt erişmek yerine, bu sınıflara arayüz veya soyut sınıflar üzerinden erişmemiz gerekir. Bu yolla somut sınıflara ve bunların eriştiği tüm diğer somut sınıflara olan bağımlılığı yokedebiliriz. DIP prensibine aykırı yapılar, birbirine bağımlı sınıflar doğurur, ki bu da temelde OCP, yani değişmeden genişleyebilme prensibine terstir. Çünkü bir sınıfta yapılacak olan değişiklik, diğer tüm sınıflarda değişiklik yapmayı gerektirebilir. Zaten OCPyi anlatırken geliştirdiğimiz örneğin son hali de DIPa uygundu, çünkü hatırlarsanız orada geliştirdiğimiz Servis1, Servis2, ... sınıflarına bir soyut sınıf (arayüz de olabilirdi) aracılığıyla erişerek ServisN sınıflarına bağımlılığı yokederek varolan kodun değişmeden genişleyebilmesini sağlamıştık. Şimdi DIPın isminin açılımında da yeralan, bağımlılıkları tersine çevirmekten tam olarak ne kastedildiğini anlayabilmek için, önceki OCP örneğimizden daha kapsamlı bir örnek verelim.
Örneğin, A sınıfının B1 ve B2 sınıflarına, B1 sınıfının C1, C2 sınıflarına, B2 sınıfının C1, C3 ve C4 sınıflarına erişim yaptığı (yani bu sınıfları kullandığı) aşağıdaki örneği ele alalım:
class A { void IslemYap() { Console.WriteLine("A sınıfı için birtakım işlemler..."); } void BaskaIslemYap(B1 b1, B2 b2) { Console.WriteLine("A sınıfı için başka birtakım işlemler..."); b1.IslemYap(); b2.IslemYap(); } } class B1 { void IslemYap() { Console.WriteLine("B1 sınıfı için birtakım işlemler..."); } void BaskaIslemYap(C1 c1, C2 c2) { Console.WriteLine("B1 sınıfı için başka birtakım işlemler..."); c1.IslemYap(); c2.IslemYap(); } } class B2 { void IslemYap() { Console.WriteLine("B2 sınıfı için birtakım işlemler..."); } void BaskaIslemYap(C1 c1, C3 c3, C4 c4) { Console.WriteLine("B2 sınıfı için başka birtakım işlemler..."); c1.IslemYap(); c3.IslemYap(); c4.IslemYap(); } } class C1 { void IslemYap() { Console.WriteLine("C1 sınıfı için birtakım işlemler..."); } } class C2 { void IslemYap() { Console.WriteLine("C2 sınıfı için birtakım işlemler..."); } } class C3 { void IslemYap() { Console.WriteLine("C3 sınıfı için birtakım işlemler..."); } } class C4 { void IslemYap() { Console.WriteLine("C4 sınıfı için birtakım işlemler..."); } } |
->C2
->B2->C1
->C3
->C4 Bu gösterimde -> işareti okun gittiği yöne olan bağımlılığı gösterir, yani A sınıfı, B1, B2ye, B1 sınıfı C1 ve C2ye, B2 sınıfı C1, C3 ve C4e, yani dolaylı yoldan A sınıfı malesef B1, B2, C1, C2, C3, C4 sınıflarının hepsine bağımlı haldedir. Bu sınıflardan herhangi birinde yapılacak bir değişiklik direkt olarak Ayı etkiler ve muhtemelen A sınıfının değiştirilmesini veya yeniden derlenmesini gerektirir. Görüldüğü gibi, A sınıfının bağımlılıkları bir sarmaşığın kolları gibi, erişebildiği tüm sistemi kaplıyor.
Bu durum, prosedürel dillerde prosedür/fonksiyonların birbirlerini çağırdığı ve en tepedeki prosedür/fonksiyonun(muhtemelen main() fonksiyonunun), daha aşağı seviyede çağırılan tüm diğer prosedür/fonksiyonlara bağımlı hale geldiği durum ile çok benzerdir. Peki bu bağımlılık zinciri (bağımlılık ağacı de denebilir) ve burada herhangi bir sınıfın tetikleyeceği bir zincirleme kazayla nasıl başa çıkabiliriz? Bunun cevabı, erişen sınıf ile erişilen sınıf arasına soyut sınıf/arayüz yerleştirerek, somut sınıflar üzerine varolan bağımlılığın, soyut sınıf/arayüzlere dönüştürülmesidir.
Yani, önceki örneğimizdeki şu bağımlılıklar: A->B1->C1
->C2
->B2->C1
->C3
->C4 Şuna dönüşür: A->IB1<-B1->IC1<-C1
->IC2<-C2
->IB2<-B2->IC1<-C1
->IC3<-C3
->IC4<-C4 Bu yeni halinde A sınıfı sadece IB1 ve IB2 arayüzlerine bağımlıdır, yani tüm B1, B2, C1, C2, C3, C4 sınıflarına olan bağımlılığı ortadan kalkmıştır. Yeni durumda B1 sınıfıda yalnızca implemente ettiği IB1 arayüzüne ve erişim yaptığı IC1 ve IC2 arayüzlerine bağımlıdır. Gördüğümüz gibi tepeden aşağıya tüm bağımlılık tersyüz edilmiş, her somut sınıf sadece onu çevreleyen arayüzlere bağımlı hale gelmiştir, ve arayüzler değişmediği sürece (ki arayüzlerin değişmesine genelde ihtiyaç da olmaz, değişikliğe implementasyonlarda ihtiyaç duyulur) bu bağımlılıkların sisteme hiçbir etkisi yoktur.
Yeni haliyle kodumuzu yazacak olursak:
class A { void IslemYap() { Console.WriteLine("A sınıfı için birtakım işlemler..."); } void BaskaIslemYap(IB1 b1, IB2 b2) { Console.WriteLine("A sınıfı için başka birtakım işlemler..."); b1.IslemYap(); b2.IslemYap(); } } interface IB1 { void IslemYap(); void BaskaIslemYap(IC1 c1, IC2 c2); } class B1 : IB1 { void IslemYap() { Console.WriteLine("B1 sınıfı için birtakım işlemler..."); } void BaskaIslemYap(IC1 c1, IC2 c2) { Console.WriteLine("B1 sınıfı için başka birtakım işlemler..."); c1.IslemYap(); c2.IslemYap(); } } interface IB2 { void IslemYap(); void BaskaIslemYap(IC1 c1, IC3 c3, IC4 c4); } class B2 : IB2 { void IslemYap() { Console.WriteLine("B2 sınıfı için birtakım işlemler..."); } void BaskaIslemYap(IC1 c1, IC3 c3, IC4 c4) { Console.WriteLine("B2 sınıfı için başka birtakım işlemler..."); c1.IslemYap(); c3.IslemYap(); c4.IslemYap(); } } interface IC1 { void IslemYap(); } class C1 : IC1 { void IslemYap() { Console.WriteLine("C1 sınıfı için birtakım işlemler..."); } } interface IC2 { void IslemYap(); } class C2 : IC2 { void IslemYap() { Console.WriteLine("C2 sınıfı için birtakım işlemler..."); } } interface IC3 { void IslemYap(); } class C3 : IC3 { void IslemYap() { Console.WriteLine("C3 sınıfı için birtakım işlemler..."); } } interface IC4 { void IslemYap(); } class C4 : IC4 { void IslemYap() { Console.WriteLine("C4 sınıfı için birtakım işlemler..."); } } |
Adapter ve Factory Method Tasarım Kalıpları ile Bağımlılığı Yoketmek Yukarıdaki son örnekte gördüğümüz B1 ve B2 sınıfları, aslında bir anlamda ICn arayüzlerini IBn arayüzlerine çeviren birer adaptör gibi görülebilir. C1, C2, C3, C4 sınıflarının ve IC1, IC2, IC3, IC4 arayüzlerinin bizim kontrolümüz dışında bir kütüphane olduğunu ve arasıra tasarımının değiştiğini düşünelim. Bu kötü bir kütüphane tasarımı veya zorunluluk gereği, örneğin yeni ve gelişimini tamamlamamış bir teknoloji veya bir donanıma bağımlılık nedeniyle olabilir. Zaman içinde değişkenlik gösteren bu C katmanını bizim altyapımıza uyarlamak için B katmanındaki sınıflar gibi sınıflara ihtiyacımız olur, bu kullanım sık sık karşımıza çıktığı için buna literatürde adapter deseninin nesne biçimi (adapter pattern - object form) denmiştir. Adapter deseni iki biçimde karşımıza çıkar, bu örnekteki nesne biçiminin dışında, bir de sınıf biçimi vardır. Adına nesne biçimi denen kullanımda, B sınıfı IB arayüzüzünü implemente eder ve C sınıfını (veya IC arayüzünü veya soyut sınıfını) kullanır. Sınıf biçimindeki kullanımda ise, B sınıfı hem IB arayüzüzünü hem de IC arayüzünü implemente ederek IC arayüzünü veya soyut sınıfını kullanır.
Önemli bir ikinci nokta da, dikkat ederseniz örneğimizdeki A, B, C sınıflarımızda hiç bir sınıftan nesne yaratmadık, nesneler metodlarımıza parametre olarak geçildi. Eninde sonunda bu altyapıyı kullanacak bir sınıfta, parametre olarak aktarılacak olan bu sınıflardan nesenelerin yaratılması gerekiyor, ancak nesneleri new operatörüyle yaratılırsak, yarattığımız somut sınıfa bağımlılık ortaya çıkar.
Yani, yazımızın ilk başlarında OCP için verdiğimiz örneğe benzeyen aşağıdaki örneği incelersek:
interface IServis { void IslemYap(); } class Servis1 : IServis { void IslemYap() { Console.WriteLine("Servis1 için birtakım işlemler..."); } } class Servis2 : IServis { void IslemYap() { Console.WriteLine("Servis2 için birtakım işlemler..."); } } class Client { static void Main(string[] args) { IServis s = new Servis2(); s.IslemYap(); } } |
Örneğin yukarıdaki kodu şöyle yazalım:
interface IServis { void IslemYap(); } class Servis1 : IServis { void IslemYap() { Console.WriteLine("Servis1 için birtakım işlemler..."); } } class Servis2 : IServis { void IslemYap() { Console.WriteLine("Servis2 için birtakım işlemler..."); } } class ServisFactory { IServis Servis1Yarat() { return new Servis1(); } IServis Servis2Yarat() { return new Servis2(); } } class Client { static void Main(string[] args) { ServisFactory sf = new ServisFactory(); IServis s = sf.Servis2Yarat(); s.IslemYap(); } } |
Özellikle, başkalarının kullanımına yönelik sınıf kütüphaneleri tasarlayan veya böyle kütüphaneleri kullanan herkesin kafasında, kullanılan yapılara dair oluşabilecek soru işaretlerinin, bu yazı ile azalacağını umuyor, iyi çalışmalar diliyorum. Daha kapsamlı bilgi edinmek için,http://www.objectmentor.com adresinden Robert Martinin bu konulardaki makalelerine ulaşabilirsiniz.http://www.csharpnedir.com/articles/read/?id=812&title=OOP'nin%20Temel%20Prensipleri%20ve%20Factory%20Method%20ile%20Adaptor%20Tasar%C4%B1m%20Desenleri
Hiç yorum yok:
Yorum Gönder