require 'java'
include_class "java.awt.event.ActionListener"
class MyActionListener < ActionListener
def actionPerformed(event)
puts event
end
end
While documenting a JRUBY-66 workaround and thinking about a longterm fix, it hit me like a diamond bullet through my forehead: interfaces should be treated like modules.
My justification:
- You can include many modules, but only extend one class...just like interfaces. Currently in JRuby you can only implement one interface, which is stupid.
- Modules imply a particular set of behaviors not specific to a given class hierarchy...just like interfaces.
- Ruby implementations of Java interfaces can't extend any other classes; you can't both extend Array and implement Collection, if that were your goal.
- Ruby implementations of Java interfaces have bugs when defining initialize, since they don't really just implement that interface...they extend one of our JavaSupport proxies.
Item #3 limits your ability to re-open core Ruby classes and add new Java interfaces to them, something that might greatly simplify mapping Ruby types to Java-land.
Item #4 is the cause of JRUBY-66, since we need to make sure the proxy's initializer is called.
In our defence, we inherited much of this Java integration behavior from the original project owners; however I think mapping interfaces to modules allows for much more powerful and uniform Java integration support.
I know it would be a fairly significant change to make Java interfaces
act like modules, but it seems much more logical to me. It's also primarily a new feature we could phase in, with the < syntax continuing to work for old style interface implementation.
Thoughts?
# yes, I know encapsulation would be better...this is just an example
...
include_class "javax.swing.JButton"
class MyActionRecorder < Array
include ActionListener
def actionPerformed(event)
self << event
end
end
How do you enforce that the methods from the interface are implemented?
ReplyDeleteOr is it intentional to not do so?
It's not necessarily intentional; there's just no way to really enforce it. At the time the interface is extended (or hopefully, included) the class is still "open" as far as Ruby is concerned. We can't determine whether methods will be implemented later.
ReplyDeleteAlso, interface extension is done using Java's Proxy class, which implictly implements all interface methods right away. When you provide a real body for those methods later in the Ruby code, you're just filling in the blanks.
Of course, the class is still open. I really program too much in Java.
ReplyDeleteI agree with your analysis of how interfaces should be implemented.
ReplyDeleteDo you see an
include_interface
method alongside include_class, but with Java implementations that are the same behind the scenes?
BTW I'm really pleased by my explorations into JRuby and think you're really onto something. In particular I've had some success putting together webapps with Spring MVC that don't use any Java code at all!
The current thinking is something like this:
ReplyDeleteclass MyClass
implement ActionListener
implement Collection
include Enumerable
...
end
I'm still of two minds whether overloading include is ok or whether we should introduce a new keyword because interfaces are their own beasts. Really it comes down to deciding how "Java" we want the ruby code to be; if we take a purely Ruby view, then mixin inheritance (using include) isn't that much different than multiple inheritance of types and method signatures with interfaces. The only striking difference is that interface inheritance doesn't come with any implementation code. If you take the Java point of view, interfaces are completely different from modules, and we should have a separate keyword and enforce that all methods have been implemented somehow. The Java approach starts to seem really foreign and cumbersome in Ruby, however, so I'm leaning toward the "Ruby way".
Interface should be treated like modules? Duh!
ReplyDelete