Service Locator と DI どちらを使うか、という話。

Service Locator とは?

Create a service locator that contains references to the services and that encapsulates the logic that locates them. In your classes, use the service locator to obtain service instances

サービスへの参照を含み、それらを見つけるロジックをカプセル化するサービスロケーターを作成します。 クラスからは、サービスロケータを使用してサービスインスタンスを取得します。

The Service Locator Pattern | Microsoft Docs

どちらがどうなのか?

The choice between Service Locator and Dependency Injection is less important than the principle of separating service configuration from the use of services within an application.

Service Locator と Dependency Injection のどちらを選択するかは、アプリケーション内でサービスの設定とそれの利用を分離するという原則よりも重要ではありません。

Inversion of Control Containers and the Dependency Injection pattern

Service location is not an anti-pattern. There are anti-patterns that involve use of a service locator along with other similar constructs. There are anti patterns that involve the use of DI. Most devises we use in programming involve both (virtuous) patterns, and anti-patterns, which is really just a grand way of saying pros and cons. Generally speaking people who summarise the world in terms of only pros or only cons are said to be engaging in splitting.

Service Locator 自体はアンチパターンではありません。 他の要素と共に Service Locator を使用するときにアンチパターンが存在します。 DI を利用する場合にもアンチパターンがあります。 私たちがプログラミングで使用するほとんどの案は、良いパターンと悪いパターンの両方を含みます。 一般的に言えば、長所のみまたは短所のみの観点から要約する人々は、分割に従事していると言われています。

Splitting (also called black and white thinking or all-or-nothing thinking) is the failure in a person’s thinking to bring together both positive and negative qualities of the self and others into a cohesive, realistic whole. It is a common defense mechanism used by many people. The individual tends to think in extremes (i.e., an individual’s actions and motivations are all good or all bad with no middle ground.)

分割(白黒思考またはオールオアナッシング思考とも呼ばれます)は、自己と他者の肯定的な面と否定的な面の両方の性質をまとまりのある現実的な全体にまとめることができないことです。 それは多くの人々によって使用されている一般的な防御メカニズムです。 個人は極端に考える傾向があります(すなわち、個人の行動や動機はすべて悪い、またはすべてが悪いこともありません)。

I need to take a look about and see what discussions there may be on the subject of polarised views and whether they are more prevalent among programmers than other professions.

私は偏極的な見解に関してどのような議論ができるのか、またそれらについて議論します。 他の職業よりもプログラマーの間で流行しています。

Service locator vs dependency injection. | Weird Scenes Inside The Goldmine

Dependency injection is all the rage now on Android but there are also some other patterns worth considering for managing dependencies. A Service Locator is a very simple pattern which can be implemented in few lines (50?) of code and can sometimes be a viable alternative or complement to DI frameworks like Dagger 2.

依存性注入は現在Android上で大流行していますが、依存性を管理するために考慮する価値のある他のパターンもいくつかあります。 Service Locatorは、ほんの数行(50?)のコードで実装できる非常に単純なパターンであり、Dagger 2のようなDIフレームワークに代わる実行可能な代替手段である場合もあります。

The Service Locator is considered by some (or many) as an anti-pattern, but I would argue that you always have to choose the right tool for the job (and it’s good to have a variety of tools in your toolbox). In some cases the Service Locator can be an extremely simple and efficient solution. It can also get handy in case you have constraints like APK size, method count, build speed or overall complexity.

Service Locatorは、一部(または多数)によってアンチパターンと見なされていますが、仕事に適したツールを常に選択する必要があると思います(ツールボックスにさまざまなツールを用意しておくことをお勧めします)。 場合によっては、Service Locatorは非常に単純で効率的なソリューションになります。 APKサイズ、メソッド数、ビルド速度、全体的な複雑さなどの制約がある場合にも便利です。

Service Locator pattern in Android - INLOOPX - Medium

‘Dependency Injection’ is only one form of the D in the SOLID design principles, ie. Dependency Inversion. I do highly recommend Dependency Inversion in any application that has a need to interact with dynamic data, or that has any level of business logic. Both Service Locator and Dependency Injection can be an anti-pattern or a viable pattern depending on the scenario.

「依存性注入」は、SOLID設計原則におけるDの1つの形式にすぎません。 依存関係の反転 動的データとやり取りする必要があるアプリケーション、またはあらゆるレベルのビジネスロジックを持つアプリケーションでは、Dependency Inversionを強くお勧めします。 シナリオに応じて、Service LocatorとDependency Injectionの両方をアンチパターンまたは実行可能パターンにすることができます。

Dependency Injection is over Hyped – Candor Developer

まとめ

「Service Locator」は、「シンプルなDI」てなイメージでいようと思います。

しかし、Dagger2を使うほうが後々便利なのかもしれません。

👉 Service Locator is an Anti-Pattern 


coroutine の使い方 - Android Architecture Blueprints v2 #1

気がついたら「v2」です。MVVM のみとなって、 RxJava は姿を消していますが。

👉 googlesamples/android-architecture: A collection of samples to discuss and showcase different architectural tools and patterns for Android apps. 



現在、4つのバリエーションが公開されていますが、以下は共通。


- kotlin
- coroutine
- single activity
- architecture component
- navigation component + fragment
- presentetion layer(per page) = fragment + view model
- reactive ui = live data + data binding
- data layer = repositpory + local(room) + remote
- datalayer one shot operations(no listener or data streams)

非同期処理には、どのバリエーションも「coroutine」使う。

どのように coroutine を使っているか、を定型化しておきたい。

関数 coroutineScope

「kotlinx-coroutines-core」の関数の「coroutineScope」 が目についたので見ておく。


suspend fun <R> coroutineScope(
    block: suspend CoroutineScope.() → R
): R (source)

CoroutineScope を作成し、このスコープで指定された suspend ブロックを呼び出します。その外側のスコープから coroutineContext を継承しますが、そのコンテキストのJobをオーバーライドします。

この関数は、処理の並列分解用に設計されています。 このスコープ内のいずれかの子コルーチンが失敗すると、このスコープは失敗し、残りのすべての子はキャンセルされます(supervisorScope との違いを参照してください)。 与えられたブロックとそのすべての子コルーチンが完了するとすぐに戻ります。
スコープの使用例は次のようになります。


suspend fun showSomeData() = coroutineScope {

  val data = async(Dispatchers.IO) { // <- extension on current scope
     // ... load some UI data for the Main thread ...
  }

  withContext(Dispatchers.Main) {
    doSomeWork()
    val result = data.await()
    display(result)
  }
}

この例のスコープの意味は次のとおりです。

1. showSomeDataは、データがロードされてUIに表示されるとすぐに戻ります。
2. doSomeWorkが例外をスローすると、非同期タスクはキャンセルされ、showSomeDataはその例外を再スローします。
3. showSomeData の外部スコープが取り消されると、開始された async ブロックとwithContext ブロックの両方が取り消されます。
4. 非同期ブロックが失敗すると、withContext はキャンセルされます。

現在のジョブが外部でキャンセルされた場合、メソッドは CancellationException をスローします。このスコープ内に未処理の例外がある場合(たとえば、このスコープ内の起動で開始されたクラッシュコルーチンから)は、対応する未処理のThrowableをスローします。

coroutineScope - kotlinx-coroutines-core

実際の記述

coroutine を使った非同期処理は 各 ViewModel を起点に記述されています。


class TasksViewModel(
    private val tasksRepository: TasksRepository
) : ViewModel() {

    fun loadTasks(forceUpdate: Boolean) {
        viewModelScope.launch {

            // 対応する Repository メソッドをコール
            // val tasksResult = tasksRepository.getTasks(forceUpdate)

        }
    }

}

👉 android-architecture/TasksViewModel.kt at master · googlesamples/android-architecture 

Repository を展開して、スコープ周りを抜き出してみると、


class TasksViewModel(
    private val tasksRepository: TasksRepository
) : ViewModel() {

    fun loadTasks(forceUpdate: Boolean) {
        viewModelScope.launch {

            withContext(ioDispatcher) {
            
                // ...            

            }

        }
    }

}

というこれまで通り withContext() を使った dispatcher の切り換え。

他には、

前述の coroutineScope 関数を使った記述が、単独、並列、入れ子の3種類。


override suspend fun saveTask(task: Task) {
    coroutineScope {
        launch { tasksRemoteDataSource.saveTask(it) }
        launch { tasksLocalDataSource.saveTask(it) }
    }
}


override suspend fun clearCompletedTasks() {
    coroutineScope {
        launch { tasksRemoteDataSource.clearCompletedTasks() }
        launch { tasksLocalDataSource.clearCompletedTasks() }
    }
    withContext(ioDispatcher) {
        cachedTasks?.entries?.removeAll { it.value.isCompleted }
    }
}


override suspend fun deleteAllTasks() {
    withContext(ioDispatcher) {
        coroutineScope {
            launch { tasksRemoteDataSource.deleteAllTasks() }
            launch { tasksLocalDataSource.deleteAllTasks() }
        }
    }
}

👉 android-architecture/DefaultTasksRepository.kt at master · googlesamples/android-architecture 

今後、さらに簡潔に記述されていくのだろうが、意図は今のが理解しやすいと思いメモ。




Android Paging Library と Retrofit

例えば、このパターン、Network only。

ここでは、PositionalDataSource を拡張するが、他のタイプのDataSource拡張でも同じ。

compositeDisposable のように viewModelScope をはるばる持ってきたにもかかわらず、


class RemoteDataSource(
  private val coroutineScope: CoroutineScope,
  private val service: RemoteService,
  private val s: String
) : PositionalDataSource<Item>() {

  @ExperimentalCoroutinesApi
  override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Item>) {
    Timber.d("RemoteDataSource#loadInitial: ${Thread.currentThread().name}")
    ...

loadInitial() 内は、メインスレッドではなく、別スレッドで実行されている。


Timber.d("RemoteDataSource#loadInitial: ${Thread.currentThread().name}")


D/RemoteDataSource: RemoteDataSource#loadInitial: arch_disk_io_0
D/RemoteDataSource: RemoteDataSource#loadInitial: arch_disk_io_1
D/RemoteDataSource: RemoteDataSource#loadInitial: arch_disk_io_3

これらは、Android Architecture Component が作成した独自スレッド。

よって、同期なRetrofitの実行処理で良い、となる。

If I use enqueue with Retrofit 2.3 it will doesn't work but if i do a .execute() the LiveData is correctly triggered

Retrofit 2.3で、enqueue() を使用しても機能しませんが、execute() を実行するとLiveDataが正しくトリガーされます。

Android Paging Library LiveData> is triggered before the end of the api call

公式リファレンスにも記述はあるという。

To display data from a backend server, use the synchronous version of the Retrofit API to load information into your own custom DataSource object.

バックエンドサーバーからのデータを表示するには、同期バージョンのRetrofit APIを使用して、独自のカスタムDataSourceオブジェクトに情報をロードします。

Network only - Paging library overview

しかし、DiffUtilを使ったリフレッシュなアニメーションが実行されない。

「手間がかかる」でなく「沼にハマる」ことが最近は多くなった。

APIの仕様がおせっかいすぎやしないか。

しきいを下げようとして、余計に混乱させるばかり。

👉 あなたは Android Architecture Component をどう思いますか? 
👉 Fragment と Toolbar の歴史の話 - Qiita