ASP.NET MVC Ajax.ActionLink ile sayfa yenilemeye son

30 Nis

İnternet sayfalarında artık yeni teknolojilerle birlikte javascript’in gücü iyice gösterilmeye başlandı. Sayfa yenilemeleri yapmak yerine sadece javascript ile sayfa üzerindeki etiketler(div) güncellenmekte. Linklere, butonlara tıklandığında istenen sonuçlar etkili bir biçimde bizi rahatsız etmeden önümüze dökülmekte. Son zamanlarda özellikle sosyal paylaşım sitelerinde javascript kendini o kadar belli ediyor ki, javascript’i icat edenler bile birgün bu kadar kullanılacağını tahmin etmemiştir.

Lafı fazla uzatmadan javascript özelliğini, liklere tıklandığında sayfa yenilemesi yapmadan önümüze getiren özelliği ASP.NET MVC mimarisinde kullanmaya çalışacağız.

Bir restorantın menü listesini önümüze getiren bir senaryomuz olduğunu varsayalım. Menü listesinde ürün adı ve fiyatı olsun. Bu yapıyı temsilen bir Menu adında sınıf oluşturmalıyız.


public class Menu
{
      public int Id { get; set; }
      public string Name { get; set; }
      public decimal Price { get; set; }
}

Şimdi MenuController adında bir controller sınıfı oluşturup bu controller sınıfının Index metodu ve Index view sayfası oluşturalım.


public class MenuController : Controller
{
      List<Menu> menu = new List<Menu>
      {
           new Menu{Id =1, Name="Hamburger", Price=3.15M},
           new Menu{Id =2, Name="Cheeseburger", Price=4.05M},
           new Menu{Id =3, Name="Cola", Price=2.0M},
           new Menu{Id =4, Name="Cips", Price=1.25M},
      };

      public ActionResult Index()
      {
           return View();
      }

      public ActionResult GetMenu()
      {
           return PartialView("_MenuPart", menu);
      }

}

Menu Listesi linkine tıklandığında sayfa yenilenmeksizin yukardaki liste tablo halinde linkin aldına indirilacek.

Bunun için yapılacak hazırlıklardan biri jquery.unobtrusive-ajax.min.js dosyasının view sayfasına referans edilmesi gerekmektedir. Aksi taktirde yaptığımız işlem çalışmayacaktır. Bu söylediklerim ASP.NET MVC3 için geçerli tabiki.

Referans olarak ana Layout sayfasına ekliyorum javascript dosyasını.

Şimdi Index view sayfası içine “Menu Listesi” linkini oluşturmalıyız.


@Ajax.ActionLink("Menu Listesi",
                 "GetMenu",
                 new AjaxOptions{
                                 InsertionMode=InsertionMode.Replace,
                                 UpdateTargetId="MenuBox",
                                 HttpMethod="GET",

                 })

<div id="MenuBox"></div>

Ajax helper sınıfının ActionLink Extension metodu ile link oluşturma işlemini gerçekleştirebiliriz. Bu metodun aldığı AjaxOptions parametresinde belirtilen, InsertionMode özelliği Replace olarak işaretlenir. Bu sayede işlem sonrasında, yani GetMenu metodundan dönen değeri UpdateTargerId parametresindeki html element içeriği ile değiştirileceğini belirtiyoruz.

Örneğimizde GetMenu metodu bir _MenuPart adında bir PartialView döndürüyor.

_MenuPart partial view içeriği ise şu şeklide olacaktır.


<table>
    <tr>
         <th>Name</th>
         <th>Price</th>
         <th></th>
    </tr>
    @foreach (var item in Model) {
    <tr>
        <td>@Html.DisplayFor(modelItem => item.Name)</td>
        <td>@Html.DisplayFor(modelItem => item.Price)</td>
        <td>@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
            @Html.ActionLink("Details", "Details", new { id=item.Id }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.Id })
       </td>
    </tr>
    }

</table>

Sonuç olarak Index sayfasındaki Menu Listesi linkine tıklandığında bu tablo önümüze gelecek.

Test Edilebilir Tasarımın Düzenlenmesi (Refactoring)

28 Nis

Refactoring işlemi kodun işlevini değiştirilmeden daha okunabilir hale getirmek amacıyla yeniden düzenlenmesi işlemidir. Özellikler test güdümlü programlamada (Test Driven Development) gerekli olan gevşek bağlılık (loosely coupled) prensibinin uygulanabilmesi için bazı kurallara uymak durumunda kalabiliyoruz.

Özellikle sıkı bağlılığın(tightly coupled) yok edilmesi için uygulamak gereken bazı özellikler vardır. Bunlar:

  • Uygulama kodunu bir interface ile temsil etmek
  • Test edilen sınıfa soyut nesneleri aktararak soyut nesneler üzerinden işlemleri yapmak.
  • Soyut Interface nesnesini constructor seviyesine almak.
  • Soyut interface nesnesini bir property olarak almak.
  • Soyut interface nesnesini metod çağırma işleminden önce almak.

Constructor seviysinde nesne almak (Constructor Injection)

Bu senaryoda, constructor yardımıyla interface nesnesini kabul edeceğiz. Daha sonra alınan bu nesne kullanılmak üzere private erişim belirleyicili bir metoda aktarılır.


public class ReportSender{

       private IReporter reporter;

       public ReportSender(IReporter reporter){
              this.reporter = reporter;
       }

       // Diğer üyeler
       //....
       //..
       //.
 }

Problemler

Test edilen sınıf eğer birden fazla soyut nesne alıyorsa constructor sayısı veya bir constructorun aldığı parametre sayısı artacak. Bu da kodun düzenini ve okunabilirliğini bozacaktır.

Bu sorunu çözmek için kullanılacak yöntemlerden biri, gerekli tüm başlangıç değerlerinin bir sınıf içerisinde başlatılmasıdır (parameter object refactoring).

Bir diğer çözüm ise bağımlılıkların ters çevrilmesidir(Inversion of Controls IoC). Bu işlemi yapan hazır paketler bulunmaktadır. Örneğin Spring.NET, Castle Windsor, Ninject gibi. Bu konteynır yapılar sayesinde bir interface nesnesine karşılık gelecek somut nesne belirlenir ve o interface görüldüğünde somut nesnesi bağlanmaktadır.

Ne zaman kullanılmalı

Constructor seviyesinde nesneler alıp başlatmak istediğinizde, IoC konteynırlar nesne oluşturma sırasında hız kazandırmaktadır. Öte yandan constructor parametreler, API’nizi kullananlara, parametrenin opsiyonel olmadığını göstermektedir.

Property seviyesinde nesne almak(Property Injection)

Bu senaryoda, esnek bağı sağlayacak olan, bir property olacaktır. Dependency injection olarakta bilinen bu teknik bir önceki yönteme benzemektedir. Constructor injection gibi bu yöntem de API üzerinde gerekli bağımlılıkları göstermektedir.


public class ReportSender{

       public  IReporter Reporter {get; set;};

       // Diğer üyeler
       //....
       //..
       //.
}

Ne zaman kullanılmalıdır

Bu teknik, bağımlılığın isteğe bağlı olduğu durumlarda kullanılır. Bağlılığın opsiyonel bırakılmak istenen durumlarda tercih edilebilir.

Metod çığırılmadan önce nesne almak

Bu teknikte ise nesneyi almak için property veya constructor yerine factory sınıfları kullanılır. Bu sayede soyut nesne, sınıf içerisine aktarılmış olur.


public class ReportSender{

       private IReporter reporter;

       public ReportSender(IReporter reporter){
              this.reporter = FactoryClass.CreateReportInstence();
       }

       // Diğer üyeler
       //....
       //..
       //.
 }

Ne zaman kullanılmalıdır

Bu teknik, sınıf girdilerini(input) kontrol etmek istendiğinde kullanılabilir. Ancak test ortamında, bağımlılıkları test edmek istediğinizde kötü sonuç verecektir. Çünkü test ortamında sınıf içine mock object aktarıp davranışları test etmek istendiğinde başarısız oluruz.

Silverlight Invalid Charecter Encoding Sorunu

25 Nis

Silverlight ortamında bazı proje şablonlarını kullanırken dile özgü karakterlerin kullanılması sonucu hatalarla karşılaşabilmekteyiz. Bu hatalardan biri de benim başıma geldi. Esri Silverlight API kullanırken hazır şablonlardan birini seçip edit etmeye başladığımda design sayfasında bir hata oluştu. Hatanın sebebi Türkçe karakterler kullanmam idi. Oluşan hata şu şekildeydi.

Error  1   Cannot resolve reference assemblies. Please check the reference assemblies. Invalid character in the given encoding. Line 116, position 53.  

Proje template oluşturulurken muhtemelen global ayarlar seçilsin şeklinde ayarlanmış olmalı ki ayarlarda Türkçe encoding seçiliydi.

Bu ayarı UTF-8 yapmalıydım. Bu işlem için aşağıdaki yolu izledim. File -> Advenced Save Options…

Encoding seçeneklerinden de Unicode(UTF-8 with signature) – Codepage-65001 seçeneğini seçip OK tuşuyla devam ettim.

Ardından projeyi bir kez derlediğimde sorun ortadan kalktı. İşte mutlu son.

Tekrar görüşmek dileğiyle.

Asp.net MVC JsonResult Tipli Metodlar İçin Unit Test

24 Nis

ASP.NET MVC mimarisi üzerinde test güdümlü çalışırken karşılaşacağımız test tiplerinden biri de JsonResult tipindeki action metodların testidir. Test tipinde veri döndüren metodların testleri kolay olabilmektedir. Ancak JsonResult tipindeki metodlar veriyi JSON tipine dönüştürerek sunduğu için, metod tarafından bize sunulan veriler text şeklinde olmayacaktır.

Normalde önce test metodu yazılarak gidilmelidir ancak burada test kodu geliştirmeyi konu almadığımız için, yazılmış metodu test ederek ilerlemeye çalışalım.

Test edilecek metod şu şekilde belirlenebilir.

public JsonResult GetMenu()
{
     var menu = new List<Menu>
     {
         new Menu{ MenuID=1, MenuName="Hamburger, Cola, Cips"},
         new Menu{ MenuID=2, MenuName="Cheeseburger, Cola, Cips"},
         new Menu{ MenuID=3, MenuName="Pizza, Cola, Cips"}
     };

     return Json(menu,JsonRequestBehavior.AllowGet);
}

Yemek menüsünü JSON formatında sunan bir metod.

Bu metodun test edilmesi sırasında aslında bazı sorulara yanıt arıyoruz olacağız. Bir başka deyişle önce test metodunun yazıldığı senaryolarda iddalar doğrulanarak gidilir. Bizim senaryomusda ise GetMenu metodunun:

  • Tipinin JsonResult olduğu iddası
  • Boş veri döndürmediği iddası
  • Dönen verinin formatının ne olacağının iddası

ortaya atılarak doğrulanmaya çalışılmıştır.


[TestMethod]
public void GetMenu_ShouldReturn_JSON()
{
    // Arrange
    HomeController controller = new HomeController();

    // Act
    var result = controller.GetMenu() as JsonResult;

    // Assert
    Assert.IsInstanceOfType(result, typeof(JsonResult));
    Assert.IsNotNull(result.Data);
}

İlk iki iddamızı bu metod sayesinde dğrulayabiliriz. Bu test başarıyla geçecektir. Ancak metodun döndürdüğü verinin ne olduğunu veya string şeklinde alınıp şuna eşittir denilebilmesi için serileştirilme işleminden geçirilmelidir. Çünkü result.Data nesnesi object tipindedir, ve içeriği okunamaz durumdadır. Serileştirilme işlemi sonrasında işlenebilir bir somut veri elde edilebilir.

     Assert.AreEqual("", result.Data.ToString());

Şeklinde bir idda hata döndürecektir. Test Fail duruma düşecektir.

Aşağıdaki lekilde serileştirme işlemini gerçekleştirdiğimizde bu tür sorunlarla karşılaşmayacağız.

[TestMethod]
public void GetMenu_ShouldReturn_JSON()
{
     // Arrange
     HomeController controller = new HomeController();

     // Act
     var result = controller.GetMenu() as JsonResult;

     var serializer = new JavaScriptSerializer();
     var output = serializer.Serialize(result.Data);

     // Assert
     Assert.IsInstanceOfType(result, typeof(JsonResult));
     Assert.IsNotNull(result.Data);
     Assert.AreEqual(@"[{""MenuID"":1,""MenuName"":""Hamburger, Cola, Cips""},
                        {""MenuID"":2,""MenuName"":""Cheeseburger, Cola, Cips""},
                        {""MenuID"":3,""MenuName"":""Pizza, Cola, Cips""}]",
                        output);
}

Tekrar görüşmek dileğiyle…

ASP.NET MVC Returnurl

24 Nis

Web sayfalarımızda korunan bazı sayfalara yönlendirme yapan bağlantılara(link) tıklandığında, bu sayfanın koruma altında olduğunu belirtmek amacıyla kullanıcıyı sistem giriş sayfasına yönlendiririz. Kullanıcı, giriş yaptıktan sonra en son kaldığı sayfayı hatırlamayabilir. Bu gibi durumlar, genelde forum sayfalarına girdiğimizde karşımıza gelir. İstediğimiz konuyu görmek istediğimizde üye giriş sayfasına yönlendiriliriz ve giriş yaptıktan sonra kaldığımız yerden devam ederiz. Giriş yapıldıktan sonra kaldığımız sayfaya yönlendirilmezsek, tekrar arama motoruna gidip konu linkine tıklamak zorunda kalabiliriz.

Kendi hazırladığımız sistemlerde de bu gibi bir durum başımıza geldiğinde, üye giriş yaptığında son kaldığı sayfaya yönlendirilmesi gerektiğinde ne yapmalıyız?

ASP.NET MVC ile hazırlanan sistemlerde login işlemi sonrası en son geldiğimiz sayfaya yönlendirme yapmak için, en son sayfa URL’i returnUrl adında bir değişkende tutulur.

public ActionResult LogOn(LogOnModel model, string returnUrl)
{
      if (Membership.ValidateUser(model.UserName, model.Password))
      {
         if (!string.IsNullOrEmpty(returnUrl))
         {
              return Redirect(returnUrl);
         }
         else
         {
              return RedirectToAction("Index", "Home");
         }
      }

      return View(model);
}

Eğer returnUrl parametresi boş değilse direk o sayfaya yönlendirme yapılır, boş ise Index sayfasına yönlendirilir.

Bu durumda akla gelen ilk soru “link üzerinde returnUrl parametresi nasıl ayarlanmalı?” olacaktır.

@Html.ActionLink(“Giriş”, “LogOn”, “Account”, new { returnUrl=Request.RawUrl }, null)

Request.RawUrl property’sini returnUrl olarak belirliyoruz. Örneğin ürünler sayfasında bir işlem yaparken, giriş yapılması istenirse, kullanıcı adı ve şifre girildikten sonra tekrar ürünler sayfasına yönlendirilmiş oluruz.

Eğer login işlemi sonrasında direk Index sayfasına yönlendirilsin istiyorsak link aşağıdaki gibi olmalıdır.

@Html.ActionLink(“Giriş”,”LogOn”, “Account”)

Tekrar görüşmek ümidiyler…

Repository Pattern Nedir?

7 Nis

Veri merkezli yazılımların iş katmanlarından veriye ulaşım işlemleri sırasında meydana gelen ve gözardı edilen bazı ayrıntılar, yazılımın ilerki aşamalarında önümüze bir çığ misali yığılıp kalmaktadır. Özellikle katmanlı mimaride geliştirilen yazılımlarda iş kuralları ve katmanlar, düzgün  oluşturulmadığı taktirde bir işi N defa yapmak zorunda kalabiliriz. Ancak nesneye yönelik (object oriented) yapıların ilkelerinden biri de, bir kuralı bir kez belirleyip N kez çalıştırmaktır. Örneğin veritabanındaki öğrenciler tablosuna öğrenci eklemeyi yapan bir nesne ve bir metod olmalıdır.

İş katmanında dikkat edilmesi gereken bazı kurallar vardır. Bunlar:

  • Kod tekrarlarından kaçınmak
  • Hata yakalamayı kolaylaştırmak
  • Kod yazımını ve okunuşunu kolaylaştırmak
  • Test edilebilir bir yapı kurgulamak

Repository pattern de bizi bu kurallara uymaya zorlamaktadır. Peki repository pattern nedir?

Veri merkezli uygulamalarda veriye erişimin ve yönetimin tek noktaya indirilmesini sağlayan bir tasarım desenidir. Veri merkezli uygulama olması şart değil elbette. Ama bu yazıdaki uygulama veriye erişim kurallarını belirleyen bir örnek üzerinden yürütülecektir.

Örneğimiz, bir ASP.NET MVC web uygulaması içerisinden alınmış repository uygulanışını göstermektedir. Verileri, tek noktadan taşıma işlemini yapacak olan repository yapısı da Entity Framework altyapısını kullanmaktadır. Repository pattern için kullanılan teknoloji çokta önemli değildir. MVC veya Entity Framewok gibi detaylara bu örnekte gerek yoktur, yeri gelmişken bahsetmeden geçemedim. İstersek kendi veri katmanımızı (data layer) hazırlayarak da yola devam edebiliriz.

ASP.NET MVC mimarisinin tanıtımı konusunda en etkili örnek olan açık kaynak kodlu Nerddinner uygulaması üzerinden işlemlerimizi yürütmeye çalışacağız. Neden hazır bir uygulama üzerinden gittim? Kendim de bir sınıf hazırlayıp burada kullanabilirdim pek tabi. Ancak, Nerddinner uygulamasını merak edip bakmanızı da istedim, çok faydalı olacağından eminim.

Nerddinner nedir? Hikaye olarak, insanların yemek yiyebilecek yerleri internet üzerinden kaydedebilecekleri veya daha önceden, başkaları tarafından kaydedilen yemek yenilebilecek yerleri görebilmelerini sağlamak şeklinde özetlenebilir. Bu kısmı da yeri gelmişken kısaca özetledikten sonra konumuza dönebiliriz.

Projede yemek yerleri, Dinner tablosunda tutulmaktadır. Entity framework tarafında da, veritabaındaki Dinner tablosu Dinner sınıfı tarafından temsil edilmektedir. Bu noktada Dinner tablosuna CRUD(Create, Update, Delete) işlemlerini gerçekleştirebilmek için bir arayüz (interface) tanımlanır. Bu arayüzü uygulayan(implement) eden sınıflar Dinner tablosuna CRUD işlemlerini yapmaya hazır demektir.

Hazırladığımız interface aşağıdaki gibidir.


public interface IDinnerRepository {

       IQueryable<Dinner> FindAllDinners();
       IQueryable<Dinner> FindDinnersByText(string q);

       Dinner GetDinner(int id);

       void Add(Dinner dinner);
       void Delete(Dinner dinner);

       void Save();
}

Bu arayüzü uygulayan bir sınıfı da aşağıdaki gibi gösterebiliriz.

public class DinnerRepository : NerdDinner.Models.IDinnerRepository
{

       NerdDinnerEntities db = new NerdDinnerEntities();

       Public IQueryable<Dinner> FindDinnersByText(string q)
       {
              return db.Dinners.Where(d => d.Title.Contains(q)
                     || d.Description.Contains(q)
                     || d.HostedBy.Contains(q));
       }

       public IQueryable<Dinner> FindAllDinners()
       {
              return db.Dinners;
       }

       public Dinner GetDinner(int id)
       {
             return db.Dinners.SingleOrDefault(d => d.DinnerID == id);
       }

       public void Add(Dinner dinner)
       {
             db.Dinners.AddObject(dinner);
       }

       public void Delete(Dinner dinner)
       {
            foreach (RSVP rsvp in dinner.RSVPs.ToList())
                db.RSVPs.DeleteObject(rsvp);

            db.Dinners.DeleteObject(dinner);
      }

      public void Save()
      {
            db.SaveChanges();
      }

}

Buradaki NerdDinnerEntities tipi Entity Framework tarafına ait, ObjectContext tipinden türemiş ve kullanıcı tarafından oluşturulan nesneleri alıp kaydetme, güncelleme veya silme işlemleri için oluşturulmuş, motor(engine) görevindeki sınıftır.

Save metodu içerisinde de zaten NerdDinnerEntities tipinin SaveChanges metodu çağrılmıştır ki bu metod, yapılan değişiklikleri veritabanına yansıtır.

Artık Dinner tipindeki bir nesnenin veritabaına kaydedilmesi, güncellenmesi veya silinmesi gibi işlemlerden sorumlu olan sınıf DinnerRepository sınıfıdır. Başka hiçbir yerde bu işlemler için kod yazmaya gerek yoktur. Bir süre sonra, sistemde tarihe göre arama işlemi yapılmak istendiğinde, ilgili metod IDinnerRepository arayüzünde belirlenerek, DinnerRepository sınıfına uygulandığında işlem tamamdır. Tek sorumluluk(Single Responsibility)  DinnerRepository sınıfına aittir. Bu sayede yazılım kurallarından “Tek Sorumluluk” ilkesine de uymuş olduk.

Repository pattern sayesinde test edilebilirlik te mümkün hale gelmektedir. Bu daha ayrıntılı bir konu olduğu için ileriki yazılarımızda işlemeye çalışacağız.

Repository patternle karşılaşmadan önce, kendi kendime desenler oluşturup, tek noktadan veri yönetimini sağlamaya çalışıyordum. Ancak bu desen bana daha düzgün geldi ve artık bu deseni iyice benimsedim. Test gücünü de gördükten sonra vazgeçilmezim oldu. Herkese de tavsiye ederim.

Bu benim aklıma gelmişti dememek için

5 Nis

Bugün izlediğim bir video, düşüncelerime tercüman oldu adeta. Bu yüzden bu videoyu paylaşmak istedim. Ana fikir olarak, düşüncelerin gerçekleştirilebilmesi için inanarak işe başlamanın şart olduğu ve hiçbirşey için çok geç olmadığı şekilinde değerlendirebiliriz.

Daha fazla konuşmadan videoyla sizi başbaşa bırakmak istiyorum.

Video içeriğini metin olarakta çıkarttım.

Think when you were still a KID
Think when you were a KID, everything is POSSIBLE
You put the BLANKET, over your back, then you became a SUPERMAN
You take your mom’s BROOM and ride on it, then you became a WITCH
You climb up the TREE, not knowing that you might fell, you simply MADE IT
You just had an IDEA and made it happened, because YOU BELIVE
You were then, CREATIVE with strong BELIEF
As you become ADULT, things seems to be a little different
When you failed in your exam, you think “I can NEVER make it”
When you have a BRILLIANT marriage proposal, then you think “maybe it’s NOT the right time”
When you want to start a BUSINESS, you think “maybe it’s NOT as easy”
When you are ALREADY in the BUSINESS, you hard someone says do NOT take risk.
Then, when you have a BRILLIANT IDEA that,
Can create mode JOBS for pooper nations
Can provide a better LIVING for more people
Can make our nation PROUD
Can help the GROWTH of our nation and can create a better WORLD
And you said “I am NOT SURE…”
You are still CREATIVE but with more DOUBT
Then YEARS LATER, you heard … someone had the same idea as yours,
What makes him DIFFERENT from you?
His ACTION.
He puts his IDEA into reality.
He is the most promising Creative Young Entrepreneur of year.
His ACTION won him this recognition.
Then you started to think, How I WISHED I had taken ACTION That years ago…
THINK you were a KID, If that idea came by when you were still a kid, could you have taken ACTION???
When back then, you strongly belive…
It is never too late…, you just need to have an IDEA and make it happen
You still can, … if you BELIVE.
Creativity starts from BELIEF.

Dependency Injection Tasarım Deseni

4 Nis

Yazılım dünyasında ürünlerden daha çok prensiplere ve ilkelere önem verenlerdenim. Java veya C# dilinde yazmışım çokta dert değil benim için. Bir dilin yazım kurallarını ve ürün geliştirme araçlarını öğrenmek çokta uzun zaman almaz. Yazılım geliştirdiğimiz ortamların (IDE) pratiklerini anlamak çok önemlidir elbette. Ama yazılım prensipleri ve ilkelerini benimsemek, o ilkelere sadık kalarak yazılım geliştirmek bana göre daha önemlidir. Bu yazımızda da yazılım geliştiricilerin zamanla edindiği tecrübelereden biri olan Dependency Injection tasarım deseni üzerinde duracağız.

Resimde ne görüyorsunuz diye bir soru sorsam, süpheli süpheli bakmaya mı başlarsınız, yoksa USB anahtarlıklar ve iki anahtar mı dersiniz bilmiyorum. Ama sistematik bir görüntü var aslında resimde. Canı isteyen sistemdeki anahtarlıkları halkadan boşandırıp değiştirebilir. Çünkü halka esnek bir yapıda yani birbirine sabitlenmemiş. Aynı şekilde isteyen, ipliğin ucundaki USB aygıtlarını da istediği renkle değiştirebilir. Çünkü iplikte basit bir düğüm, dolayısıyla anahtarlık ve anahtarlar arasında gevşek bir bağ mevcut. Aynı şekilde USB aygıtlarının kapakları da birbirine uyumlu olduklarından  istenen renkte anahtarlık veya kapak seçilebilir ve ikili oluşturulabilir. Bu durum sistemin esnek olduğunu ve tüm bileşenler arasında gevşek bir bağlantı söz konusu olduğunu göstermekdir.

Yazılım tasarım desenlerinde de sıklıkla karşılaştığım “gevşek bağlılık” deyiminin de ifade etmek istediği şey yukarıdaki şekilde anlatıldığı gibidir. Nesnelerin birbirine uyumlu ve birbirini etkilemeyecek şeklide olmasıdır.

Yazılım tasarım desenlerinden biri olan, Dependency Injection (Bağımlılıkların Dışarıdan Enjekte Edilmesi) deseni de bileşenlerin(components) birbirine olan sıkı bağlarının gevşetilmesini ve yazılımın test edilebilir bir hale getirilmesini amaçlamaktadır. Test edilebilir tasarım konusuna daha sonraki yazılarda değinmek istiyorum. Şu anda ilgileneceğimiz konu tasarımdaki gevşek bağlılık(Loosely Coupled) üzerinden ilerleyecektir.

Bir örnek uygulama üzerinden devam etmelim. Senaryomuz bir şirketin çalışanlarının aylık ve günlük satış istatistiklerini veren bir sınıfın tasarlanması şeklinde olacaktır.

public interface IEmployeeStatistics
{
       decimal GetDailySales(int employeeId);
       decimal GetMonthlySales(int employeeId);
}

Yukarıdaki arayüz(interface), diğer bir deyişle sözleşme, employeeId bilgisi verilen çalışanın günlük ve aylık satış tutarlarını verecek şekilde hazırlanmıştır.

Aşağıdaki EmployeeStatistics sınıfı ise IEmployeeStatistics arayüzünden türetilmiş sınıfların getireceği istatistikleri dış dünyaya sunmaktan sorumlu olacaktır.

public class EmployeeStatistics
{
       private IEmployeeStatistics employeeStatistics;

       public EmployeeStatistics(IEmployeeStatistics employeeStatistics)
       {
              this.employeeStatistics = employeeStatistics;
       }

       public decimal GiveMeDailyReports(int employeeId)
       {
              return employeeStatistics.GetDailySales(employeeId);
       }

       public decimal GiveMeMonthlyReports(int employeeId)
       {
              return employeeStatistics.GetMonthlySales(employeeId);
       }

}

EmployeeStatistics sınıfının kurucu metodundaki bağımlılık, IEmployeeStatistics arayüzü tarafından sağlanmıştır.

Dış dünyaya sunum yapan sınıf olan EmployeeStatistics sınıfı, eline gelen istatistiklerin hangi veritabanından kullandığını, hangi aşamalardan geçtiğini, ne zorluklar çektiğini bilmez. Hatta sınıfın adını bile bilmez. EmployeeStatistics sınıfı için önemli olan sadece constructor metoduna gelecek olan tipin IEmploeyeeStatistics arayüzünü uygulamış (implement) olmasıdır. Aynen yukardaki resimde anahtarlığa bağlanan USB anahtarlıkların ne şekilde olduğunun önemsiz olduğu gibi.

Şimdi de MSSQL veritabanından istatistik çeken sınıfı oluşturalım.

public class EmoleeStatisticsFromMSSQL : IEmployeeStatistics
{
       public decimal GetDailySales(int employeeId)
       {
              //.....
              //.....
              // MSSQ'den getirme işlemleri

              decimal result = 5214M;

              return result;
       }

       public decimal GetMonthlySales(int employeeId)
       {
              //.....
              //.....
              // MSSQ'den getirme işlemleri

              decimal result = 23514M;

              return result;
        }
}

Eğer EmployeeStatistics sınıfı içerisinde IEmployeeStatistics tipini değilde şu anda oluşturduğumuz EmployeeStatisticsFromMSSQL tipini direk kullansaydık iki sınıf arasında (EmployeeStatistics ve EmployeeStatisticsFromMSSQL) sıkı bir bağ oluşacaktı. Bu durumda, sistemde çalışma zamanında(runtime) EmployeeStatisticsFromMSSQL sınıfı yerine EmployeeStatisticsFromOracle gibi bir değişimi imkansız hale getirecekti. Ancak bu sıkı bağı IEmployeeStatistics arayüzü ortadan kaldırdı ve esnek bir bağ oluşturdu. Artık çalışma zamanında istenen sınıf devreye alınabilir. İstediğimiz sınıfı nasıl devreye alacağımız ise EmployeeStatistics sınıfına baktığımızda anlaşılabilmektedir. Sınıf içerisine Constructor seviyesinde bir enjeksiyon yapılmaktadır. Bağımlılıklar dışarıdan sınıf içerisine enjekte edilmektedir.

Bir süre sonra şirketiniz veritabanı yazılımını MSSQL yerine Oracle tarafına geçirmek istediğinde yapmamız gereken sadece EmployeeStatisticsFromOracle sınıfını oluşturmak olacaktır.

public class EmoleeStatisticsFromOracle : IEmployeeStatistics
 {
       public decimal GetDailySales(int employeeId)
       {
              //.....
              //.....
              // Oracle'dan getirme işlemleri

              decimal result = 5214M;

              return result;
       }

       public decimal GetMonthlySales(int employeeId)
       {
               //.....
               //.....
               // Oracle'dan getirme işlemleri

               decimal result = 23514M;

               return result;
       }

}

Bu sayede yazılımımız değiştirilmeye kapalı , geliştirilmeye açık hale gelmiş olacaktır. Yani yazılım prensiplerinden biri olan Open Closed (Gelişime açık değişime kapalı) prensibine de uygun hale gelmiştir.

Sınıfın kullanımı ise şu şekilde olacaktır.

public class Run
{
       public void Raporla()
       {
             IEmployeeStatistics reportProvider = getRaporUretici();

             EmployeeStatistics statistics = new EmployeeStatistics(reportProvider);

             var daily = statistics.GiveMeDailyReports(1);
             var monthly = statistics.GiveMeMonthlyReports(1);
       }

       private IEmployeeStatistics getRaporUretici()
       {
             //Web.config dosyasından hangi veritabanından çalışılacak, öğren.
             //"OR" ise Oracle, "MS" ise MSSQL

             var db = getDBFromConfig();

             IEmployeeStatistics reportProvider;

             if(db == "MS")
                 reportProvider = new EmoleeStatisticsFromMSSQL();
             if(db == "OR")
                 reportProvider = new EmoleeStatisticsFromOracle();
             else 
                 reportProvider = new NullStatistics();

             return reportProvider;
        }
}

Artık kodumuz değiştirilmeden geliştirilebilir haldedir. Yani config dosyasından belirlenen bir flag ile çalışan sisteme müdahale edilebilmektedir. Bu imkanı kolaylaştıran yöntem ise bağımlılıkların dışarıdan alınmasını sağlayan, Dependency Injection tasarım desenidir.

Kullanıma hazır bir yapıyı oluşturabildik. Tekrar görüşmek ümidiyle.