Just checking quickly - am I right that one reason...
# getting-started
r
Just checking quickly - am I right that one reason to use an abstract class with abstract vals rather than an interface is you can only use
by lazy
to memoize calculated vals on an abstract class, not on an interface?
Copy code
// compiles, only runs expensiveOperationOn(things) once
abstract class FooAbstractClass {
  abstract val things: List<Thing>
  val thingsSupportSomething by lazy { expensiveOperationOn(things) }
}

// compiles but fails at runtime
abstract class FooAbstractClassFails {
  abstract val things: List<Thing>
  val thingsSupportSomething = expensiveOperationOn(things)
}

// does not compile
interface FooInterfaceDoesNotCompile {
  val things: List<Thing>
  val thingsSupportSomething = expensiveOperationOn(things)
}

// does not compile
interface FooInterfaceDoesNotCompileByLazy {
  val things: List<Thing>
  val thingsSupportSomething by lazy { expensiveOperationOn(things) }
}

// compiles, runs expensiveOperationOn(things) every time `thingsSupportSomething` is dereferenced
interface FooInterface {
  val things: List<Thing>
  val thingsSupportSomething: Boolean get() = expensiveOperationOn(things)
}
j
Sort of. But you can still use an interface with
thingsSupportSomething
without any initializer, and then implement it with a private/internal abstract class to handle laziness if need be. The fact that the property is computed doesn't matter to the interface definition. It's an implementation detail
r
Yes, I was just seeking to avoid the repetition in all the implementations
j
If you want to share implementation details between different subclasses, then that's definitely doable with an abstract class, but it's orthogonal to how you define the interface
☝️ 1
You could say that just the abstract class is enough in this case, but with time I found interfaces useful even then (it enables delegation for example)
r
I nearly always prefer interfaces, not least because they are safer (avoiding the
compiles but fails at runtime
problem) but also because they decouple better - you can make anything implement an interface. But computed values without repetition are a bit of a pain. I suppose I could do the interface & base abstract class pattern, but there's quite a lot of repetition there too 🙂.
Frustration with that pattern:
Copy code
sealed interface Foo
abstract class BaseFoo : Foo
class Foo1(): BaseFoo()
class Foo2(): BaseFoo()
I'd like to prevent clients referring to
BaseFoo
, its existence is an implementation detail and I don't want to commit to it continuing to exist... but I don't think I can hide it if I want
Foo
,
Foo1
&
Foo2
to be public?
y
You could use some
by
interface delegation I guess, but it has its pitfalls
r
I ended up wrapping
List<Thing>
into a
Things
class which could have the
expensiveOperationOn
function on it, allowing it to memoize the result. So I can lose the intermediate abstract class and have this:
Copy code
interface FooInterface {
  val things: Things
  val thingsSupportSomething: Boolean get() = things.memoizedExpensiveOperation()
}