Home > Blockchain >  How can a method from an interface can be called in the given project? (Whether I'm wrong?)
How can a method from an interface can be called in the given project? (Whether I'm wrong?)

Time:01-16

This is the project I'm trying to understand. I'm trying to understand what goes after what in this project. What confuses me, is that getFeatures() call in ViewModel. It seems that it calls an abstract function in the interface which is implemented in the file DefaultMapsRepository. I don't understand how it works. I thought that call should be of function from DefaultMapsRepository class where getFeatures() is implemented. So, as I understand, getFeatures() call in ViewModel calls the not implemented method from the interface and then that interface somehow finds the implementation of it and that override fun getFeatures() code runs its body. Correct me if I'm wrong. But If I create another implementation of getFeatures() how would that interface choose which implementation to use? I heard somewhere that interface of repository makes code easier to test but it's hard to understand how if I don't know how this all code works. I sometimes like to test how code works in the console to make code look simpler but I can't replicate the same situation because I'm not able to run the code if I add something to fun main() constructor. I think such structure of a project is used a lot and I want to understand it very well.

CodePudding user response:

An interface is just a contract. If CoolInterface has a property someValue: Int and a method doThing(): Boolean, then anything that implements that interface is guaranteed to have that property and that method.

The MapsViewModel class takes a MapsRepository parameter, and MapsRepository is just an interface with one function:

suspend fun getFeatures(): Resource<FeaturesResponse>

You can pass in anything that implements that interface, which DefaultMapsRepository does:

class DefaultMapsRepository @Inject constructor(
    ...
) : MapsRepository {

Since it implements that interface, it needs to implement that getFeatures function, which it does:

// override is the keyword that shows you're not just declaring a new function,
// it's implementing something abstract in a superclass/interface (or overriding
// an open function)
override suspend fun getFeatures(): Resource<FeaturesResponse> {

So you can pass in anything at all as your repository, so long as it implements MapsRepository, meaning you need some implementation of that getFeatures function. It doesn't "decide" a version to use, it calls it on whatever you pass in. So you can test it with whatever mock class or object you like

val mockRepo = object : MapsRepository {
    override suspend fun getFeatures(): Resource<FeaturesResponse> {
        return Resource.Error("oh snap")
    }
}

val viewModel = MapsViewModel(mockRepo, whateverTheOtherThingIs)
// calls getFeatures() on the repo
viewModel.getFeatures()

If none of that makes any sense, you should read up on what interfaces and polymorphism are!

CodePudding user response:

A different way of explaining it that might help. Suppose you have this interface and class.

interface Pet {
    fun sayName()
}

class Dog(val name: String): Pet {
    override fun sayName() {
        println(name)
    }
}

If some function asks for a Pet, you can pass it an instance of anything that implements the Pet interface.

fun sayPetName(pet: Pet) {
    pet.sayName()
}

From the function's point of view, it doesn't have to know what class was passed to it. It just knows that whatever was passed to it is an instance of a Pet, and must therefore have a non-abstract sayName() function.

Even though the function sayName() is abstract inside the definition of the interface, it would be impossible to create an instance of a class where there is an abstract function. There's no such thing as an abstract function in an instance of a class. You can define abstract classes that have abstract functions, but you cannot create instances of them.

You could pass a Dog instance to this function since it qualifies as a Pet. When the function calls pet.sayName(), it is up to the instance of Dog to respond. The function itself doesn't have to know anything about what class type was passed to it.

In the same way, if you have an instance of a Pet that was passed to your constructor, your class can use the sayName() function on it. If you pass the class a Dog instance, even though the class is storing it in a property that is marked Pet and the class doesn't know it has a Dog, if it calls sayName() on it, the Dog instance will use its implementation of sayName().

class SomeClass(val pet: Pet) {
    fun sayThePetName() {
        pet.sayName()
    }
}

If a Dog were passed to the constructor of the above class, it does not get "downgraded" into a Pet interface that has an abstract function. It's still a Dog instance even if that specific type is not exposed to the class in this scope. The code inside the class doesn't know or care that it's a Dog. It just knows it has a reference to some actual class instance that has the functions defined by the Pet interface.

  •  Tags:  
  • Related