I have a question about dealing with interfaces. I get an error when I try to use an interface in this way.
When I try to implement the interface this way, I get the error that the setter cannot be marked as "private". How can you implement that a variable is in an interface, can be changed with methods and the variable itself cannot be changed from the outside when implemented in a class?
It would be possible to declare the variable with "val" in the interface and then adopt it as "var" in the class, but then no standard implementation of the methods in the interface is possible.
If this is not possible in principle, what is a correct solution approach that is OOP compliant?
Many thanks for your help!
interface Product{
var count: Int
fun add(i: Int) {
count += 1
}
fun remove(i: Int) {
count -= i
}
}
class Salami(count: Int) : Produkt {
override var count: Int = count
private set // Error
}
Good question! In principle, the visibility modifier you're looking for is called protected
.
// NOTE: Non-working code; see below for explanation
interface Product {
var count: Int
protected set
fun add(i: Int) {
count += i
}
fun remove(i: Int) {
count -= i
}
}
class Salami(count: Int) : Product {
override var count: Int = count
protected set // Error
}
If this worked, it would say "count
can be read publicly but can only be assigned from Product
and its subclasses", which sounds like what you want.
Unfortunately, the JVM (and Kotlin by extension) doesn't allow the protected
modifier inside of interfaces. The reasons for this have been discussed in other places, but regardless of the reasons, it won't work right now, which makes life difficult for the exact design pattern you're trying to write.
So we're left resorting to older Java-style patterns. You could always make your interface an abstract class
(with all of the limitations that come with that).
abstract class Product(count: Int) {
var count: Int = count
private set
fun add(i: Int) {
count += i
}
fun remove(i: Int) {
count -= i
}
}
class Salami(count: Int) : Product(count) {}
But now callers have to extend a class rather than implement an interface, which restricts them to single-inheritance.
You could instead use the abstract implementation pattern, which was used by standard Java libraries like Swing before we had default implementations in interfaces. With this pattern, your interface Product
is completely abstract and has no default implementations. But then you provide an abstract class AbstractProduct
which implements all of the methods neatly. Callers are still free to implement the methods themselves if they need to extend another class, but most callers will just extend AbstractProduct
. In your case, that would look like this.
interface Product {
val count: Int
fun add(i: Int): Unit
fun remove(i: Int): Unit
}
abstract class AbstractProduct(count: Int) : Product {
// Note: final modifier is required here since we have a private
// setter, which cannot be open.
final override var count: Int = count
private set
override fun add(i: Int) {
count += i
}
override fun remove(i: Int): Unit {
count -= i
}
}
Now most applications would look like this.
class Salami(count: Int) : AbstractProduct(count) {}
But if Salami
needed to extend Meat
or some other class, it could instead do the work itself like so.
class Salami(count: Int) : Meat(), Product {
final override var count: Int = count
private set
override fun add(i: Int) {
count += i
}
override fun remove(i: Int): Unit {
count -= i
}
}
So users can still get the full generality if needed, but in the simple case, everything will just work. It's not perfect, but it gets close to what you want. Just remember that functions taking a product should always take Product
, not AbstractProduct
, as the latter is an implementation detail.
Finally, you could always favor composition over inheritance. This count
setup you've got is sort of its own encapsulated subsystem, so we could make that explicit.
class ProductCount(initialValue: Int) {
var value: Int = initialValue
private set
fun add(i: Int): Unit {
value += i
}
fun remove(i: Int): Unit {
value -= i
}
}
interface Product {
val productCount: ProductCount
}
class Salami(count: Int) : Product {
override val productCount = ProductCount(count)
}
Now only ProductCount
can ever modify the count. Not even Product
can do it, aside from the add
and remove
methods provided to it. We've decoupled a product's count from the product itself. And, if you want, you can even provide delegator methods on product
which call the same methods on ProductCount
, to make the API look even more like your original one.
interface Product {
val productCount: ProductCount
fun add(i: Int): Unit = productCount.add(i)
fun remove(i: Int): Unit = productCount.remove(i)
val count: Int
get = productCount.value
}
Now your public API is exactly what you wanted it to be. You've got a count
integer variable which cannot be modified from the outside, and you've got add
and remove
methods that do modify it. The fact that all of this is encapsulated inside a ProductCount
class is fairly well hidden from the users of your API.