Jared Rieger
Jared Rieger

Reputation: 1

Kotlin Interface method abstraction

I'm exploring the Substitution principal and from what I've understood about the principal is that a sub type of any super type should be passable into a function/class. Using this idea in a new section of code that I'm writing, I wanted to implement a abstract interface for a Filter like so

interface Filter {
    fun filter(): Boolean
}

I would then imagine that this creates the contract for all classes that inherit this interface that they must implement the function filter and return a boolean output. Now my interpretation of this is that the input doesn't need to be specified. I would like it that way as I want a filter interface that guarantee the implementation of a filter method with a guarantee of a return type boolean. Does this concept even exists in Kotlin? I would then expect to implement this interface like so

class LocationFilter {
    companion object : Filter {
        override fun filter(coord1: Coordinate, coord2: Coordinate): Boolean {
            TODO("Some business logic here")
        }
    }
}

But in reality this doesn't work. I could remove remove the filter method from the interface but that just defeats the point of the whole exercise. I have tried using varargs but again that's not resolving the issue as each override must implement varargs which is just not helpful. I know this may seem redundant, but is there a possibility to have the type of abstraction that I'm asking for? Or am I missing a point of an Interface?

Upvotes: 0

Views: 739

Answers (3)

kerelape
kerelape

Reputation: 124

The whole this is wrong!

First, OOP is a declarative concept, but in your example the type Filter is just a procedure wrapped in an object. And this is completely wrong.

Why do you need this type Filter? I assume you need to get a collection filtered, so why not create a new object that accepts an existing collection and represents it filtered.

class Filtered<T>(private val origin: Iterable<T>) : Iterable<T> {
    
    override fun iterator(): Iterator<T> {
        TODO("Filter the original iterable and return it")
    }
}

Then in your code, anywhere you can pass an Iterable and you want it to be filtered, you simply wrap this original iterable (any List, Array or Collection) with the class Filtered like so

acceptCollection(Filtered(listOf(1, 2, 3, 4)))

You can also pass a second argument into the Filtered and call it, for example, predicate, which is a lambda that accepts an element of the iterable and returns Boolean.

class Filtered<T>(private val origin: Iterable<T>, private val predicate: (T) -> Boolean) : Iterable<T> {
    
    override fun iterator(): Iterator<T> {
        TODO("Filter the original iterable and return it")
    }
}

Then use it like:

val oddOnly = Filtered(
    listOf(1, 2, 3, 4),
    { it % 2 == 1 }
)


Upvotes: 0

gidds
gidds

Reputation: 18547

my interpretation of this is that the input doesn't need to be specified.

Where did you get that interpretation from?

You can see that it can't be correct, by looking at how the method would be called.  You should be able to write code that works for any instance of Filter — and that can only happen if the number and type of argument(s) is specified in the interface.  To use your example:

val f: Filter = someMethodReturningAFilterInstance()
val result = f.filter(coord1, coord2)

could only work if all implementations used two Coordinate parameters. If some used one String param, and others used nothing at all, then how would you call it safely?

There are a few workarounds you could use.

If every implementation takes the same number of parameters, then you could make the interface generic, with type parameter(s), e.g.:

interface Filter<T1, T2> {
    fun filter(t1: T1, t2: T2): Boolean
}

Then it's up to the implementation to specify which types are needed.  However, the calling code either needs to know the types of the particular implementation, or needs to be generic itself, or the interface needs to provide type bounds with in variance.

Or if you need a variable number of parameters, you could bundle them up into a single object and pass that.  However, you'd probably need an interface for that type, in order to handle the different numbers and types of parameters, and/or make that type a type parameter on Filter — all of which smells pretty bad.

Ultimately, I suspect you need to think about how your interface is going to be used, and in particular how its method is going to be called.  If you're only ever going to call it when the caller knows the implementation type, then there's probably no point trying to specify that method in the interface (and maybe no point having the interface at all).  Or if you'll want to handle Filter instances without knowing their concrete type, then look at how you'll want to make those calls.

Upvotes: 1

broot
broot

Reputation: 28302

Let's think about it a little. The main point of abstraction is that we can use Filter no matter what is the implementation. We don't need to know implementations, we only need to know interfaces. But how could we use Filter if we don't know what data has to be provided to filter? We would need to use LocationFilter directly which also defeats the point of creating an interface.

Your problem isn't really related to Kotlin, but to OOP in general. In most languages it is solved by generics/templates/parameterized types. It means that an interface/class is parameterized by another type. You use it in Kotlin like this:

interface Filter<in T> {
    fun filter(value: T): Boolean
}

object LocationFilter : Filter<Coordinate> {
    override fun filter(value: Coordinate): Boolean {
        TODO()
    }
}

fun acquireCoordinateFilter(): Filter<Coordinate> = LocationFilter

fun main() {
    val coord: Coordinate = TODO()
    val filter: Filter<Coordinate> = acquireCoordinateFilter()

    val result = filter.filter(coord)
}

Filter is parameterized, meaning that we can have a filter for filtering strings (type is: Filter<String>), for filtering integers (Filter<Int>) or for filtering coordinates (Filter<Coordinate>). Then we can't use e.g. Filter<String> to filter integers.

Note that the code in main() does not use LocationFilter directly, it only knows how to acquire Filter<Coordinate>, but the specific implementation is abstracted from it.

Also note there is already a very similar interface in Java stdlib. It is called Predicate.

Upvotes: 1

Related Questions