17 Nisan 2014 Perşembe

Çok Parametre Alan Metotlar ve Builder Pattern


Merhaba Arkadaşlar,

Bildiğiniz üzere java'da optional parameter ve named arguments desteği yok dolayısıyla bazen çalıştığımız sınıflar içerisinde çok parametre alan constructor ve metotlarla karşılabiliyoruz, bu da ilgili sınıfın kullanımını ve okunabilirliğini zorlaştıran bir durum. Özellikle nesne inşası sırasında istenen parametrelerden çoğu kez 2-3 tanesi bizi ilgilendiriyor fakat ihtiyaç duyduğumuz parametreler için uygun overload olmaması yüksek ihtimal, üstelik ilgi parametrelerin çeşitli kombinasyonları için overlaod'lar yazmak başlı başına bir yük.

Örnek üstünden gidersek, uygulama içerisinde http istekte bulunmak için elimizdeki sınıfa ait aşığdaki gibi method overload'ları olsun:
public class SimpleHttpClient() { 
 ..
 public String sendRequest(String url);
 public String sendRequest(String url, HttpMethod method, Map params);
 public String sendRequest(String url, HttpMethod method, Map params, Map headers);
 public String sendRequest(String url, HttpMethod method, Map params, Map headers, long timeout);
 ...
}
Sınfı tasarımında farkedeceğiniz gibi url dışındaki parametreler opsiyonel. Şimdi biz bu sınıfı kullanarak "http://xx/api" url'indeki restful servise belirli bir timeout için istekte bulunmak istiyoruz, bu durumda son overload versiyonunu kullanacağız:
String jsonResult = new SimpleHttpClient().request("http://xx/api", null, null, null, 4*1000);
Gördüğünüz gibi çok da hoş olmayan bir metot çağrımı ortaya çıktı. Metot çağrımı daha okunaklı ve kullanışlı hale getirmek için ilgili parametreler bir sınıf altında toplanıp metoda parametre olarak, bu sınıfın nesne örneği geçilebilir:
public class HttpRequest() { 
 // kısa tutmak adına getter-setter koymuyorum
 public String url,
 public HttpMethod method;
 ..
 public HttpRequest(String url){ this.url = url; }
}

public class SimpleHttpClient() { 
 ..
 public String sendRequest(HttpRequest request);
 ..
}
Bu durumda http isteğinde bulunduğumuz kod aşağıdaki şekli alacaktır:
String url = "http://xx/api";
HttpRequest request = new HttpRequest(url);
request.timeout = 4*1000;

String jsonResult = new SimpleHttpClient().request(request);
Evet bu haliyle SimpleHttpClient sınıfı kullanımı daha derli toplu oldu. Çok parametreli çağrım probleminin yapıcı metotlar üzerindeki yansıması yazılım literatüründe telescoping constructor olarak geçiyor ve bu bir anti-pattern. Problemdeki benzerlik dolaysıyla elimizde duruma çözüm olarak Builder Pattern'nini kullanalım, bu sayede SimpleHttpClient sıfının api'si daha hoş bir hal alacaktır. Builder Pattern kompleks sınıfların nesne üretim sürecinini istemciden gizleyen creational bir pattern'dir. HttpRequest sınıfına bu deseni Fluent Interface ile uygulayalım:
public class HttpRequest {
    public String url;
    public HttpMethod method;
 ..

    private HttpRequest(Builder builder) {
        url = builder.url;
        method = builder.method;
        ..
    }

    public static class Builder{
        private String url;
        private HttpMethod method;
        ..

  // url zorunlu
        public Builder(String val) { 
            url = val;
        }

        public Builder usingMethod(HttpMethod val) {
            method = val;
            return this;
        }

        public Builder withParams(Map val) {
            params = val;
            return this;
        }
  ..

        public HttpRequest build(){
            return new HttpRequest(this);
        }
    }
}
Bakalım baştaki metot çağrımımızın son hali nasıl olmuş:
HttpRequest reqest = new HttpRequest.Builder("url")
                .withTimeout(4*100)
                .build();
Gördüğünüz üzere artık SimpleHttpClient sınıfı kullanımı çok daha kolay/okunabilir hale geldi. Günümüz modern yazılım çatılarında benzer yaklaşımlar sıklıkla uygulanıyor örneğin; android içinde diyalog(AlertDialog.Builder), bildirim(Notification.Builder), animasyon(ViewPropertyAnimator) oluştururken, entity framework içinde sorgularda, configuring ve property mapping yapılarında vs.

Popüler ide'ler için builder oluşturan plugin vs'ler mevcut, misal intellij için InnerBuilder'ı deneyebilirsiniz, zamandan kazanmış olursunuz.

Son kullanıcının artan, sürekli değişen istekleri gibi faktörler dolaysıyla yazılım projeleri artık çok daha kapsamlı ve takım çalışmasını mecburi kılıyor. Yazılım projelerinde ortak çalışma söz konusu olduğunda, özellikle de bakım süreçlerinde yazılım codebase'inin okunaklı olmasının önemi aşikar. Hal böyle olunca biz yazılımcıların kod yazarken "problemi çözen kodu yazdım, oldu bitti gitti" mantığını bir kenara bırakıp, "okunabilir kod" kriterini aklımızın bir köşesine yazıp, gerekli özeni göstererek geliştirme yapmamız elzem.

Daha temiz, anlaşılır kod yazma üzerine güzel kitaplar mevcut, ben Robert C. Martin'in "Clean Code" adlı kitabını okudum, bence her yazılımcı bu ve benzeri bir kitabı okumalı. Girdiyi yazarın okunabilirlik üzerine olan güzel bir sözüyle bitiyorum, mutlu günler.

"The ratio of time spent reading [code] versus writing is well over 10 to 1 [therefore] making it easy to read makes it easier to write."

4 yorum:

  1. Ellerine sağlık, her satırını okudum hepsi çok değerli, daha fazla Clean Code Snippet'ları bekliyoruz:)

    YanıtlaSil
  2. sagolasın Hakan, "clean code" olayı son zamanlarda cok karsıma cıkıyor, ilgimi cekiyor, vakit bulursam ustune giderim konunun :]

    YanıtlaSil
  3. Eline sağlık üstad, clean code'a en ihtiyaç duyduğum anda en iyi örneklerle hızır gibi yeyistin. Devamını bekliyoruz :-D

    YanıtlaSil
  4. Teşekkürler ustad, devamini getirecegim insallah :]

    YanıtlaSil