Android'de bağımlılık ekleme

Bağımlılık ekleme (DI), programlamada yaygın olarak kullanılan ve Android geliştirme için çok uygun olan bir tekniktir. DI ilkelerine uyarak iyi uygulama mimarisinin temelini hazırlarsınız.

Bağımlılık yerleştirmeyi uygulamak size aşağıdaki avantajları sağlar:

  • Kodun yeniden kullanılabilirliği
  • Yeniden düzenleme kolaylığı
  • Test kolaylığı

Bağımlılık yerleştirme ile ilgili temel bilgiler

Özellikle Android'de bağımlılık yerleştirmeden bahsetmeden önce, bu sayfada bağımlılık yerleştirmenin işleyiş şekliyle ilgili genel bir bakış sunulmaktadır.

Bağımlılık yerleştirme nedir?

Sınıflar genellikle diğer sınıflara referans gerektirir. Örneğin, bir Car sınıfının Engine sınıfına referans vermesi gerekebilir. Bu gerekli sınıflara bağımlılıklar adı verilir. Bu örnekte Car sınıfı, çalışacak Engine sınıfının bir örneğine sahip olmaya bağlıdır.

Bir sınıf, ihtiyaç duyduğu bir nesneyi üç şekilde alabilir:

  1. Sınıf, ihtiyaç duyduğu bağımlılığı oluşturur. Yukarıdaki örnekte Car, kendi Engine örneğini oluşturup başlatır.
  2. Başka bir yerden almak. Context alıcıları ve getSystemService() gibi bazı Android API'leri bu şekilde çalışır.
  3. Verinin parametre olarak sağlanmasını sağlayın. Uygulama, sınıf oluşturulduğunda bu bağımlılıkları sağlayabilir veya her bir bağımlılığa ihtiyacı olan işlevlere aktarabilir. Yukarıdaki örnekte Car oluşturucu, parametre olarak Engine değerini alır.

Üçüncü seçenek ise bağımlılık yerleştirmedir. Bu yaklaşımda, sınıfın bağımlılıklarını alır ve sınıf örneğinin bağımlılıklarını elde etmesini sağlamak yerine bunları sağlarsınız.

Bir örnekle açıklayalım. Bağımlılık ekleme olmadan, kodda kendi Engine bağımlılığını oluşturan bir Car temsil edilir:

Kotlin

class Car {

    private val engine = Engine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}

Java

class Car {

    private Engine engine = new Engine();

    public void start() {
        engine.start();
    }
}


class MyApp {
    public static void main(String[] args) {
        Car car = new Car();
        car.start();
    }
}
Bağımlılık enjeksiyonu yapılmayan araç sınıfı

Car sınıfı kendi Engine öğesini oluşturduğundan bu, bir bağımlılık yerleştirme örneği değildir. Bu, aşağıdaki nedenlerle sorunlu olabilir:

  • Car ve Engine birbiriyle sıkı sıkıya bağlıdır. Car örneği, bir Engine türü kullanır ve hiçbir alt sınıf ya da alternatif uygulama kolayca kullanılamaz. Car kendi Engine yapısını oluşturacak olsaydı yalnızca Gas ve Electric türündeki motorlar için aynı Car öğesini yeniden kullanmak yerine iki tür Car oluşturmanız gerekirdi.

  • Engine ürününün aşırı bağımlılığı, testi daha zor hale getirir. Car, gerçek bir Engine örneğini kullandığından farklı test durumları için Engine öğesini değiştirmek üzere test yinelemesi kullanmanızı engeller.

Bağımlılık yerleştirme ile kod nasıl görünür? Başlatma sırasında kendi Engine nesnesini oluşturan her bir Car örneği yerine, oluşturucuda parametre olarak bir Engine nesnesi alır:

Kotlin

class Car(private val engine: Engine) {
    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val engine = Engine()
    val car = Car(engine)
    car.start()
}

Java

class Car {

    private final Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
    }
}


class MyApp {
    public static void main(String[] args) {
        Engine engine = new Engine();
        Car car = new Car(engine);
        car.start();
    }
}
Bağımlılık enjeksiyonu kullanılan araba sınıfı

main işlevi, Car değerini kullanır. Car, Engine öğesine bağlı olduğundan uygulama, Engine örneği oluşturur ve daha sonra bunu Car örneği oluşturmak için kullanır. DI tabanlı bu yaklaşımın avantajları:

  • Car yeniden kullanılabilir. Car için farklı Engine uygulamalarını aktarabilirsiniz. Örneğin, Car tarafından kullanılmasını istediğiniz ElectricEngine adında yeni bir Engine alt sınıfı tanımlayabilirsiniz. DI kullanırsanız tek yapmanız gereken güncellenmiş ElectricEngine alt sınıfının bir örneğini iletmektir. Car başka değişiklik yapmadan çalışmaya devam eder.

  • Car kolayca test edilir. Farklı senaryolarınızı test etmek için iki kez test edebilirsiniz. Örneğin, Engine için FakeEngine adlı bir test ikilisi oluşturabilir ve bunu farklı testler için yapılandırabilirsiniz.

Android'de bağımlılık eklemenin iki temel yolu vardır:

  • Oluşturucu Yerleştirme. Bu, yukarıda açıklanan yöntemdir. Sınıfın bağımlılıklarını kurucuya aktarırsınız.

  • Alan Yerleştirme (veya Setter Yerleştirme). Etkinlikler ve parçalar gibi belirli Android çerçeve sınıfları, sistem tarafından örneklenir. Bu nedenle, kurucu yerleştirme mümkün değildir. Alan yerleştirme, bağımlılıklar sınıf oluşturulduktan sonra örneklendirilir. Kod şöyle görünür:

Kotlin

class Car {
    lateinit var engine: Engine

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.engine = Engine()
    car.start()
}

Java

class Car {

    private Engine engine;

    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
    }
}

class MyApp {
    public static void main(String[] args) {
        Car car = new Car();
        car.setEngine(new Engine());
        car.start();
    }
}

Otomatik bağımlılık yerleştirme

Önceki örnekte, bir kitaplığa ihtiyaç duymadan farklı sınıfların bağımlılıklarını kendiniz oluşturdunuz, sağladınız ve yönettiniz. Buna elle bağımlılık ekleme veya manuel bağımlılık yerleştirme denir. Car örneğinde yalnızca tek bir bağımlılık vardı ancak daha fazla bağımlılık ve sınıf, bağımlılıkların manuel olarak yerleştirilmesini daha sıkıcı hale getirebilir. Manuel bağımlılık yerleştirme de çeşitli sorunlara yol açar:

  • Büyük uygulamalarda, tüm bağımlılıkları almak ve bunları doğru şekilde bağlamak büyük miktarda standart kod gerektirebilir. Çok katmanlı bir mimaride, üst katman için bir nesne oluşturmak üzere altındaki katmanların tüm bağımlılıklarını sağlamanız gerekir. Somut bir örnek vermek gerekirse, gerçek bir araba yapmak için motora, şanzımana, şaseye ve başka parçalara ihtiyacınız olabilir. Bir motorun da silindirlere ve bujilere ihtiyacı olabilir.

  • Bağımlılıkları iletmeden önce oluşturamadığınızda (örneğin, geç başlatmalar veya uygulamanızın akışları için nesneleri kapsama aldığınızda) bellekteki bağımlılıklarınızın kullanım ömrünü yöneten özel bir kapsayıcı (veya bağımlılık grafiği) yazıp sürdürmeniz gerekir.

Bağımlılık oluşturma ve sağlama sürecini otomatik hale getirerek bu sorunu çözen kitaplıklar vardır. Bunlar iki kategoriye ayrılır:

  • Çalışma zamanında bağımlılıkları bağlayan yansıma tabanlı çözümler.

  • Derleme zamanında bağımlılıkları bağlamak için kod oluşturan statik çözümler.

Dagger; Java, Kotlin ve Android'de kullanılan ve Google tarafından yönetilen popüler bir bağımlılık ekleme kitaplığıdır. Dagger, sizin için bağımlılık grafiğini oluşturup yöneterek uygulamanızda DI'yi kullanmayı kolaylaştırır. Guice gibi yansıma tabanlı çözümlerin geliştirme ve performans sorunlarının çoğunu ele alan, tamamen statik ve derleme zamanı bağımlılıkları sunar.

Bağımlılık yerleştirme alternatifleri

Bağımlılık yerleştirmeye alternatif olarak hizmet bulucu kullanabilirsiniz. Hizmet bulucu tasarım deseni, sınıfların somut bağımlılıklardan ayrılmasını da iyileştirir. Bağımlılıkları oluşturan ve depolayan, ardından talep üzerine bu bağımlılıkları sağlayan hizmet bulucu adlı bir sınıf oluşturursunuz.

Kotlin

object ServiceLocator {
    fun getEngine(): Engine = Engine()
}

class Car {
    private val engine = ServiceLocator.getEngine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}

Java

class ServiceLocator {

    private static ServiceLocator instance = null;

    private ServiceLocator() {}

    public static ServiceLocator getInstance() {
        if (instance == null) {
            synchronized(ServiceLocator.class) {
                instance = new ServiceLocator();
            }
        }
        return instance;
    }

    public Engine getEngine() {
        return new Engine();
    }
}

class Car {

    private Engine engine = ServiceLocator.getInstance().getEngine();

    public void start() {
        engine.start();
    }
}

class MyApp {
    public static void main(String[] args) {
        Car car = new Car();
        car.start();
    }
}

Hizmet konum belirleyicisi kalıbı, öğelerin tüketilme şekli açısından bağımlılık yerleştirme işleminden farklıdır. Hizmet bulucu kalıbıyla sınıflar kontrol sahibi olur ve nesnelerin yerleştirilmesini ister; bağımlılık yerleştirme ile uygulama, kontrole sahiptir ve gerekli nesneleri proaktif olarak ekler.

Bağımlılık yerleştirme ile karşılaştırıldığında:

  • Bir hizmet konum bulucunun gerektirdiği bağımlılıkların toplanması, tüm testlerin aynı küresel hizmet bulucuyla etkileşimde bulunması gerektiğinden kodun test edilmesini zorlaştırır.

  • Bağımlılıklar API yüzeyinde değil sınıf uygulamasında kodlanır. Sonuç olarak, sınıfa dışarıdan ne gerektiğini bilmek daha zordur. Sonuç olarak, Car veya hizmet bulucuda bulunan bağımlılıklarda yapılan değişiklikler, referansların başarısız olmasına yol açarak çalışma zamanı veya test hatalarına yol açabilir.

  • Uygulamanın tamamının kullanım ömrü dışında bir konuyu kapsamak istiyorsanız nesnelerin ömürlerini yönetmek daha zordur.

Android uygulamanızda Hilt'i kullanma

Hilt, Android'de bağımlılık yerleştirme için Jetpack'in önerdiği kitaplıktır. Hilt, projenizdeki her Android sınıfı için container'lar sağlayarak ve bunların yaşam döngülerini sizin için otomatik olarak yöneterek uygulamanızda DI işlemini yapmanın standart bir yolunu tanımlar.

Hilt, Dagger'ın sağladığı derleme zamanı doğruluğu, çalışma zamanı performansı, ölçeklenebilirlik ve Android Studio desteğinden yararlanmak için popüler DI kitaplığı Dagger'ın üzerine kurulmuştur.

Hilt hakkında daha fazla bilgi edinmek için Dependency Injection with Hilt (Hilt ile Bağımlılık Yerleştirme) sayfasına bakın.

Sonuç

Bağımlılık yerleştirme, uygulamanıza aşağıdaki avantajları sağlar:

  • Sınıfların yeniden kullanılabilirliği ve bağımlılıkların ayrıştırılması: Bağımlılık uygulamalarını değiştirmek daha kolaydır. Kontrolün tersine çevrilmesi sayesinde kodların yeniden kullanımı iyileştirilir. Sınıflar artık bağımlılıklarının nasıl oluşturulduğunu kontrol etmez, bunun yerine herhangi bir yapılandırmayla çalışır.

  • Yeniden düzenleme kolaylığı: Bağımlılıklar, API yüzeyinin doğrulanabilir bir parçası haline gelir. Böylece uygulama ayrıntıları olarak gizlenmek yerine, nesne oluşturma zamanında veya derleme zamanında kontrol edilebilir.

  • Test kolaylığı: Bir sınıf bağımlılıklarını yönetmez. Bu nedenle, test ederken farklı durumlardan geçerek tüm farklı durumlarınızı test edebilirsiniz.

Bağımlılık yerleştirmenin avantajlarını tam olarak anlamak için bunu, Manuel bağımlılık yerleştirme bölümünde gösterildiği gibi uygulamanızda manuel olarak denemeniz gerekir.

Ek kaynaklar

Bağımlılık yerleştirme hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara bakın.

Sana Özel