Feedbacker
Feedbacker

Reputation: 969

Pass generic parameter optionally?

I have the following NetworkUseCase interface which processes a network request. P is the type of requestData and R the type of responseData.

interface NetworkUseCase<in P, out R> {
    fun process(requestData: P) : R
}

The way it is normally used is the following:

class ConcreteUseCase : NetworkUseCase<ConcreteRequestData, ConcreteResponseData> {
    override fun process(requestData: ConcreteRequestData): ConcreteResponseData {
        ...
    }
}

// It is called like this
val responseData = ConcreteUseCase().process(requestData)

However, there are some use cases where there are no requestData needed. In that case, there should be no requestData argument passed. But I am not sure how to achieve that.

val responseData = ConcreteUseCase().process()

What I have done so far is to set the type of requestData to Unit but then I still need to pass Unit as a parameter:

class ConcreteUseCase : NetworkUseCase<Unit, ConcreteResponseData> {
    override fun process(requestData: Unit): ConcreteResponseData {
        ...
    }
}

// It is called like this
val responseData = ConcreteUseCase().process(Unit)

Is there a way to achieve not being forced to always pass an argument in process()?

Upvotes: 2

Views: 92

Answers (3)

goedi
goedi

Reputation: 2113

Seems that you need a nullable input type with a default value

interface NetworkUseCase<in P, out R> {
    fun process(requestData: P? = null) : R
}

class ConcreteUseCase : NetworkUseCase<ConcreteRequestData, ConcreteResponseData> {
    override fun process(requestData: ConcreteRequestData?): ConcreteResponseData {
        //...
    }
}

Then you can just call it like that:

val responseData = ConcreteUseCase().process()

EDIT:

This solution is gonna work just fine when it is necessary to pass a parameter to the process function:

val resquestData: ConcreteRequestData = ...
ConcreteUseCase().process(resquestData)

It is due to the fact that a non-nullable type is a subtype of its correposding nullable one.

EDIT 2:

Revisiting this answer I realized that the concrete use case may always have no parameter expected. The interface still works and the concrete class should be like that:

class ConcreteUseCase : NetworkUseCase<Unit, ConcreteResponseData> {
    override fun process(requestData: Unit?): ConcreteResponseData {
        //...
    }
}

Upvotes: 1

jimmymorales
jimmymorales

Reputation: 317

You could also define a type alias for a NetworkUseCase without request data:

typealias NoRequestDataNetworkUseCase<R> = NetworkUseCase<Unit, R>

and add an extension function to the type alias like @Tenfour04 mentioned.

fun <T> NoRequestDataNetworkUseCase<T>.process() = process(Unit)

so you will use it like this:

class ConcreteUseCase : NoRequestDataNetworkUseCase<ConcreteResponseData> {
    override fun process(requestData: Unit): ConcreteResponseData {
        // ...
    }
}

// It is called like this
val responseData = ConcreteUseCase().process()

But I also think is better to define a different abstraction for network use cases that doesn't need request data.

interface NoRequestDataNetworkUseCase<out R> {
    fun process() : R
}

Upvotes: 0

Tenfour04
Tenfour04

Reputation: 93834

You could write an extension function to overload it so any concrete version that uses Unit for the first type would automatically have access to this simpler overload:

fun <T> NetworkUseCase<Unit, T>.process() = process(Unit)

But if the number of arguments for the function are not always the same, why put this function in an interface at all? You wouldn't be able to use it in an abstract way.

Upvotes: 3

Related Questions