Merhaba. Bu yazımda solid kurallarından single responsibility ve open/closed kurallarına iş dünyasında karşılaştığım bir probleme üretmiş olduğum çözümden esinlenerek bir örnek vereceğim. Bu örneğin internette çokça yer alan soyut anlatımların aksine solid kullanımının yararlarının akılda canlanmasına yardımcı olacağını düşünüyorum. Hadi başlayalım;
Benim şuan çalışmakta olduğum firmamda, firma personellerinin kullanımına sunulan, ekran diye bilinen, IT ekiplerinin yazdığı çeşitli web sayfaları bulunmaktadır. Bu sayfalar ASP.NET MVC ve Telerik firmasının kendo bileşenleri kullanılarak yapılmaktadır.
Problemin Tanımlanması
Hemen konuya gireyim. Ekibimize gelen geliştirme talebi doğrultusunda var olan sayfalarda ux dönüşümleri yapmamız gerekti. Sayfalarda genellikle bir kendo grid var. Gridin üstünde arama inputu var. Bu arama input’una bir yazı yazıldığında grid içinde arama yapılması isteniyor.
İlk Çözüm
Hemen google dan arattım ve bu aramanın kendo bileşenlerinde global search olarak geçtiğini gördüm. Global search ün kendo grid üzerinde nasıl yapılacağına dair bir kod parçası buldum. Fakat bunu her sayfaya sırayla yapıştıramazdım. Bu kod tekrarı demekti. Bir bug bulunduğunda yaklaşık 10 tane sayfanın tek tek değiştirilmesi gerekirdi. Bu sebeple Typescript kullanarak bir sınıf oluşturdum ve sayfalarda bu dosyadan sınıfı çekerek kullandım. Temsili olarak daha iyi kafada oturması adına aşağıda yazayım.
export class MyGrid extends kedo.ui.Grid{
constructor(){
}
....global search ile alakalı olmayan sonradan geliştirme isteğiyle doğmuş bir takım özellikler fonksiyonlar
method1(){}
method2(){} vs...
globalSearch(stringToSearch: string){
...gridin sütunları arasında stringToSearch ü arayan kod
}
}
İşler Değişiyor
Buraya kadar herşey güzel. Şimdi gelelim işin renginin değiştiği yere. Yeni bir geliştirme talebi geldi. Talepte yaklaşık 10000 kaydın global search ile aranması isteniyor. Sizinde düşüneceğiniz üzere 10000 kayıt çok fazla. inputa bir değer yazıp 10000 kayıt içinde aramak performans problemlerine sebebiyet verebilir. Bu sebeple gridi server paging yani server tarafında sayfalama yapacak şekilde ayarlamam gerekti. 10000 kaydı tek seferde değilde sayfa sayfa çekecekti. Dolayısıyla aramayı da server tarafında sql sorgusu içinde yapmalıydı.
Burası da güzel. Kafamızda çözümümüz canlandı. 10000 kaydı sayfa sayfa çekecektik artık. Ama bir sn. Eskiden grid servisten gelen sayısal rakamların yani idlerin ilgili tablolardaki karşılığını alıp gösteriyordu. Yani ürün id 13 yerine gridde Kredi Kartı yazıyordu mesela. Local olarak Kredi Kartı bilgisi gridin sütunları içinde olduğu için global search ararken sıkıntı yaşamıyordu. Kredi yazınca tüm kayıtlar arasında arayıp Kredi Kartı’ nı geliyordu.
Ama Server Side Pageing ve Server Side Filtering kullanırken filtreleme yaptığımızda tablodan verileri çekecek sql sorgusuna Kredi Kartı diye string bir değer gönderemezdik. Onun yerine tablonun içeriğinde bulunan 13 nolu ürünü getir diyebilirdik. Bu sebeple bir dönüştürme yapmam gerekti. Inputa yazılan yazıları ürün değerleri arasında aratıp ilgili ürün id yi göndermem gerekiyordu servise.
SOLID Yardıma Koşuyor
Şimdi gelelim SOLID in devereye girdiği yere. Ne yapmalıydık? globalSearch fonksiyonuna bir paramatre daha ekleyip o parametreye göre Ürünler arasında ara ve id yi filtrele mi diyecektik?
export class MyGrid extends kedo.ui.Grid{
constructor(){
}
....global search ile alakalı olmayan sonradan geliştirme isteğiyle doğmuş bir takım özellikler fonksiyonlar
method1(){}
method2(){} vs...
globalSearch(stringToSearch: string, lookup:Array){
...eğer lookup doluysa lookup dizisinde stringToSearch ü ara ve bu değerleri de grid de arat
...gridin sütunları arasında stringToSearch ü arayan kod
}
}
Hayır. Öyle yapsaydık eğer yeni sayfamız için globalsearch de birşey değiştirmemiz gerektiğinde eski sayfalar etkilenebilirdi. İşte SOLID burda devreye giriyor. Bir SearchHelper adında sınıf tanımlayacağız. SearchHelper hem arama ile ilgili kodu barındırıp yüksek tutarlılık yani high cohesion sağlayacak, yani başka bir değişyle single responsibilitye hizmet etmiş olacak, hem bu SearchHelper ı constructor aracılığı ile dışardan alacağımız için dependency inversion ve dependency injection kuralını uymuş olacağız, hem de inject edilen bu bağımlılığı türeyen bir sınıf olarak gönderdiğimizde open closed princeple a uymuş olacağız. Yani şöyle;
export class MyGrid extends kedo.ui.Grid{
private searchHelper: DefaultSearchHelper;
constructor(searchHelper: DefaultSearchHelper){
this.searchHelper = searchHelper;
}
....global search ile alakalı olmayan sonradan geliştirme isteğiyle doğmuş bir takım özellikler fonksiyonlar
method1(){}
method2(){} vs...
globalSearch(stringToSearch: string){
this.searchHelper.globalSearch(stringToSearch);
}
}
Nihai Çözümümüz
Şimdi DefaultSearchHelper ve LookupSearchHelper adında iki sınıf oluşturalım. LookupSearchHelper DefaultSearchHelper dan türeyecek. Dolayısıyla DefaultSearchHelper yerine LookupSearchHelper kullanabileceğiz. Aynı zamanda ondan türedeği için onun metodlarını da kullanabilecek.
export class DefaultSearchHelper {
constructor(){
}
globalSearch(stringToSearch: string){
...gridin sütunları arasında stringToSearch ü arayan kod
}
}
export class LookupSearchHelper extends DefaultSearchHelper{
private lookup:Array;
constructor(lookup:Array){
this.lookup = lookup;
}
globalSearch(stringToSearch: string){
...lookup dizisinde stringToSearch ü ara ve bu değerleri de grid de arat
super.globalSearch(stringToSearch);
}
}
Şimdi gelelim kodu kullandığımız yere. Eski kodumuz gridi DefaultSearchHelper ile kullanacak. Hatta ben bir factory oluşturmuştum ve gridi burdan çekiyordum. Dolayısıyla DefaultSearchHelper ile kullanmak için eski sınıflarda bir değişiklik yapmama gerek kalmadı. new MyGrid(new DefaultSearchHelper()) metodunu factory içinde kullanarak varsayılan olarak DefaultSearchHelper ile gelmesini sağladım gridimin.
Buna ek olarak da yeni bir factory method yazıp SearchHelper belirleme opsiyonu verdim. Dolayısıyla yeni sınıflarım LookupSearchHelper ı kolaylıkla kullanabilecek.
Sonuç
Şimdi sınıflarımız open closed princeple ı nasıl gerçekleştirdi. Open closed yani değişime kapalı kalıtıma açık oldu. Bunu DefaultSearchHelper ı tanımlayıp bağımlılık olarak inject ederek sağlamış olduk. DefaultSearchHelper yerine ondan türeyen her türlü sınıfı gride inject edebiliriz.
Aynı zamanda sınıflarımız sadece kendileriyle ilgili metodları barındırdığı için high cohesion yani yüksek tutarlılık oldu. Yani single responsibility kuralına uymuş olduk.
Bu yazımda anlatacaklarım bu kadardı. Umarım amacıma ulaşıp SOLID in kafanızda canlamasına yardımcı olmuşumdur.