Description
Consider the use case when an API/library exposes a channel that will be consumed in multiple locations within an app.
I have implemented this pattern as something like this:
class DataSource {
private val _data: BroadcastChannel<Data> = ...
// Public API
val data: ReceiveChannel<Data>
get() = _data.openSubscrption()
}
This is risky, as it is not obvious to consuming code that data
returns a new channel instance which must be cancelled every time.
Another way is like this:
class DataSource {
private val data: BroadcastChannel<Data> = ...
}
Now it is obvious what is happenning, because the consuming code must call openSubscription()
itself. However, the downside is that consuming code can close or even inject elements into the BroadcastChannel
which may break the API.
I propose to create ReadOnlyBroadcastChannel<E>
and then modify BroadcastChannel<E>
to inherit from ReadOnlyBroadcastChannel<E>
, like so:
interface ReadOnlyBroadcastChannel<E> {
fun openSubscription(): ReceiveChannel<E>
}
interface BroadcastChannel<E>: ReadOnlyBroadcastChannel<E>, SendChannel<E> {
...
}
This way the library API can become like this:
class DataSource {
private val _data: BroadcastChannel<Data> = ...
// Public API
val data: ReadOnlyBroadcastChannel<Data> = _data
}
Now, the BroadcastChannel
is safe from being modified by client code (of course it can still be type-cast...), and the usage of the api is clearer.