Burak Selim Senyurt(MVP)
Matematik Mühendisi bir .NET Severin Yazıları...

Tasarım Prensipleri - Interface Segregation

Perşembe, 2 Temmuz 2009 16:16 by bsenyurt

Merhaba Arkadaşlar,

Bir süredir pek çok nesne yönelimli yazılım disiplininde önem arz eden ve kullanılan Tasarım Prensiplerini(Design Principles) incelemeye ve öğrendiklerimi sizlere aktarmaya çalışıyorum. Şu ana kadar pek çok prensibi inceledik ve kısaltmalarına tanık olduk.

  • LCP (Loose Coupling Principle)
  • OCP (Open Closed Principle)
  • SRP (Single Responsibility Principle)
  • LSP (Liskov Substituation Principle)
  • DIP (Dependency Inversion Principle)

Elbetteki önemli olan, kısaltmalarının karşılıklarını bilmek değil Wink , söz konusu prensiplerin farkına vararak yazılım geliştirmek yada geliştirilen yazılım içerisinde bu prensiplerin uygulanabileceği, uygulanması gereken yerleri tespit edebilmektir. Bu hususları dikkate alarak ara sıra tasarım prensiplerini tekrar etmeye özen göstererekten, yeni tasarım prensibini incelemeye başlayabiliriz. Interface Segregation Principle(ISP)

Çok hızlı bir giriş olacak ama konuya aşağıdaki sınıf diagramında görülen tipleri göz önüne alarak başlayalım.(Her zamanki gibi ilkenin özlü sözünü kavrayabilmek için örnekle başlamakta yarar olduğu kanısındayım)

Kod tarafından baktığımızda ise;

 interface IComponent
    {
        Guid ComponentId { get; }

        void Initialize();
        void Draw();
        void Render();
    }

    public class WinButton
    : IComponent
    {
        #region IComponent Members

        public Guid ComponentId
        {
            get { return Guid.NewGuid(); }
        }

        public void Initialize()
        {
            Console.WriteLine("Windows Button başlangıç işlemleri");
        }

        public void Draw()
        {
            Console.WriteLine("Ekrana çizdirme işlemleri");
        }

        public void Render()
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    class WebButton
        :IComponent
    {
        #region IComponent Members

        public Guid ComponentId
        {
            get { return Guid.NewGuid(); }
        }

        public void Initialize()
        {
            Console.WriteLine("Web Button başlangıç işlemleri");
        }

        public void Draw()
        {
            throw new NotImplementedException();
        }

        public void Render()
        {
            Console.WriteLine("HTML Render işlemleri");
        }

        #endregion
    }

Aslında böylesine kötü bir tasarım ile konuya başlamak istemezdim ancak ISP ilkesinin neyi öğütlediğini bilmek adına bu yeterli bir yaklaşımdır. Hedefte bir sistemin parçası olan görsel bileşenlerin uygulaması gereken kuralları bildiren IComponent isimli arayüz tipi bulunmaktadır. Görsel bileşenler olarak örneğimizde, Windows ve Web tabanlı uygulamalardaki Button kontrolleri göz önüne alınmaktadır. Ancak bir windows kontrolünün temel olarak ekrana çizdirilmesi ile, bir Web kontrolünün istemci tarafına HTML içeriği olarak Render edilmesi iki farklı ve ap ayrı fonksiyonelliktir. Dolayısıyla ISP ilkesine bu tespitten itibaren ters düşülmeye başlanmaktadır. Nitekim, IComponent arayüzünü uygulayan WinButton sınıfı içerisinde yer alan Render metodunun kesin olarak implemente edilmemesi gerekir. Hatta implemente edilmesi anlamsızdır. Bu nedenle çözüm olarak içerisinden NotImplementedException istisnasının fırlatıldığını görmekteyiz. Diğer taraftan benzer durum WebButton bileşeni içinde geçerlidir. Öyleki Web arayüzü için Render işlemi önemli iken, Draw isimli fonksiyonelliğin gerçekleştirilmemesi gerekir. Ayrıca IComponent arayüzüne yeni eklentiler yapılmak istendiğinde, Liskov Substitution ilkesine ters düşebilecek durumlarında oluşması söz konusudur. (Bknz : Tasarım Prensipleri : Liskov Substitution Wink ) Peki bu sorunlar bize neyi göstermektedir?

IComponent arayüzünü uygulayan tipler, aslında kendi içlerinde kullanmayacakları fonksiyonellikleri uyarlamak zorunda kalmışlardır. NotImplementedException ile istisna fırlatılmış olsa bile... İşte Interface Segregation ilkesi, bir istemcinin kullanmayacağı arayüz fonksiyonelliklerini hiç bir şekilde uygulamaması gerektiğini belirtmektedir. Buda tahmin edileceği üzere söz konusu fonksiyonellikleri farklı arayüzlere bölerek mümkün olabilir. Yani, yukarıda tasarlamış olduğumuz sistemi aşağıdaki hale getirerek ISP ilkesine sadık kalmayı başarabiliriz.

Kod içeriğimizi ise şu şekilde güncellemeliyiz;

interface IComponent
    {
        Guid ComponentId { get; }

        void Initialize();
    }
    interface IWebComponent
    {
        void Render();
    }
    interface IWinComponent
    {
        void Draw();
    }

    public class WinButton
    : IComponent,IWinComponent
    {
        #region IComponent Members

        public Guid ComponentId
        {
            get { return Guid.NewGuid(); }
        }

        public void Initialize()
        {
            Console.WriteLine("Windows Button başlangıç işlemleri");
        }

        #endregion

        #region IWinComponent Members

        public void Draw()
        {
            Console.WriteLine("Ekrana çizdirme işlemleri");
        }

        #endregion
    }

    class WebButton
        : IComponent,IWebComponent
    {
        #region IComponent Members

        public Guid ComponentId
        {
            get { return Guid.NewGuid(); }
        }

        public void Initialize()
        {
            Console.WriteLine("Web Button başlangıç işlemleri");
        }

        #endregion

        #region IWebComponent Members

        public void Render()
        {
            Console.WriteLine("HTML Render işlemleri");
        }

        #endregion
    }

Görüldüğü gibi Web tarafını ilgilendiren Render fonksiyonu IWebComponent isimli arayüzde, Windows tarafını ilgilendiren Draw metodu ise IWinComponent arayüzü içerisinde ele alınmıştır. Artık, ortak olan üyelerin IComponent, Web tarafını ilgilendirenlerin IWebComponent ve Windows tarafını ilgilendirenlerinde IWinComponent içerisinde toplanması sağlanarak ISP ilkesinin korunması sağlanabilir. Hatta söz konusu senaryoda IWebComponent ve IWinComponent arayüzlerinin IComponent arayüzünden türemesi dahi düşünülebilir.

Bu ilke ile ilişkili olaraktan internet ve basılı kaynaklarda çok çok güzel örnekler yer almaktadır. Örneğin Object Oriented Design isimli sitede bir şirketin çalışanları göz önüne alınmıştır. Çalışanlar yemek yiyen insanlar ve yemek yemeyip sürekli çalışan Robotlardan oluşmaktadır. Laughing 

Ancak ilk etapta tasarlanan herşeyi içinde barındıran şişman arayüz(Fat Interface), yemek yeme fonksiyonunu barındırdığı için, Robot' larında gerekmediği halde söz konusu işlevselliği uygulaması zorunlu olmuştur ki bu andan itibaren ISP ilkesine ters bir durum oluşmaktadır. Bu yazıyıda fikir vermesi açısından incelemenizi tavsiye ederim.

Böylece geldik bir ilkenin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

ISP.rar (31,41 kb)

Tasarım Prensipleri - Dependency Inversion

Salı, 30 Haziran 2009 22:45 by bsenyurt

Merhaba Arkadaşlar,

Bu yazımızda Dependency Inversion isimli tasarım prensibinden bahsediyor olacağız. Bu prensip kabaca, alt sınıflar ve üst sınıflar arasında kuvvetli bir bağ olmamasını önermektedir. Bunun en büyük gerekçesi, alt sınıflarda olabilecek sık değişiklerin, üst sınıfında değişmesine neden olabilecek olmasıdır ki bu hızla değişen yazılım ihtiyaçlarında sorunlara neden olmaktadır. Buna birde yeni alt tipler ile genişletilebilme olasılıklarınıda eklersek, üst ve alt sınıflar arasındaki bağımlılıkların ortadan kaldırılmasının (bağımsızlık olarak düşünmek istesemde, bağımlılığın tersine çevrilmesi olarak bilmek zorundayız Wink ) aslında ne kadar önemli olduğu anlaşılabilir. Durumu daha iyi kavrayabilmek adına basit bir örnek üzerinden ilerlemek çok daha doğrudur. Öncelikli olarak aşağıdaki sınıf diagramı ve kod içeriğinde görülen örnek Console uygulamasını göz önüne alalım.

using System;

namespace Problem
{
    // Low Level Class
    class XmlContent
    {
        public string Content { get; set; }

        public void Parse()
        {
            Console.WriteLine("parsing işlemi");
        }
    }

    // High Level Class
    class Parser
    {
        XmlContent xContent { get; set; }

        public Parser(XmlContent xmlContent)
        {
            xContent = xmlContent;
        }

        public void DoWork()
        {
            // Kompleks işlemler yapıldığını varsayabiliriz.
            xContent.Parse();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Parser prsr = new Parser(new XmlContent { Content = "<Kitaplar><Kitap><Ad>C#</Ad></Kitap><Kitap><Ad>VB.Net</Ad></Kitap></Kitaplar>" });
            prsr.DoWork();
        }
    }
}

Bu örnekte yer alan Parser sınıfı, kendi içerisinde tanımlı olan XmlContent tipine bağımlıdır. XmlContent tipi temel olarak bir Xml içeriğini ifade etmek üzere tasarlanmış olup, ayrıştırma işlemi için özel bir metoda sahiptir. Diğer yandan Parser sınıfı içerisinde yer alan DoWork metodu, XmlContent tipinin Parse metodunu çağırmaktadır. DoWork metodu içerisinde aslında, XmlContent tipi ile ilişkili olaraktan farklı ve hatta karmaşık bazı iş kurallarının uygulandığı farz edilebilir. Ancak alt sınıf(Low Level Class) olarak kabul edebileceğimiz XmlContent tipinin yapısında olabilecek değişikliker tahmin edileceği üzere üst sınıfı(High Level Class) doğrudan etkileyecektir. Özellike üst sınıfta kod değişikliğine gidilmesi gerekecektir. Diğer yandan senaryoya yeni bir tip eklenmek istendiğinde de Parser tipinin bozulması ve içeriğinin değiştirilmesi gerekecektir. Yani genişletilebilirlik söz konusu olduğunda,  üst sınıfta kod meydana gelecek kod değişimi kaçınılmaz olacaktır. Şimdi senaryomuzu genişletip sorunu ortaya koymaya çalışalım. Bu amaçla var olan sisteme, yeni bir alt sınıfın eklendiğini farzedelim. Örneğin Parser sınıfının, Json formatındaki dökümanları ifade eden bir sınıf ilede çalışması istenebilir. Bu durumda 3 temel sorundan bahsedebiliriz;

  • Yeni eklenen tip nedeniyle Parser sınıfının kendisine müdahele edilmesi ve DoWork metodunun kod içeriğinin değiştirilmesi gerekecektir.
  • DoWork metodu veya Parser sınıfı içerisindeki bazı fonksiyonelliklerin işleyişi olumsuz yönde etkilenebilir.
  • Her ne olursa olsun, Unit Test işlemlerinin tekrardan oluşturulması ve yapılması gerekecektir.

Şimdi biraz durup bu sorunların üstesinden nasıl gelebileceğinizi düşünmenizini öneririm. Hatta düşünürken bir kahve arası verebilir ve çevrenizde bu konu ile ilgili kişiler varsa onlarla durumu tarışabilirsiniz.Wink

Dependency Inversion prensibi, yukarıda bahsediğimiz tipte bir senaryonun oluşmasını engellemek için, üst sınıf ile alt sınıf arasına bir soyutlayıcının(abstract class veya interface) konulmasını belirtir. Yani üst sınıf ve alt sınıf arasında bir interface veya abstract tipin kullanılması önerilmektedir. İşte örnek senaryomuzun Dependency Inversion prensibine uygun olarak geliştirilen son hali.

using System;

namespace Solution
{
    // Abstraction Layer
    interface IContent
    {
        string Content { get; set; }

        void Parse();
    }

    // Low Level Class
    class XmlContent
        :IContent
    {
        #region IContent Members

        public string Content { get; set; }

        public void Parse()
        {
            Console.WriteLine("Xml parsing işlemi");
        }

        #endregion
    }

    // Low Level Class
    class JsonContent
        : IContent
    {
        #region IContent Members

        public string Content { get; set; }

        public void Parse()
        {
            Console.WriteLine("Json parsing işlemi");
        }

        #endregion
    }

    // High Level Class
    class Parser
    {
        IContent content { get; set; }

        public Parser(IContent cntnt)
        {
            content = cntnt;
        }

        public void DoWork()
        {
            // Kompleks işlemler yapıldığını varsayabiliriz.
            content.Parse();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Parser prsr = new Parser(new XmlContent { Content = "" });
            prsr.DoWork();

            prsr = new Parser(new JsonContent { Content = "" });
            prsr.DoWork();
        }
    }
}


Görüldüğü gibi XmlContent ve JsonContent isimli tiplerimiz IContent arayüzünden türetilmiştir. Buna bağlı olarakta Parser tipi ile IContent arayüzü arasında bir bağlantı oluşturulmuştır. Bir başka deyişle, Parser tipinden XmlContent veya JsonContent tiplerine doğrudan bir bağ mevcut değildir. Artık alt sınıflar olan XmlContent ve JsonContent içerisinde istenilen değişiklikler yapılabilir. Söz gelimi Parse metodlarının iş mantığında değişimler olabilir yada tipler içerisine yeni üyeler eklenebilir. Sonuç olarak; arayüzün(veya soyut tipin) yapısının değiştirilmediği düşünüldüğünde, üst sınıfın, alt sınıflardaki olası değişiklier ve sistemdeki genişletmelere karşı bağımsız olması garanti altına alınmış olur.

Bir sonraki yazımızda görüşünceye dek hepinize mutlu günler dilerim.

DIP.rar (40,27 kb)

Tasarım Prensipleri - Liskov Substitution

Salı, 30 Haziran 2009 00:57 by bsenyurt

Merhaba Arkadaşlar,

Bu günkü blog yazımızın kahramanı Barbara Liskov(http://en.wikipedia.org/wiki/Barbara_Liskov). Ve tahmin edeceğiniz üzere konumuz tasarım prensipleri içerisinde uygulanan disiplinlerden birisi olan Liskov Substitution(LSP) ilkesi. Bu ilke üst sınıf(Base Class) ve alt sınıf(Sub Class) arasındaki ilişkinin rol aldığı bir prensip olarak göz önüne alınabilir aslında. İlkenin özet cümlesini söylemeden önce basit bir örnek üzerinden ilerlemenin daha iyi olacağı kanısındayım. Nitekim özet cümleyi okuduğunuzda kafanızın karışmamasını garanti edemeyeceğim. Undecided

Örnek Console uygulamamızda Document isimli bir abstract sınıfımız mevcuttur. Bu sınıf Pdf, Xps gibi dökümanların ortak özellikleri ile uygulaması gereken kuralları tanımlamaktadır. Örneğin dökümanın network üzerinde bir yere gönderilmesi veya printer üzerinden yazdırılması bu anlamda zorunlu fonksiyonellikler olarak düşünülebilir. Diğer yandan, DocumentManager isimli bir başka sınıfta, Document sınıfına bağımlı olan bir tip olarak karşımıza çıkmaktadır. Bu tip kendi içerisinde, Document tipinden türeyen nesne örnekleri üzerinde ortak işlemlerin yapılmasını sağlamaktadır. Söz gelimi birden fazla Document tipinin yazdırılması veya gönderilmesi bu ortak operasyonlar olarak düşünülebilir. Burada işin önemli kısımlarından birisi, Xps, Pdf gibi sınıfların ve sonradan sisteme eklenecek Document türevli tiplerin, DocumentManager tarafında ortaklaşa kullanılabilecek olmasıdır. Dilerseniz örneğimize bakalım.

using System;

namespace Problem
{
    // Base Class
    abstract class Document
    {
        public int PageCount { get; set; }
        public string Name { get; set; }
        public string Owner { get; set; }

        public abstract void Send();
    }

    // Sub Class
    class Pdf
        : Document
    {
        public override void Send()
        {
            Console.WriteLine("PDF gönderme işlemi\n\tDöküman {0}\n\tSayfa Sayısı -> {1}\n\tDöküman sahibi -> {2}", Name, PageCount.ToString(), Owner);
        }
    }

    // Sub Class
    class Xps
        : Document
    {
        public override void Send()
        {
            Console.WriteLine("XPS gönderme işlemi\n\tDöküman {0}\n\tSayfa Sayısı -> {1}\n\tDöküman sahibi -> {2}", Name, PageCount.ToString(), Owner);
        }
    }

    // Client
    static class DocumentManager
    {
        public static void SendAll(Document[] dcmnts)
        {
            foreach (Document dcmnt in dcmnts)
            {
                dcmnt.Send();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Document[] dcmnts ={
                                     new Pdf{ Name="eXtreme Programming.pdf", PageCount=800, Owner="The Good"},
                                     new Xps{Name="Programming with C#.xps", PageCount=350, Owner="The Bad"},
                                     new Xps{Name="Design Patterns.xps",PageCount=890,Owner="The Ugly"}
                                };

            DocumentManager.SendAll(dcmnts);           
        }
    }
}

Console uygulaması Pdf ve Xps sınıflarına ait nesne örneklerinin toplandığı Document tipinden bir diziyi oluşturmakta ve bunu DocumentManager sınıfındaki SendAll metoduna göndermektedir. Xps ve Pdf tipleri, Document sınıfından türemiştir ve Send metodunun abstract olarak tanımlanması nedeniylede, söz konusu fonksiyonu kendi içlerinde ezmişlerdir(Override). Bu nedenle Send metodu içerisindeki döngüde, Document tipinden nesne örnekleri ele alınmasına rağmen, Send metodu çalışma zamanında yeri geldiğinde Xps için, yeri geldiğinde de Pdf için çalıştırılacaktır. İşte nesne yönelimli olmanın güzel noktalarından birisi.Wink Uygulamayı çalıştırdığımızda aşağıdaki sonuçlar alırız.

Ancak Liskov Substitution ilkesinin savunduğu da bir kural vardır. Bu kuralı göstermek için senaryomuzu şu andan itibaren biraz değiştiriyor olacağız. İlk olarak uygulamaya Document sınıfından türeyen CSharp isimli yeni bir tip eklediğimizi düşünelim. Bu tip cs uzantılı kod dosyalarını ifade etmektedir.

Yeni senaryomuza göre CSharp isimli tiplerin temsil ettiği dökümanların(cs uzantılı kod dosyaları olarak düşünebiliriz) herhangibir sebeple Send işlemine tabi tutulmaması gerekmektedir. Oysaki Send metodu Document tipi içerisinde abstract olarak tanımlandığından, CSharp tipi içerisinde de ezilmesi gerekmektedir. Peki ya sistemde Send işleminin yaptırtılması istenmiyorsa...

İki seçeneğimiz olabilir. Bunlardan birincisi CSharp tipi içerisindeki Send metodundan üst katmana doğru bir istisna(Exception) fırlatmaktadır. Belkide geliştirici tarafından yazılmış bir istisna tipide söz konusu olabilir. Diğer bir alternatif ise Client tipi içerisindeki(DocumentManager sınıfı), SendAll metodunda tip kontrolü yapmaktır. Buna göre gelen tip CSharp ise Send işleminin icra edilmemesi sağlanabilir. Bir başka deyişle kodlarımızı aşağıdak gibi güncelleştirebiliriz.

using System;

namespace Problem
{
    // Base Class
    abstract class Document
    {
        public int PageCount { get; set; }
        public string Name { get; set; }
        public string Owner { get; set; }

        public abstract void Send();
    }

    // Sub Class
    class Pdf
        : Document
    {
        public override void Send()
        {
            Console.WriteLine("PDF gönderme işlemi\n\tDöküman {0}\n\tSayfa Sayısı -> {1}\n\tDöküman sahibi -> {2}", Name, PageCount.ToString(), Owner);
        }
    }

    // Sub Class
    class Xps
        : Document
    {
        public override void Send()
        {
            Console.WriteLine("XPS gönderme işlemi\n\tDöküman {0}\n\tSayfa Sayısı -> {1}\n\tDöküman sahibi -> {2}", Name, PageCount.ToString(), Owner);
        }
    }

     // Sub Class
    class CSharp
        : Document
    {
        public override void Send()
        {
            // Seçeneklerden birisi exception fırlatılması olabilir. Bu durumda exception' ın üst katmanlarda ele alınması(catch) gerekir.
            throw new Exception("Bu döküman için gönderme işlemi yapılamaz");
        }
    }

    // Client
    static class DocumentManager
    {
        public static void SendAll(Document[] dcmnts)
        {
            foreach (Document dcmnt in dcmnts)
            {
                // Bir diğer seçenek tip kontrolü olabilir. Tipe göre söz konusu alt sınıf operasyonunun gerçekleştirilmemesi sağlanabilir.
                if(dcmnt is CSharp)
                    continue;
                else
                    dcmnt.Send();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Document[] dcmnts ={
                                     new Pdf{ Name="eXtreme Programming.pdf", PageCount=800, Owner="The Good"},
                                     new Xps{Name="Programming with C#.xps", PageCount=350, Owner="The Bad"},
                                     new CSharp{Name="ProductEntity.cs", PageCount=15, Owner="SourceSafe"},
                                     new Xps{Name="Design Patterns.xps",PageCount=890,Owner="The Ugly"}
                                };

            DocumentManager.SendAll(dcmnts);           
        }
    }
}

Örnekte SendAll metodu içerisinde tip kontrolü yapılarak söz konusu senaryonun gerçeklenmemesi sağlanmıştır. Hatta uygulama çalıştırıldığında bir önceki ile aynı sonuçlar alınacaktır. Ancak burada ilk başka Open Closed ilkesine ters düşen bir durum yaşanmaktadır. Öyleki, sisteme yeni bir tip eklendiğinde, DocumentManager içerisindede değişiklik yapılması zorunludur. Zaten, Open Closed prensibine uymayan durumlar Liskov Substitution ilkesininde bozulduğu anlamına gelmektedir. Liskov Substitution ilkesi bize şunu söylemektedir; üst sınıf ile alt sınıf nesne örnekleri yer değiştirdiklerinde, üst sınıf kullanıcısı alt sınıfın operasyonlarına erişmeye devam edebilmelidir. Oysaki son senaryoda, CSharp alt sınıfındaki operasyona erişilirken özel bir işlem yapılması gerekmiştir(tip kontrolü veya istisna yakalama işlemi). Bu örnekte LSP ilkesine aykırı olan durumu ortadan kaldırmak için yapılması gereken tek şey vardır oda CSharp sınıfını Document sınıfına ait Domain yapısından çıkartmak.Undecided

Internet ve basılı kaynaklarda LSP ilkesini araştırdığınızda Rectangle ve Square örneği ilede karşılaşabilirsiniz. Bu senaryoda, Liskov Substitution ilkesini son derece basit ve yalın bir dille anlatmaktadır. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

LSP.rar (23,83 kb)

Tasarım Prensipleri - Single Responsibility

Cumartesi, 27 Haziran 2009 04:04 by bsenyurt

Merhaba Arkadaşlar,

Sanıyorum benim gibi eskiler, yandaki resimde yer alan değerli ressamı hatırlayacaklardır. Bob Ross. Küçüklüğümde(ve halen Smile) Bob Ross' un TRT televizyonunda yayınlanan Resim Sevinci programlarını zaman zaman izler ve yarım saatlik sürede çizdiği doğa manzaralarına bakakalırdım. Rahmetli Bob bu günkü Tasarım Prensipleri uyarlanması sırasında Einstein ile birlikte küçük bir rol üstleniyor olacak. Öyleyse sözü fazla uzatmadan konumuza geçelim.

 

 

 

 

 

 

 

Hatırlayacağınız gibi son iki blog yazımda, nesneye dayalı tasarım prensipleri içerisinde uyarlanan ilkelere değinmeye çalışmıştım. Bu günkü konumuz ise Single Responsibility prensibi. Bu prensip anlaşılması kolay ancak çoğu zaman tespit edilmesi veya gerekliliğinin ortaya çıkartılması zor bir ilke olarak karşımıza çıkmaktadır. İlkenin savunduğu tez şudur; Bir sınıf sadece tek bir sorumluluk içermelidir. Bir başka deyişle bir sınıfın birden fazla sorumluluğa sahip olmasına karşı bir ilkedir. Bunun en büyük nedeni olarak, sıklıkla yapılan ya da beklenen değişikliklerin, sorumluluk sayısı fazla olan sınıflar için yeniden kullanılabilirliği(Reusable), test edilebilirliği, genişletilebilirliği vb... zorlaştırıyor olmasıdır. Bu ilke aslında şu şekildede açıklanabilir; bir sınıfın değişikliğe uğraması için birden fazla neden olmamalıdır.

Her zamanki gibi konuyu daha kolay kavrayabilmek adına basit bir örnek üzerinden ilerlemekte yarar olduğu kanısındayım.

Bu konu ile ilişkili olaraktan çeşitli kaynaklarda oldukça farklı ve güzel örnekler bulunmakta. Özellikle şu sıralar takip ettiğim, Robert C. Martin' in Agile Principles, Patterns, and Practices in C# kitabındaki örneklerden esinlendiğimi baştan belirtmek isterim.

Öncelikli olarak problemi içeren Solution içeriğimizi ele alalım. Örneğimizde Star isimli bir sınıf bulunmaktadır. Bu sınıf basit anlamda 3 boyutlu bir yıldız şeklinin bazı değerlerini tutmaktadır. Ancak dahada önemlisi içerisinde yıldızın hacmini hesaplayan ve yıldız şeklinde bir Windows Formu çizen fonksiyonlar yer almaktadır.(Bu son görevleri kafamızın bir köşesinde şimdiden tutalım Wink) Sınıfımız bir Class Library içerisindedir. Bu sınıf içerisinde bir Windows Form' unun çizilmesi sağlandığından System.Windows.Forms assembly' ını referans etmektedir. Diğer yandan Star isimli sınıfı kullanan iki farklı uygulama söz konusudur. Bunlardan birisi bir Windows uygulaması olup Star sınıfı içerisinde Form çizen operasyonu ele almaktadır. Diğer uygulama ise basit bir Console projesidir ve sadece generic List<T> tabanlı bir Star nesne koleksiyonundaki hacim değerlerini kullanarak bilimsel bir hesaplama gerçekleştirmektedir. Solution içeriği aşağıda görüldüğü gibidir.

Gelelim kod içeriğimize. GraphicLib isimli sınıf kütüphanemizde yer alan Star sınıfına ait kodlar aşağıdaki gibidir.

using System;
using System.Windows.Forms;

namespace GraphicLib
{
    public class Star
    {
        public int CornerCount { get; set; }
        public int LineWidth { get; set; }
        public int zValue { get; set; }

        public Form Paint(string text)
        {
            Form frm = new Form();
           
            // Aslında yıldız şeklinde bir form çizdirildiği varsayılabilir
            frm.Text = text;
            frm.Width = LineWidth*2;
            frm.Height = Convert.ToInt32((CornerCount * LineWidth) / 3.14);

            return frm;
        }

        public double Volume()
        {
            // Tamamen hayali bir hacim hesaplaması. Normalde oluşturulan yıldızın hacminin hesaplandığı düşünülmektedir.
            return CornerCount * LineWidth;
        }
    }
}

Star sınıfı içerisinde yapılan anlamsız işlemlere takılmayın. Amacımız tamamen işe yarar bir sınıfı kullanmak değil şu aşamada Wink Ancak Paint ve Volume metodları bizim için oldukça önemlidir. Computer isimli Console uygulamamız içerisinde Geometric isimli yardımcı bir sınıf bulunmaktadır.

using System.Collections.Generic;
using GraphicLib;

namespace Computer
{
    class Geometric
    {
        // Sembolik bir matematiksel hesaplama yaptığı varsayılır
        public double Compute(List<Star> stars)
        {
            double total = 0;

            for (int i = 0; i < stars.Count; i++)
            {
                total += stars[i].Volume();
            }

            return total;
        }
    }
}

Bu sınıf içerisinde yer alan Compute metodu, parametre olarak gelen List<Star> koleksiyonunundaki her bir eleman için Volume metodunu ele almaktadır. Computer isimli programa ait test kodları ise aşağıdaki gibidir.

using GraphicLib;
using System;

namespace Computer
{
    class Program
    {
        static void Main(string[] args)
        {
            Geometric einstein = new Geometric();

            double result=einstein.Compute(
                new System.Collections.Generic.List<GraphicLib.Star>{
                    new Star{CornerCount=10,LineWidth=12},
                    new Star{CornerCount=8,LineWidth=24},
                    new Star{CornerCount=5,LineWidth=35}
                }
                );
            System.Console.WriteLine("Bilimsel hesaplama yapılmıştır");
           
            System.Console.WriteLine(result.ToString());

            Console.ReadLine();
        }
    }
}

Peki ya WinForms uygulamamız. Bu uygulama içerisinde de Painter isimli bir sınıf bulunmaktadır.

using System.Windows.Forms;
using GraphicLib;

namespace ProblemWinForm
{
    class Painter
    {
        public void DrawScreen(Star aStar)
        {
            Form form = aStar.Paint("Yeni Form");
           
            form.Show();           
        }
    }
}

Bu sınıf içerisinde yer alan DrawScreen metodu, parametre olarak Star tipinden bir referans almakta ve Paint metodunu çağırmaktadır. Ve sıra Bob' tadır. İşte Windows formumuzdaki Button kontrolüne basılınca olmasını istediklerimiz.

using System;
using System.Windows.Forms;
using GraphicLib;

namespace ProblemWinForm
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnDraw_Click(object sender, EventArgs e)
        {
            Painter bobRoss = new Painter();
            bobRoss.DrawScreen(new Star() { LineWidth = 100, CornerCount = 12 });
        }
    }
}

Peki şimdi ne oldu? Görüldüğü gibi tozu dumana kattık ve ortalığı iyice karştırdık. Karıştırdım Sealed Aslında aşağıdaki şekil zihnimizi biraz daha kolay aydınlatabilir.

Sanıyorumki durum şimdi biraz daha netleşmektedir. Yinede açıklamaya çalışayım. Windows uygulamasında yer alan Painter sınıfı için, Star tipi içerisinde kullanılan tek bir fonksiyonellik vardır. Paint isimli metod. Painter kesinlikle Volume isimli fonskiyonla ilgilenmemektedir ki işinede yaramamaktadır zaten. Aksine Volume metodu, Geometric tipinin yer aldığı Console uygulaması için önemlidir. Diğer yandan Geometric sınıfı içinde, Star tipindeki Paint metodunun bir önemi yoktur. Buda uygulamaların taşınmaları veya dağıtılmaları esnasında gereksiz olan assembly' larında taşınması anlamına gelmektedir. Bahsettiklerimizden yola çıkarak şu sonuca varabiliriz; Star sınıfı iki farklı sorumluluğu üstlenmektedir ve bu, Single Responsibility ilkesine ters düşmektedir.

Öyleyse Single Responsibility prensibine uygun olacak şekilde nasıl bir çözüm üretebiliriz. Tek yapılması gereken sorumlulukları farklı sınıflara dağıtmak olacaktır. Yani iki farklı Star tipi tasarlanacak, bunlardan birisi Form çizme işlemini üstlenirken diğeri ise sadece hesaplama işlemlerini üzerine alacaktır. Söz gelimi GeometricStar ve GraphicStar isimli iki farklı sınıf bu amaçla tasarlanabilir.

Grafiksel işlemlere ait sorumluluklar farklı bir sınıfa, bilimsel hesaplamalar ile ilişkili sorumluluklarda diğer bir sınıfa verilmiştir. Her iki sınıf için değişmez olan ortak özellikler ise bir üst sınıfta toplanmıştır.

Görüldüğü gibi ilke son derece basit ama bazen tespit edilmesi, görülmesi ve hatta uygulanması kolay olmayabilir. Undecided Söz gelimi bu yazıdaki örneklerde farklı Assembly' lar yer almaktadır. Star sınıfı üzerindeki sorumlulukları farklı sınıflara dağıtmış olsak bile, Console ve Windows uygulamalarının her ikiside aynı kütüphaneyi referans etmekte ve her iki Star tipinede(dolayısıyla her iki sorumluluğada) erişebilmektedir. Belkide sadece sorumlulukları dahilindeki tiplere erişmeleri için bir takım önlemler alınması gerekebilir. Nitekim bu durumda, gereksiz tiplerinde taşınması söz konusudur ki buda ürünün çevikliğini negatif etkileyebilir. İşte hepimize kafa karıştırıcı olduğu kadar gerekli olan bir tartışma konusu. Yorumlarınız, tüm okurlarımız için değerli olacaktır.

Böylece geldik bir yazımızın daha sonuna. Bu yazımızda basit bir şekilde Single Responsibility prensibini incelemeye çalıştık. Örnek tam olarak faydalı olmasada, bir sınıfın tek bir sorumluluğa sahip olması gerekliliğinin Single Responsibility prensibinin kendisi olduğunu anlamış bulunuyoruz. Laughing Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

SRP.rar (109,33 kb)

Tasarım Prensipleri - Open Closed

Perşembe, 25 Haziran 2009 16:22 by bsenyurt

Merhaba Arkadaşlar,

Bir önceki yazımda, yazılm tasrımında benimsenen ilkelerinden birisi olan Loose Coupling prensibine değinmiştik. Bu yazımızda ise, Open Closed(Açık Kapalı) prensibine değiniyor olacağız.(Bu prensibini pek çok yazılım disiplininde görebilirsiniz. Örneğin eXtreme Programming veya Aspect Oriented Programming-AOP içerisinde.)

Açık kapalı prensibi aslında son derece basit bir ilkedir. Bu ilke bir sistemin sürekli olarak değişimlere maruz kalabileceğini göz önüne alaraktan(ki örneğin çevik süreçlerde hızlı değişimler asıl odaklanılan noktadır), genişletilmeye açık ama modifiye edilmeye kapalı varlıkların(Sınıf, Method vb...) kullanılmasını önerir. Gerçekten de günümüz Enterprise çözümlerin çoğunda,müşteri ihtiyaçlarına göre yazılımın sürekli güncelleniyor olması gerekmektedir. Bu noktada güncelleştirme işlemleri sırasında koda dokunmadan ilerlemeye çalışmak neredeyse imkansızdır. Ancak bu risk en aza indirgenmeye çalışılabilir. OCP(Open Closed Principle) bu noktada devreye giren prensiplerden sadece birisidir. Tabikide bu teorik anlatım bir örnekle süslenmediği takdirde çok anlaşılır değildir Wink Gelin önce problemli bir tasarım ile yola çıkalım ve sonrasında ise OCP' i nasıl uygulayabileceğimize bakalım(ki bu noktada bir önceki blog yazısına göre bir dejavu yaşayabilirsiniz benden söylemesi Surprised )

Yukarıdaki basit UML şemasında farklı formatlarda resimler üretmek için kullanılan bir yaratıcı sınıf(ImageCreator) görülmektedir. İlişkidende anlaşılacağı üzere ImageCreator sınıfı ile diğer resim sınıfları arasında kuvvetli bir bağ vardır. Bu acemi tasarımı biraz toparlamaya çalışmak istediğimizi düşünelim. Belkide aşağıdaki UML şemasında görülen kurguyu tasarlamış olabiliriz.

Bu kez ImageBase isimli bir ata sınıfı işin içerisine katmışız gibi görünüyor. Hatta koduda aşağıda şekilde tasarladığımızı düşünelim(Tabi bu şekilde kod yazmamızın amacı tamam şakacıktan. Amaç bizi Open Close prensibine götüren sebepleri ortaya koyabilmek. Wink )

using System;

namespace Problem
{
    class ImageCreator
    {
        ImageBase _image = null;
        public ImageCreator(ImageBase obj)
        {
            _image = obj;
        }
        public void Randomize()
        {
            if (_image is Bmp)
                ((Bmp)_image).Randomize();
            else if (_image is Jpg)
                ((Jpg)_image).Randomize();
            else if (_image is Tif)
                ((Tif)_image).Randomize();
            else
                Console.WriteLine("Geçersiz format");
        }
        public void Draw()
        {
            if (_image is Bmp)
                ((Bmp)_image).Draw();
            else if (_image is Jpg)
                ((Jpg)_image).Draw();
            else if (_image is Tif)
                ((Tif)_image).Draw();
            else
                Console.WriteLine("Geçersiz format");
        }   
    }

    class ImageBase
    {
    }

    class Bmp
        :ImageBase
    {
        public void Randomize()
        {
            Console.WriteLine("Random bitmap");
        }
        public void Draw()
        {
            Console.WriteLine("Draw bitmap");
        }
    }

    class Jpg
        :ImageBase
    {
        public void Randomize()
        {
            Console.WriteLine("Random Jpg");
        }
        public void Draw()
        {
            Console.WriteLine("Draw Jpg");
        }
    }

    class Tif
        :ImageBase
    {
        public void Randomize()
        {
            Console.WriteLine("Random Tif");
        }
        public void Draw()
        {
            Console.WriteLine("Draw Tif");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ImageCreator creator = new ImageCreator(new Jpg());
            creator.Randomize();
            creator.Draw();

            creator = new ImageCreator(new Tif());
            creator.Randomize();
            creator.Draw();
        }
    }
}

Kodun amacına göre farklı formatta Image tiplerini oluşturan bir sınıf söz konusudur. Bu sınıf içerisinde yer alan Randomize ve Draw isimli metodlar parametre olarak ImageBase tipinden referanslar almaktadır. Her iki metodda kendi içerisinde, gelen tipin Jpg, Bmp veya Tif olup olmadığına bakarak işlemler yapmaktadır(Kapalılık ilkesi zaten if kısmında bozulmaktadır Sealed ) Hatta tip tespitinden sonra doğru Randomize yada Draw metodunu çağırabilmek için bir Cast işlemininde uygulandığını görebiliriz. Tabiki normal şartlarda bu tip bir tasarımı tercih etmeyiz, etmemeliyiz. Nitekim söz konusu tasarımın şu sorunları doğuracağı ortadadır.

  • Yeni bir resim formatı sisteme eklenmek istendiğinde ImageCreator sınıfı içerisinde yer alan Randomize ve Draw metodlarında yer alan if koşullarına ilaveler yapılması gerekmektedir. Buda üretici sınıf koduna müdahele edilmesi anlamına gelmektedir.
  • Her yeni imaj eklenişinde unit testlerininde(eğer hazırlanmışlarda) tekrardan tasarlanması veya oluşturulması gerekir. Özellikle UnitTest' i yapılmış olan bir kod parçasında tekrardan değişikliğe gidilmek zorunda kalınması, testin yeniden kurgulanmasınıda gerektirecektir. En azından eski teste olan güveni sorgulatacaktır.

Bu sonuçlara göre ImageCreator sınıfı için Closed bir yapı sağlanamadığını ifade edebiliriz. Bir başka deyişle modifiyeye açık(ama olmaması gereken) bir tip söz konusudur. Peki öyleyse Open Closed prensibine uygun olarak kod nasıl tasarlanabilir. Önce UML şemasındaki düzenlememizi yapalım.

Görüldüğü gibi ImageCreator ile Jpg, Bmp, Gif ve benzeri resim sınıflar arasındaki kuvvetli bağ ortadan kaldırılmış, kurallar interface tipine yıkılmıştır. Peki ya bu şemayı C# tarafında nasıl uygulayabiliriz. İşte örnek Console uygulaması kodlarımız.

using System;

namespace Solution
{
    public class ImageCreator<T>
        where T:IImage
    {
        private T _image;

        public ImageCreator(T img)
        {
            _image = img;
        }

        public void RandomizeImage()
        {
            _image.Randomize();
        }
        public void DrawImage()
        {
            _image.Draw();
        }
    }

    public interface IImage
    {
        void Randomize();
        void Draw();
    }

    class Bmp
        :IImage
    {
        public void Randomize()
        {
            Console.WriteLine("Random bitmap");
        }
        public void Draw()
        {
            Console.WriteLine("Draw bitmap");
        }
    }

    class Jpg
        :IImage
    {
        public void Randomize()
        {
            Console.WriteLine("Random Jpg");
        }
        public void Draw()
        {
            Console.WriteLine("Draw Jpg");
        }
    }

    class Tif
        :IImage
    {
        public void Randomize()
        {
            Console.WriteLine("Random Tif");
        }
        public void Draw()
        {
            Console.WriteLine("Draw Tif");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ImageCreator<Bmp> creator = new ImageCreator<Bmp>(new Bmp());
            creator.RandomizeImage();
            creator.DrawImage();

            ImageCreator<Tif> creator2 = new ImageCreator<Tif>(new Tif());
            creator2.RandomizeImage();
            creator2.DrawImage();           
        }
    }
}

Dikkat edileceğiz üzere Jpg, Gif ve Bmp isimli sınıflar IImage arayüzünü uygulamaktadır. Diğer taraftan ImageCreator sınıfı kendi içerisinde IImage arayüzünü ele alarak Randomize ve Draw operasyonlarını icra etmektedir(Neden dejavu yaşayacağınızı anladınız sanırım Laughing ) Buna göre ImageCreator tipinin yapısını bozmadan sisteme yeni resim formatları eklenmesi sağlanabilir. Dolayısıyla ImageCreator sınıfı OCP uyumlu hale getirilmiştir.

Ve bu blog girişinin Özlü Cümlesi: OCP ilkesi, sınıf, metod gibi OOP varlıklarının genişletilmeye açık(Open) ancak düzenlenmeye kapalı(Closed) olması gerektiğini savunur. Özellikle müşteriden gelen istekler nedeniyle sık sık genişletilmesi gereken varlıklarda, genişletmenin kod içerisinde mümkün olduğunca az meydana gelmesine çalışmak gerekir. İlkenin amacını, yeni fonksiyonelliklerin kazandırılması için minimum kod değişikliğinin yapılması olarak düşünebiliriz.

Bu arada kod ve resimlerin bazı yerlerinde Tif bazı yerlerinde Gif formatlarını ele aldığımı farkettim. Ama son yaptıpımız OCP tasarımına göre hiç sorun değil Wink Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

OCP.rar (42,53 kb)

Tasarım Prensipleri - Loose Coupling

Çarşamba, 24 Haziran 2009 10:46 by bsenyurt

Merhaba Arkadaşlar

Yazılım teknolojilerinde uygulanan tekniklerin çoğunda temel tasarım prensipleri sıklıkla ele alınmaktadır/Alınmalıdı. Örneğin eXtreme Programming, Aspect Oriented Programming vb... yazılım geliştirme tekniklerinde bu prensiplerin çoğuna rastlayabiliriz. Bu yazı ile birlikte Temel Tasarım Prensiplerinin incelenmesine başlıyor olacağız ki özellikle büyük çaplı projelerde bu tip disiplinler büyük bir öneme sahiptir.

Enterprise yazılım süreçlerinde en çok zorlanılan noktalardan biriside müşteri ihtiyaçlarının sürekli olarak en hızlı şekilde karşılanması gerekliliğidir. Bu durumda yazılımın bir süre sonra kendinden geçerek Sealed dağılmasını engellemek gerekmektedir. Bu anlamda uygulanan süreçlerin içerisinde önem arz eden noktalardan biriside, kullanılacak yazılım disiplinleridir. Bu nedenle Hatta bu presipler içerisinde tasarım desenlerininde(Design Patterns) önemli bir yeri vardır. Serimizin bu ilk yazısına en kolay prensip ile başlıyor olacağız; Zayıf Bağlılık(Loose Coupling) prensibi.

Konuyu kavramamın en güzel yolu tahmin edeceğiniz üzere basit bir örnek üzerinden ilerlemek olacaktır. Bu amaçla aşağıdaki Console uygulamasını geliştirdiğimizi düşünelim.

using System;

namespace Problem
{
    // WinScreen tipine ait nesne örneklerinin yaratıcısı olan sınıf
    class ScreenCreator
    {
        private WinScreen winScreen = null;

        public ScreenCreator()
        {

        }
        public ScreenCreator(WinScreen winScr)
        {
            winScreen = winScr;
        }

        public void InitializeScreen()
        {
            winScreen.Initialize();
        }
        public void DrawScreen()
        {
            winScreen.Draw();
        }
    }

    // Windows tabanlı sistemleri için düşünülen bir ekran
    class WinScreen
    {
        public void Initialize()
        {
            Console.WriteLine("WinScreen initialize işlemi");
        }
        public void Draw()
        {
            Console.WriteLine("WinScreen Draw işlemi");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Ekran üretme işlemi başladı");

            ScreenCreator creator = new ScreenCreator(new WinScreen());
            creator.InitializeScreen();
            creator.DrawScreen();
        }
    }
}

Bu örnek uygulama ScreenCreator ve WinScreen isimli iki sınıf ve arasındaki ilişki ele alınmaktadır. ScreenCreator sınıfına ait nesne örnekleri, aşırı yüklenmiş yapıcı metoda gelen WinScreen referansları üzerinden Initialize ve Draw isimli fonksiyonelliklerin uygulanabilmesine imkan tanımaktadır. Böylece bir Windows Formunun başlatılması ve ekrana çizilmesi işlemlerinin gerçekleştirileceği düşünülmektedir.

Uygulamayı çalıştırdığımızda aşağıdaki sonuçları alırız.

Aslında kodu dikkatlice incelediğimizde ve yazılım tasarımına baktığımızda bazı sıkıntılar olduğunu görebiliriz. Örneğin;

  • ScreenCreator nesne örnekleri tek başlarına anlamsızdır. Kullanışlı olmaları için WinScreen sınıfı ile birlikte ele alınmaları gerekir.
  • WinScreen sınıfı içerisinde yapılacak değişiklikler ScreenCreator tipinide etikeyecektir.
  • ScreenCreator sadece WinScreen tipini ele almaktadır. Oysaki web ekranları, WPF ekranları, mobile ekranlar veya x ekranlar için tasarlanmış sınıfların ScreenCreator tarafından ele alınması mümkün değildir. Kuvvetli bağ buna müsade etmemektedir.

Bu sonuçlara göre ScreenCreator ve WinScreen nesne örnekleri arasında aşağıdaki UML diagramında görülen ilişkinin olduğunu düşünebiliriz.

İşte Loose Coupling ilkesi söz konusu sorunlara çözüm getirmeyi kolaylaştırmaktadır. Aslında nesneler arasındaki bu kuvvetli bağların kaldırılması işi kökten çözebilir. Ne varki OOP(Object Oriented Programming) dillerinde bu mümkün değildir. Dolayısıyla bağı zayıflaştırmaya çalışmak üzere bir takım işlemler yapılabilir. .Net tarafından olaya baktığımızda interface veya abstract tipleri kullanarak zayıf bağlı bir ortam oluşturabiliriz. İşte Loose Coupling prensibini uygulayan yeni kod içeriğimiz.

using System;

namespace Solution
{
    public interface IScreen
    {
        void Initialize();
        void Draw();
    }

   public class WinScreen
       : IScreen
   {
       #region IScreen Members

       public void Initialize()
       {
           Console.WriteLine("WinScreen Initialize işlemi");
       }

       public void Draw()
       {
           Console.WriteLine("WinScreen draw işlemi");
       }

       #endregion
   }

   public class WebScreen
       :IScreen
   {
       #region IScreen Members

       public void Initialize()
       {
           Console.WriteLine("WebScreen initialize işlemi");
       }

       public void Draw()
       {
           Console.WriteLine("WebScreen draw işlemi");
       }

       #endregion
   }

    public class MobileScreen
        : IScreen
    {
        #region IScreen Members

        public void Initialize()
        {
            Console.WriteLine("MobileScreen initialize işlemi");
        }

        public void Draw()
        {
            Console.WriteLine("MobileScreen draw işlemi");
        }

        #endregion
    }

    public class ScreenCreator
    {
        private IScreen _screen;

        public ScreenCreator(IScreen scr)
        {
            _screen = scr;
        }
        public void InitializeScreen()
        {
            _screen.Initialize();
        }
        public void DrawScreen()
        {
            _screen.Draw();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ScreenCreator creator = new ScreenCreator(new WebScreen());
            creator.InitializeScreen();
            creator.DrawScreen();

            creator = new ScreenCreator(new WinScreen());
            creator.InitializeScreen();
            creator.DrawScreen();

            creator=new ScreenCreator(new MobileScreen());
            creator.InitializeScreen();
            creator.DrawScreen();
        }
    }
}

Yeni tasarımızda Initialize ve Draw isimli operasyonlar IScreen isimli bir arayüz içerisinde bildirilmiş ve bu arayüzden türeyen tiplerde ayrı ayrı uygulanmışlardır. ScreenCreator sınıfı ise kendi içerisinde IScreen interface referansını ele almaktadır. Buna göre IScreen arayüzünü implemente eden her sınıf, ScreenCreator tarafından kullanılabilir. Bir başka deyişle ScreenCreator sınıfının, üreteceği ekran ile ilişkili herhangibir bilgiye sahip olmasınada gerek yoktur. Sadece Interface referansının Initialize ve Draw operasyonlarını çağırmaktadır. Diğer yandan IScreen arayüzünü implemente eden tipler içerisinde yapılacak değişimler, ScreenCreator sınıfını doğrudan etkilemeyecektir. Hatta bu etki en aza indirgenmiş olacaktır. Olaya basit bir UML şeması ile baktığımızdaysa tipler arasındaki ilişkileri daha net görebiliriz.

Programı çalıştırdığımızda ise aşağıdaki sonuçları elde ederiz.

Böylece Temel Tasarım Prensiplerinden birisi ve bana kalırsa belkide en basiti olan Loose Coupling ilkesini incelemiş olduk. İlerleyen yazılarımızda diğer prensiplerede bakıyor olacağız. Örneğin Open Closed, Single Responsibility, Liskov Substitution vb... Wink Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

LooseCoupling.rar (39,67 kb)