Coupling ve Cohesion

Merhaba. Bu yazımda coupling ve cohesion nedir bunlardan bahsedeceğim.

Hemen kaynağımızı belirtelim. Kaynağımız wikipedia ve wikipedia coupling konusunda derki;

In software engineeringcoupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are;[1] the strength of the relationships between modules.

wikizero

Yani wikipedia nın dediğine göre coupling yazılım modulleri arasında bağlılığın derecesidir. Bu modüllerin birbirne bağlılığının ölçüsüdür.

Peki cohesion nedir. Yazılım dünyasında cohesion coupling in tam tersidir. Coupling arttıkça cohesion azalır, cohesion arttıkca coupling azalır. Fazla uzatmadan wikipedia’ dan aldığım tanımı paylaşayım.

In computer programmingcohesion refers to the degree to which the elements inside a module belong together.[1] In one sense, it is a measure of the strength of relationship between the methods and data of a class and some unifying purpose or concept served by that class. In another sense, it is a measure of the strength of relationship between the class’s methods and data themselves

wikizero

Wikipedia diyor ki; cohesion modullerin içindeki elemanların birbirine aitlik derecesidir. Peki nedir bu elemanlar? Bir sınıfı örnek verecek olursak; bu elemanlar property’lere, metodlara karşılık gelmektedir. Bu metodlar ve propertyler birbirine ne kadar benzer ise cohesion o kadar yüksektir.

Kısacası coupling modullerin birbirleri arasındaki bağlılık derecesi, cohesion ise bu modullerin içindeki elemanların birbirine bağlılık derecesidir.

Yazılım projelerinde genellikle yüksek cohesion, düşük coupling istenir. Bir yazılım projesinde cohesion ne kadar yüksek, coupling ne kadar düşük olursa, maintainablity yani sürdürülebilirlik, reusablity yani tekrar kullanılabilirlik o kadar yüksek olur.

Low(Loose) Coupling e bir örnek

Low coupling’ e bir örnek verecek olursak; bilgisayarımızın çalışma mantığını düşünelim. Klavyeden girdiğimiz harflerin işlemcide gerekli komut ve datalara çevrildikten sonra ekran kartına gönderildiğini düşünelim.

    class Keyboard
    {
        public string Key { get; set; }
    }
    class GraphicCard
    {
        public int VGAOutput { get; set; }
    }
    class Processor
    {
        private GraphicCard GraphicCard;
        private Keyboard Keyboard;
        public Processor(GraphicCard graphicCard, Keyboard keyboard)
        {
            this.GraphicCard = graphicCard;
            this.Keyboard = keyboard;
        }
        public void WriteToScreen()
        {
            byte[] bytes = Encoding.ASCII.GetBytes(Keyboard.Key);
            //İşlemci hesaplamaları
            GraphicCard.VGAOutput = BitConverter.ToInt32(bytes, 0);
        }
    }

Herşey yolunda gibi görünüyor. Fakat öyle değil. GraphicCard sınıfı ve Keyboard sınıfı Processor sınıfına sıkı sıkıya bağlı. Çünkü ascii den byte a çevirme işlemini Keyboard’ın ve byte dan VGA sinyaline çevirme işlemini ise GraphicCard sınıfının yapması gerekiyor. Bunu en iyi bir değişiklik yapmaya çalıştığımızda anlayabiliriz.

Şimdi Keyboard sınıfına alt-gr tuşunun geldiğini ve GraphicCard sınıfına da HDMI çıkışının geldiğini varsayalım. Bu durumda Hem Keyboard, Hem GraphicCard hem de Processor sınıfında değişiklik yapmamız gerekecektir.

    class Keyboard
    {
        public string Key { get; set; }
        public bool AltGRKeyPressed { get; set; }
    }
    class GraphicCard
    {
        public int VGAOutput { get; set; }
        public int HDMIOutput { get; set; }
    }
    class Processor
    {
        private GraphicCard GraphicCard;
        private Keyboard Keyboard;
        public Processor(GraphicCard graphicCard, Keyboard keyboard)
        {
            this.GraphicCard = graphicCard;
            this.Keyboard = keyboard;
        }
        public void WriteToScreen()
        {
            byte[] bytes = null;
            if (Keyboard.AltGRKeyPressed)
            {
                //Alt gr tuşu temsili
                bytes = Encoding.ASCII.GetBytes("ALTGR" + Keyboard.Key);
            }
            else
            {
                bytes = Encoding.ASCII.GetBytes(Keyboard.Key);
            }
            //İşlemci hesaplamaları
            GraphicCard.VGAOutput = BitConverter.ToInt32(bytes, 0);
            GraphicCard.HDMIOutput = ConvertToHDMI(BitConverter.ToInt32(bytes, 0));
        }

        private int ConvertToHDMI(int v)
        {
            //HDMI Çevirme işlemleri
            return v;
        }
    }

Halbuki ascii den byte a çevirme işlemini keyboard sınıfı üstlenseydi ve GraphicCard da ekranda gösterme işlemlerini üstlenseydi ortaya daha gevşek bağlı (loosely coupled) bir tasarım çıkardı ve Processor sınıfında değişiklik yapmamıza gerek kalmazdı.

    class Keyboard
    {
        public string Key { get; set; }
        public bool AltGRKeyPressed { get; set; }
        public byte[] GetAsciiCode()
        {
            byte[] bytes = null;
            if (this.AltGRKeyPressed)
            {
                //Alt gr tuşu temsili
                bytes = Encoding.ASCII.GetBytes("ALTGR" + this.Key);
            }
            else
            {
                bytes = Encoding.ASCII.GetBytes(this.Key);
            }

            return bytes;
        }
    }
    class GraphicCard
    {
        public int VGAOutput { get; set; }
        public int HDMIOutput { get; set; }
        public void SetOutput(byte[] byteCode)
        {
            this.VGAOutput = BitConverter.ToInt32(byteCode, 0);
            this.HDMIOutput = ConvertToHDMI(BitConverter.ToInt32(byteCode, 0));
        }

        private int ConvertToHDMI(int v)
        {
            //HDMI Çevirme işlemleri
            return v;
        }
    }
    class Processor
    {
        private GraphicCard GraphicCard;
        private Keyboard Keyboard;
        public Processor(GraphicCard graphicCard, Keyboard keyboard)
        {
            this.GraphicCard = graphicCard;
            this.Keyboard = keyboard;
        }
        public void WriteToScreen()
        {
            var byteCode = Keyboard.GetAsciiCode();
            //İşlemci hesaplamaları
            GraphicCard.SetOutput(byteCode);
        }
    }

Böylece her sınıf kendi sorumluluğuna ait kod parçasını kendi içine alarak daha loosely coupled bir yapı oluşturmuş oldu. Sınıfların birbirine olan bağımlılığı azalmış oldu. Artık keyboard sınıfna ne kadar tuş gelsede Processor sınıfını ilgilendirmiyor. Processor sınıfı sadece byte isteyecek. Ya da GraphicCard sınıfına ne kadar yeni çıkış eklenirse eklensin bu Processor sınıfının umrunda değil. Ekran kartı çıkışlarını yönetmek GraphicCard sınıfına ait.

High Cohesion a bir örnek

High Cohesion’a bir örnek verecek olursak; web tabanlı bir projede bir component kütüphanesi yazdığımızı düşünelim. Bu component kütüphanesini yazarken nesne yönelimli bir yol izlediğimizi ele alalım. İlk önce en küçük yapı taşından kodlamaya başlar isek; Html in en küçük yapı taşı taglerdir. Yani diğer bir ifade ile html elementleridir. HtmlElement adında bir sınıf oluşturalım.

    class HtmlElement
    {
        public string TagName { get; set; }
        public string InnerHtml { get; set; }

        public HtmlElement(string tagName)
        {
            this.TagName = tagName;
        }

        public override string ToString()
        {
            return "<" + TagName + ">" + InnerHtml + "<" + TagName + "/>";
        }
    }

Ama her hmtl elementi içinde bir html elementi barındırmaz. Dolayısıyla bazı elementler kendiliğinden kapatılmış olur. Mesela input bunlara bir örnektir. Bu yüzden HtmlElement sınıfımızı içinde html elementi barındırmayan elementler için güncellememiz gerekecek.

    class HtmlElement
    {
        public string TagName { get; set; }
        public string InnerHtml { get; set; }
        public bool IsSelfClosed { get; set; }

        public HtmlElement(string tagName)
        {
            this.TagName = tagName;
        }

        public override string ToString()
        {
            if (IsSelfClosed)
            {
                return "<" + TagName + "/>";
            }
            else
            {
                return "<" + TagName + ">" + InnerHtml + "<" + TagName + "/>";
            }
        }
    }

Yukarıda görüldüğü üzere kendiliğinden kapanan elementler için yeni bir alan ekledik ve ToString() metodumuzu buna göre tekrar yazdık. Yapabileceklerimiz bununla da sınırlı değil. Örneğin bazı elementlerin type özelliği vardır. Yine input’u örnek verebiliriz. Input elementlerinin type özelliği text, button, submit, password olabilir. Bunlar için yeni bir property ekleyip gerekli geliştirmeleri yapmalıyız. Ama yanlış olan birşey var. Biz sürekli yeni özellikler geldiğinde bu özellikleri HtmlElement’inin içine atarsak bu sınıf çok komplex ve karmaşık bir yapı haline gelecek. İşte cohesion HtmlElement sınıfına IsSelfClosed gibi bu sınıfla ilgili olmayan metodları, property’ leri eklememektir(HtmlElement sınıfını mümkün olduğunca birbiriyle alakalı metodlardan ve property’lerden oluşacak şekilde tasarlamalıyız.). Peki buraya eklemeyeceksek ne yapacağız? Yeni bir HtmlElement sınıfı tanımlayıp kendiliğinden kapanan elementlerin özelliklerini buraya koyacağız.

    class SelfClosedHtmlElement
    {
        public string TagName { get; set; }
        public bool IsSelfClosed { get; set; }

        public SelfClosedHtmlElement(string tagName)
        {
            this.TagName = tagName;
        }

        public override string ToString()
        {
            return "<" + TagName + "/>";
        }
    }

Tabi TagName her iki sınıfta da olduğundan daha iyi bir tasarım için HtmlElement sınıfını bir base class haline getirip, sınıfları SelfClosedElement ve ContainerElement şeklikde ayırırsak daha iyi olur.

    class HtmlElement
    {
        public string TagName { get; set; }

        public HtmlElement(string tagName)
        {
            this.TagName = tagName;
        }
    }

    class ContainerElement : HtmlElement
    {
        public string InnerHtml { get; set; }
        public ContainerElement(string tagName)
            :base(tagName)
        {

        }

        public override string ToString()
        {
            return "<" + TagName + ">" + InnerHtml + "<" + TagName + "/>";
        }
    }

    class SelfClosedHtmlElement : HtmlElement
    {
        public bool IsSelfClosed { get; set; }

        public SelfClosedHtmlElement(string tagName)
            :base(tagName)
        {
        }

        public override string ToString()
        {
            return "<" + TagName + "/>";
        }
    }

Sonuç

Yukarıdaki örneklerde görüldüğü üzere coupling ve cohesion bir birinin tersi kavramlar. Cohesion’ı yükselttikçe yani bir sınıfın bileşenlerini sadece birbirine benzer şeylerden oluşturup diğer sorumlulukları üstelenebilecek sınıflara attıkça coupling düşmektedir. Zaten iyi bir yazılım projesinde de yapılmak istenen budur. High Cohesion Low Coupling.

Bir yorum bırakın