4 ms·
> even Java doesn't give you a way to specify "subclasses may read but not write this field", for example I think I know what you mean, but you can easily defi
by mike_hearn 3y ago
> even Java doesn't give you a way to specify "subclasses may read but not write this field", for example
I think I know what you mean, but you can easily define a private field with a protected get method which amounts to the same thing.
In Kotlin there's no distinction between properties and fields, so you can write:
open class Foo {
var thing: String
private set
protected get
}
to express that.
> in practice they're rarely used
I'm not sure I agree. Most Java libraries use it, and it seems to work fine. Mobile UI has used it for many years and that's an absolute ton of code. Game engines use it, games are a huge chunk of the software industry. People would use it a lot more if the web worked differently or if we had better non-web distribution systems on the desktop.
> if subclasses can only use extension points that were deliberately exposed then you're probably not gaining much flexibility over what you could do with composition.
You can certainly do everything that inheritance allows without support for it in the language, but that doesn't say much. You can also do OOP in C, people do, but it's generally more convenient to have compiler support. Used properly, inheritance can certainly lead to much more convenient APIs and code without being prone to misuse.
As an example, one of the issues I have with Jetpack Compose is that if you want to customize the controls in any way you have to wrap them. There's no way to say "I want a MyButton that's the same as Button except in this one aspect", because OOP is gone. You have to define a totally new MyButton function/composable that delegates to Button. In JS it's just about tolerable due to the dynamic typing and related features (e.g. spread operator), but in Kotlin it doesn't work despite relying on compiler plugins that change the language's core semantics. You end up with huge screenfuls of boilerplate which are just forwarding function arguments from one place to another, and if you upgrade to a new Compose do you get the new features automatically? No! Due to its browser heritage React conflates widget libraries with theming, so you've had to wrap all the widgets to get anything beyond the basic Material Design look, and that's manually generated boilerplate so you'll have to go and plumb new properties through by hand. There's also no nice way for the authors of Button to define properties that are only intended for customizers vs users because there's no concrete notion of customization at all. Ugh.
Now compare to an OOP toolkit:
class MyButton extends Button {
...
}
You upgrade the base libraries and MyButton now has the new features automatically, no code changes required.
> knowing that a given class or method is final means that you can optimise that class or method much more aggressively
Virtual method calls are treated as final when possible in good language VMs anyway, so it makes no difference. They're only emitted as virtual when there are actually multiple receivers observed at that specific call site, which would have to be implemented as a switch{} in composition, so again, you don't gain anything but do lose language convenience.
- lmm 3y ago> you can easily define a private field with a protected get method which amounts to the same thing. It's not though, a method is significantly more cumbersome. People get lazy and make it a protected field, and probably they themselves remember that the subclass mustn't change it and it's only the poor next person taking over maintenance who gets bitten. > Most Java libraries use it, and it seems to work fine. I mean specifically offering a restricted interface for inheritance by making extensive use of "final", making unstable fields and methods inaccessible etc.. In my experience libraries are rarely careful enough about that, and upgrading a library that you've subclassed from is usually a fraught process. (Wicket is literally the only such library that I've ever been able to fearlessly upgrade) > Used properly, inheritance can certainly lead to much more convenient APIs and code without being prone to misuse. I'm genuinely not convinced. I think if you have interfaces and delegation support, then a well designed non-inheritance API (which might involve e.g. splitting what were single classes in the inheritance version into two) can be just as convenient. > You end up with huge screenfuls of boilerplate which are just forwarding function arguments from one place to another Isn't the whole point of delegation as a language feature to avoid that? Can't you just say "delegating this interface to this member" and then all the methods you didn't specifically define get forwarded? > You upgrade the base libraries and MyButton now has the new features automatically, no code changes required. Or it breaks, because the base library changed in a way you didn't expect / didn't take into account the way you've subclassed it.
- _a_a_a_ 3y ago>> You end up with huge screenfuls of boilerplate that grossly exaggerates it. It's annoying but nothing like that.
- mike_hearn 3y ago> Isn't the whole point of delegation as a language feature to avoid that? Yes but Kotlin delegation is implemented for objects and interfaces, not functions. Compose is React inspired so it uses functions with keyword arguments for everything, so if a new keyword argument is used, you have to manually forward it. > Or it breaks It can happen for any kind of API upgrade, especially where you can customize large amounts of complex logic. I don't think it's unusually worse for inheritance based designs. Obviously that's predicated on there actually being a design, but that's not a problem unique to Java (e.g. C libs that export non-stable symbols is a perennial issue).