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ştirmeye değinmeden önce, bu sayfada bağımlılık eklemenin işleyiş şekliyle ilgili daha genel bir genel 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ın ihtiyaç duyduğu bir nesneyi alabilmesinin üç yolu vardır:

  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. Parametre olarak 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şturucusu parametre olarak Engine 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 bunları elde etmesini sağlamak yerine bunları sağlarsınız.

Bir örnekle açıklayalım. Bağımlılık yerleştirme kullanılmadığında, kodda kendi Engine bağımlılığını oluşturan Car öğesini temsil etmek aşağıdaki gibi görünür:

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 araba sınıfı

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

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

  • Engine platformuna duyulan aşırı bağımlılık, testi daha zor hale getirir. Car, gerçek bir Engine örneği kullandığından farklı test durumları için Engine öğesini değiştirmek üzere test ikilisi 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 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 kullanır. Car, Engine kaynağına bağlı olduğundan uygulama Engine örneği oluşturur ve daha sonra bu örneği Car örneği oluşturmak için kullanır. DI tabanlı bu yaklaşımın avantajları:

  • Car yeniden kullanılabilirliği. 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ğine geçmektir. Car yine de herhangi bir değişiklik yapmadan çalışır.

  • Car kolayca test edilebilir. 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 şekildedir. Bir sınıfın bağımlılıklarını kurucuya iletirsiniz.

  • 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 yönteminde bağımlılıklar sınıf oluşturulduktan sonra somutlaştırılır. 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 el ile bağımlılık ekleme veya manuel bağımlılık ekleme adı verilir. Car örneğinde yalnızca 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 ekleme de çeşitli sorunlara yol açar:

  • Büyük uygulamalar için tüm bağımlılıkları almak ve bunları doğru şekilde bağlamak, büyük miktarda ortak metin kodu gerektirebilir. Çok katmanlı bir mimaride, bir üst katman için nesne oluşturmak üzere bunun 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 bir motor, şanzıman, şasi ve başka parçalara ihtiyacınız olabilir.

  • Bağımlılıkları aktarmadan önce oluşturamadığınız durumlarda (örneğin, geç başlatmalar veya uygulamanızın akışları için nesneleri kapsama gibi) bellekteki bağımlılıklarınızın ömrünü yöneten özel bir container (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ği 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 çözen, tamamen statik ve derleme zamanı bağımlılıkları sunar.

Bağımlılık yerleştirmeye alternatifler

Bağımlılık yerleştirmeye bir alternatif de hizmet bulucu kullanmaktır. Hizmet konum bulucunun tasarım kalıbı, sınıfların somut bağımlılıklardan ayrıştırılmasını da iyileştirir. Bağımlılıklar oluşturup depolayan ve daha sonra bu bağımlılıkları isteğe bağlı olarak sağlayan hizmet bulucu adlı bir sınıf oluşturuyorsunuz.

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 bulucu kalıbı, öğelerin tüketilme şekli açısından bağımlılık yerleştirmeden farklıdır. Hizmet konum bulucu kalıbıyla, sınıflar kontrol sahibi olur ve nesnelerin yerleştirilmesini ister; bağımlılık eklemeyle 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 belirleyicinin gerektirdiği bağımlılıkların toplanması, tüm testlerin aynı global 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ıfın dışarıdan neye ihtiyacı olduğunu 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 neden olabilir.

  • 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 tarafından önerilen kitaplıktır. Hilt, projenizdeki her Android sınıfı için container sağlayarak ve bunların yaşam döngülerini sizin yerinize otomatik olarak yöneterek uygulamanızda DI 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 yararlanabilmek için popüler Dagger DI kitaplığının üzerine geliştirilmiştir.

Hilt hakkında daha fazla bilgi edinmek için Hilt ile Bağımlılık Yerleştirme konusuna bakın.

Sonuç

Bağımlılık ekleme, 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ı: Bir bağımlılığın uygulamalarını değiştirmek daha kolaydır. Kontrolün tersine çevrilmesi sayesinde kodların yeniden kullanımı iyileştirildi. 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 anında kontrol edilebilir.

  • Test kolaylığı: Bir sınıf bağımlılıklarını yönetmez. Bu nedenle, sınıfı test ederken farklı durumlarınızın tümünü test etmek için farklı uygulamalardan geçebilirsiniz.

Bağımlılık yerleştirmenin avantajlarını tam olarak anlamak için Manuel bağımlılık yerleştirme bölümünde gösterildiği gibi bunu 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