Reputation: 12662
Here is my dilemma.
1) I want to create instances of different classes depending on a single parameter --> Solution : Factory Pattern. The nice thing about Factory is zero parameter constructor.
2) Each of these classes have different constructors. For example
class PhotosProviderFacebook(val refActivity: WeakReference<Activity>)
class PhotosProviderLocal(val context: Context, val loaderManager: LoaderManager)
I thought about Builder Pattern. See how Builder pattern is done in Kotlin
Is there a design pattern that respect these constraints ?
1) First build an instance depending on a parameter. Possibly without passing yet the constructor inputs.
2) Pass parameters in constructor
Factory + Builder are just an idea. Maybe there is a simpler way.
Upvotes: 3
Views: 526
Reputation: 1552
This question reminds me a talk on how the GoF design patterns are implemented in a functional language.
Yes, the answer is "with functions".
In the coroutines library, there is the type Channel
and the function Channel
.
public fun <E> Channel(capacity: Int = RENDEZVOUS): Channel<E> =
when (capacity) {
RENDEZVOUS -> RendezvousChannel()
UNLIMITED -> LinkedListChannel()
CONFLATED -> ConflatedChannel()
else -> ArrayChannel(capacity)
}
In Kotlin, object construction does not need the new
keyword. So the invocations of this function looks just like using a constructor. Constructors for different XyzChannel
s are called in the body of the Channel
function.
You said "I want to create instances of different classes depending on a single parameter", and the above function does exactly the same thing. It may not be a good idea for your function to start with a uppercase letter though.
But the question remains, how does one pass different types of parameters to the function? The answer is to wrap them using subclasses of a same superclass.
This will be similar to the other answer, but utilizes one great feature of Kotlin – sealed types.1
sealed class CreationParams
class PhotosProviderFacebookParams(val refActivity: WeakReference<Activity>) : CreationParams()
class PhotosProviderLocal(val context: Context, val loaderManager: LoaderManager) : CreationParams()
fun create(params: CreationParams) = when (params) {
is CreationParamsFacebook -> PhotosProviderFacebook(params.refActivity)
is CreationParamsLocal -> PhotosProviderLocal(params.contex, params.loadManager)
}
Since the class is sealed
, the compiler can check for exhaustiveness, there is no need for an extra enum
.2
There is also no manual type casting. The compiler does it for you.
sealed
types comes from the sum types of the algebraic data type, an idea that comes from the 70s. It is rather sad to see it became mainstream only recently.Upvotes: 2
Reputation: 1675
You probably might want to use a strategy pattern in combination with good old polymorfism
First, you can add an abstract class to your concrete classes
abstract class PhotosProvider
class PhotosProviderFacebook(val params: CreationParamsFacebook): PhotosProvider()
class PhotosProviderLocal(val params: CreationParamsLocal): PhotosProvider()
Then, you can do the same for the parameters, that may change depending on the class (I used Strings instead of your classes for simplicity and to make it compile)
abstract class CreationParams
class CreationParamsFacebook(val refActivity: String): CreationParams()
class CreationParamsLocal(val context: String, val loaderManager: String): CreationParams()
Now, I code the strategy pattern for each creation method (see how I can safely cast to the correct parameters for each class with as)
abstract class PhotosProviderCreationStrategy {
abstract fun create(params: CreationParams) : PhotosProvider
}
class PhotosProviderFacebookCreationStrategy: PhotosProviderCreationStrategy() {
override fun create(params: CreationParams): PhotosProvider {
return PhotosProviderFacebook(params as CreationParamsFacebook)
}
}
class PhotosProviderLocalCreationStrategy: PhotosProviderCreationStrategy() {
override fun create(params: CreationParams): PhotosProvider {
return PhotosProviderLocal(params as CreationParamsLocal)
}
}
Finally, in order to redirect to the correct strategy I use a map with a specific type. You may also create an abstration that includes the type and the abstract params (which can be easily be converted with Jackson if you receive the date from a web service)
enum class CreationType {
FACEBOOK_PROVIDER, LOCAL_PROVIDER
}
class PhotosProviderCreator(
private val strategies: HashMap<CreationType, PhotosProviderCreationStrategy> = hashMapOf(
CreationType.FACEBOOK_PROVIDER to PhotosProviderFacebookCreationStrategy(),
CreationType.LOCAL_PROVIDER to PhotosProviderLocalCreationStrategy())
) {
fun create(creationType: CreationType, creationParams: CreationParams): PhotosProvider {
return strategies[creationType]?.create(creationParams)?:
throw RuntimeException("Strategy not found for type: $creationType")
}
}
Upvotes: 3