22 Temmuz 2014 Salı

Android İçin Dependency Injection Çözümü: Dagger


Selam,

Yazılım pattern'leriyle ilgileniyorsanız mutlaka duymuşsunuzdur dependency injection kavramını. Özünde bu desenin amacı; yazılım birimlerinin birbiriyle gevşek bağlı(loosely coopled) olmasını sağlamaktır ki bu sayede birimlerin birinde meydana gelen değişim diğer birim(ler)in de değiştirilmesini gerektirmez ve değişime daha açık bir yapı kurgulamış olursunuz. (bkz. SOLID Open-Closed ve Inversion of Control)

Geliştirme süreçlerinde sınıfların sorumluluklarını yerine getirirken başka sınfılardan faydalanması kaçınılmaz bir durum, misal A sınfı bir görevi yerine getirmek için B sınıfından faydalanıyorsa A sınıfı B sınıfına bağımlılık(dependency) oluşturmuştur. İşte bu noktada A sınıfının B sınıfının nesne örneğini new anahtar sözcüğü ile kendi içinde oluşturması yerine dışardan enjekte edilmiş şekilde(constructor injection, setter injection) almasını sağlayan yöntemdir dependency injection. Tabii burada B tipinin somut(concrete) bir tip olmak yerine soyut(abstract) bir tip olması da çok önemli zira A tipi B tipi hakkında ne kadar bilgi sahibi olursa o derece B tipindeki değişimlerden etkilenir.

Dependcy Injection(DI) hakkında Türkçe de dahil olmak üzere zibilyon tane anlatımı nette bulmak mümkün, o yüzden konuya bu kadar değinmiş olmakla yetineceğim. Piyasada pek çok di-ioc contaniner framework'ü bulmak mümkün. Android içinse bu kadar çeşitlilik söz konusu değil. Android'in ana komponenti olan Activity tipinin örneklenmesi için getirdiği kısıtlardan dolayı misal "Spring for Android" kütüphanesinin di desteği yoktur. Bunun dışında android için di çözümü olarak, Google Guice'in android uyarlaması RoboGuice ve Square'in Dagger kütüphanesi var. RoboGuice'in di için reflection kullanması dolasıyla kaybettirdiği performans ve view-injection üzerine yoğunlaşmasından dolayı ortaya çıkan eksen kayması, Square'e duyduğum sevgi-saygı gibi sebeplerden benim tercihim Dagger :]

Mobil cihazların cpu, ram vs kaynaklarının kısıtlı oluşu, piyasada halen düşük konfigrasyona sahip nispeten eski akıllı telefonların yadsınamaz ve ısrarcı varlığı, dagger'ı geliştirenlerin dikkatinden kaçmamış olacak ki, bağımlılıkları çözümleme için refection yerine compile time'da anotasyonları kullanmayı yeğleyerek daha performanslı ve güvenlikli bir çözüme gitmişler. Neyse sözü çok da uzatmadan implemantasyona geçelim:

Dagger'i android projelerinde kullanmak için öncelikle bir Module Class'ı oluşturmamız gerekli:
@Module( injects = { MainActivity.class } )
public class WelcomeModule {

    @Provides
    @Singleton
    Welcomer provideWelcomer(){
        return new DefaultWelcomer();
    }

}

WelcomeModule adlı sınıfın yapısınına bakınca hemen anotasyonlar dikkatinizi çekiyor olmalı. Yukarıdan aşağıya doğru bu anotasyonları ele alırsak:

@Module: Dagger container'ına bu sınıfın bir modül olduğunu söyler. Injects parametresinde bu modül ile bağımlıkları enjekte edilecek olan sınıf(lar) belirtilir.

@Provide: Bağımlığı(örnekte Welcomer adlı tek metotlu, interface) sağlayan/üreten metotları belirtir. İsmi önemli değil fakat "provide" prefix'i ile başlaması bir convention, dönüş tipi ise sağladığı depedency'dir.

@Singleton: Bu anotasyon ile işaretlenen provider metodu, sağlanan bağımlılığın runtime'da bir kez üretir, sonraki talepler için aynı nesneyi döner.

Dagger bu anotasyonlar sayesinde derleme zamanında bağımlılıkları çözer ve performans kazancı sağlar. Yazının başında da belirttiğim gibi, sınıfların direkt somut tipler yerine soyut tipler üzerinden bağımlılık oluşturması önemli. Bu sebepten örneğimizde bahsi geçen bağımlılık Welcomer arayüz sınıfıdır, talep edilmesi durumunda WelcomeModule tipi de bu isteği DefaultWelcomer implemantasyonuyla karşılar. DI konusuna odaklanmak adına bu tipi basit tuttum, gerçekçi senaryolar için bu tip misal veritabanından veri getiren bir dao tipi olabilir.
// Welcomer.java
public interface Welcomer {

    String sayHello(String name);

}

// DefaultWelcomer.java
public class DefaultWelcomer implements Welcomer {

    @Override
    public String sayHello(String name) {
        return "hello "+ name + " from DefaultWelcomer";
    }

}

Modülü oluşturduğumuza göre sıra geldi bir diğer önemli dagger komponentine: ObjectGraph. ObjectGraph dependency'lerin nesnelerinin tutulduğu yapıdır. Android uygulamaları için Application sınıfı, uygulama seviyesinde erişilebilir, singleton bir yapı olduğu için ObjectGrap'ı burda tutmak mantıklı:
public class App extends Application {

    private ObjectGraph objectGraph;

    @Override
    public void onCreate() {
        super.onCreate();

        initObjectGraph();
    }

    private void initObjectGraph() {
        // objectGraph nesnesini olustururken modüller üzerinden bağımlılık bilgilerini geçmiş oluyoruz
        objectGraph = ObjectGraph.create(new WelcomeModule());
    }
    
    // uygulama geneli erisim icin, bilhassa activity'ler içinden
    public ObjectGraph getObjectGraph() {
        return this.objectGraph;
    }

}

Evet gerekli alt yapıyı oluşturduk sıra dagger'ın marifetlerini görmeye geldi; yukarda modülü tanımlarken Welcomer tipi için destek sağladık. Bu durumda MainActivity adlı activity sınıfımız Welcomer tipini kullanıyor(bağımlılık) olsun ve biz Welcomer bağımlılığını dagger'ı kullanarak setter injection ile sağlayalım:
public class MainActivity extends Activity {

    // dependency
    // setter injection
    @Inject
    Welcomer welcomer;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // sınıfı objectGraph'e tanıtıyoruz
        ((App)getApplication()).getObjectGraph().inject(this);

        // eğer welcomer bağımlılığı sağlanmazsa NullPointerException almamız gerekli
        String msg = welcomer.sayHello("passenger!");
        ((TextView)findViewById(R.id.tv)).setText(msg);
    }

}

Hemen uygulamayı test edelim:


DefaultWelcomer tipinin implementasyonundan anlayacağımız üzere, beklediğimiz çıktıyı aldık. Örnekte Welcomer bağımlılığını setter injection ile elde ettik, ObjectGraph sınıfının get() metoduna Welcomer tipini geçerek de aynı sonucu elde edebilirdik.

Girdide bahsi geçen mavenized android projesine buradan ulaşabilirsiniz.

Biraz kompleks bir konu umarım yeterince açık anlatabilmişimdir, mutlu ramazanlar, esen kalın.

5 yorum:

  1. sagolasın Rize.nin cilgin cocugu :]

    YanıtlaSil
  2. Harika bir anlatım, türedi kalitesi.

    YanıtlaSil
    Yanıtlar
    1. goruyorum ki Rize'li androidci kardeslerim dogru yolda! : ]

      Sil
  3. Bu yorum yazar tarafından silindi.

    YanıtlaSil
  4. Abi çok iyi bir yazı olmuş, eline sağlık.

    YanıtlaSil