Search code examples
kotlingenericstypesfactory

Kotlin generic factories


I'm trying to create an AnimalFactory that returns generic factories for making different types of Animals, depending on the arguments passed to the AnimalFactory.

Here's the code:

interface Animal {
    fun talk(): String
}

class Cow: Animal {
    override fun talk(): String {
        return "mooo"
    }
}

class Cat: Animal {
    override fun talk(): String {
        return "miow"
    }
}

class Dog: Animal {
    override fun talk(): String {
        return "bark"
    }
}

object AnimalFactory {
    fun <T: Animal> AnimalMakerFactory(type: String): AnimalMaker<T> {
        val maker = when (type) {
            "cat" -> CatMaker()
            "dog" -> DogMaker()
            else -> CowMaker()
        }
        
        return maker
    }
}

interface AnimalMaker<out T: Animal> {
    fun make(): T
}

class CatMaker: AnimalMaker<Cat> {
    override fun make(): Cat {
        return Cat()
    }
}

class DogMaker: AnimalMaker<Dog> {
    override fun make(): Dog {
        return Dog()
    }
}

class CowMaker: AnimalMaker<Cow> {
    override fun make(): Cow {
        return Cow()
    }
}

I get a type exception:

Type mismatch.
Required: AnimalMaker<T>
Found: AnimalMaker<Animal>

I thought that AnimalMaker would solve this, but apparently not. Why is AnimalMaker<T> not of type AnimalMaker<Animal> here?


Solution

  • The return value of the function is AnimalMaker<T> and not AnimalMaker<Animal> because that’s what you declared as the return type. The variable maker is indeed an AnimalMaker<Animal> but that isn’t a match for what the function is supposed to return because T could be a subtype of Animal.

    You declared your function as having a generic type of T: Animal. Generic types are always an input to the function. In this case, it doesn’t make sense to use a generic input to the function because there’s no way to enforce that the type given is a match for the input String it corresponds with. To make your function work, you can remove <T : Animal and declare that it returns AnimalMaker<Animal>.

    A little more explanation. There are two reasons why you might want to use generics in a function signature.

    1. Enforce input parameter types.
    2. Determine the output type.

    You might use generics for one or both reasons (but the second can only be done by itself in a useful way by using reified generics, except in very specific cases where the returned class won’t be producing anything).

    In your case, your input generic is not used to enforce the input parameter since that is just a String. To use it for the second reason, you would have to cast your return value’s type to the unknown (to the compiler) type T which would be unsafe because there’s no way to know if the input type given at the call site is a valid match for the given input String. And if you expected the call site to pass the right type, it would be redundant and error prone to also require a matching String to be passed.

    Edit: If you know the input type at compile time, then you can do this with reified generics. Get rid of the String input. It would look like this:

    object AnimalFactory {
        inline fun <reified T: Animal> AnimalMakerFactory(): AnimalMaker<T> {
            @Suppress("UNCHECKED_CAST")
            return when (T::class) {
                Cat::class -> CatMaker()
                Dog::class -> DogMaker()
                Cow::class -> CowMaker()
                else -> error("No factory found for type ${T::class}.")
            } as AnimalMaker<T>
        }
    }
    
    // Example usage
    val someCatFactory = AnimalFactory.AnimalFactoryMaker<Cat>()
    val cat: Cat = someCatFactory.make()
    

    Inside this function, it is up to you to match the types up correctly, or there will be a ClassCastException at runtime. It seems logically it should be able to automatically cast them, but the compiler isn't sophisticated enough (yet?).