I am using the following technique for simulating mixins (or Traits) using Java and would like to know whether this is reasonable or just confusing to future developers. Also, I believe a good naming scheme is the best documentation, so if it would help to change the nomenclature, please provide suggestions.
/** A mixin interface which your class should implement */
public interface IsResizableMixin {
/**
* The implementation, which your class should
* instance and delegate calls to
*/
public static class IsResizableMixinImpl implements IsResizableMixin {
// A widget is e.g. a Swing Component, a GWT Widget, etc.
private Widget w;
pubic IsResizableMixinImpl(Widget w) {
this.w = w;
}
public void resizeTo(int width, int height);
/* for example */
w.setWidth(width);
w.setHeight(height);
w.getParent().redraw();
}
}
void resizeTo(int width, int height);
}
Then an example implementation of a Widget
, one that is resizable:
public class Foo extends Widget implements IsResizableMixin { private IsResizableMixin mixin; /* ... */ public Foo() { super(); this.mixin = new IsResizableMixinImpl(this); /* ... */ } /* ... */ public void resizeTo(int w, int h) { mixin.resizeTo(w, h); } }
A richer example would be a Widget
that can be set "busy", that is, overlaid with a spinner and disabled.
public interface IsBusyMixin { public class IsBusyMixinImpl implements IsBusyMixin { private Widget w; private boolean isBusy = false; pubic IsBusyMixinImpl(Widget w) { this.w = w; } public void toggleBusy(); isBusy = !isBusy(); if (isBusy) { /* overlay the component with a spinner */ } else { /* remove the spinner overlay */ } } } void toggleBusy(); } public class Foo extends Widget implements IsBusyMixin { // Other than the fact that this method exists, // Foo is unaware that it can be toggled to a busy state. public void toggleBusy() { this.mixin.toggleBusy(); } }
The benefits of this approach are:
- Code reuse. In the past, if we needed
Foo
functionality, we copy-pasted boilerplate code, but if a bug is discovered, we've lost track of everywhere it's been duplicated. - Delegate implementations can vary; we can have an implementation for a Swing Component and one for a GWT component, but their API is the same.
- Field names don't overlap, which is one of the problems with multiple inheritance.
The problems with this approach are:
- Possibly confusing. My solution is to document and hope the developer sees it.
- Method name overlapping, which is why multiple inheritance is bad (i.e., Widget may have a
getSize()
method, andIsResizeableMixin
may also declare agetSize()
). The solution is to give mixin methods distinctive names.
This is just a simple example. Putting the boolean
in the implementing class would work just as well, but I am trying to factor that out of Foo
. If we decide to change how that state is stored, all IsBusyMixin
s would need to be updated.
Any pros/cons I'm missing?
Note that Java 8's default interface implementations don't cover this use-case since mixins must hold some state.
2 Answers 2
There's always a choice, even if you need some additional state:
You can add the class directly to the class and provide methods for it in the interface or go for your solution.
Adding to the class directly would look like this:
public interface IsBusyMixin {
boolean isBusy();
void dumbToggleBusy();
void overlayTheComponentWithASpinner(); // implement here or not
void removeTheSpinnerOverlay(); // implement here or not
public void toggleBusy();
dumbToggleBusy();
if (isBusy) {
overlayTheComponentWithASpinner();
} else {
removeTheSpinnerOverlay();
}
}
}
public class Foo extends Widget implements IsBusyMixin {
@Getter private boolean isBusy;
public void toggleBusy() {
isBusy = !isBusy();
}
}
I'd say, it's always somehow possible. However, it may get way uglier then your solution. Moreover, all interface methods must be public and this exposes too much of the inner state.
So when things get a bit more complicated, I wouldn't misuse default methods that hard and would go with your approach instead.
Then you may consider to to use a default method exposing the mixin and maybe another one like
default void toggleBusy() {
this.mixin().toggleBusy();
}
Method name overlapping, which is why multiple inheritance is bad (i.e.,
Widget
may have agetSize()
method, andIsResizeableMixin
may also declare agetSize()
). The solution is to give mixin methods distinctive names.
In case one of the methods always delegates to the other, you should probably keep the names the same. Otherwise it can get confusing whatever you do.
The name IsResizeableMixin
should probably be just Resizeable
or ResizeableMixin
.
-
\$\begingroup\$ However, it may get way uglier then your solution is what I was hoping to hear. Regarding the naming, you are correct. Using
Mixin
is likely to confuse Java-only developers, who have no idea what aMixin
is in the first place. TheIs
prefix comes from using GWT too much. I like it, but not enough to force it on others. Finally, while we focused on whether Java 8 would support this or not, I would like to point out that this works with previous versions of Java as well (I haven't made the transition to 8 yet myself). Thanks for the answer! \$\endgroup\$asmodean– asmodean2015年04月24日 12:45:00 +00:00Commented Apr 24, 2015 at 12:45 -
\$\begingroup\$ @asmodean You're welcome. I wouldn't use
Mixin
in the interface name, but maybe in the implementation name (instead ofImpl
orHelper
; Java developers unfamiliar with mixins should UTFG). I agree that a pre-Java-8 solution is more usable. Using default methods for multiple inheritance like I did is a misuse - you could do it if it helped a lot, but this doesn't seem to be the case. Just follow your nose. \$\endgroup\$maaartinus– maaartinus2015年04月24日 13:43:37 +00:00Commented Apr 24, 2015 at 13:43
Your mixin's state is this
itself, so there's no additional state and you can use default methods.
public interface IsResizableMixin {
void setWidth(width);
void setHeight(height);
Redrawable setHeight(height);
default void redrawParent() {
getParent().redraw();
}
default void resizeTo(int width, int height) {
setWidth(width);
setHeight(height);
redrawParent();
}
}
public class Foo extends Widget implements IsResizeableMixin {
// This class has been intentionally left empty.
}
From your example, there's nothing missing in my Foo
.
-
\$\begingroup\$ I edited, adding an implementation of the state-holding example I'd hinted at. I don't think
void getParent().redraw();
is valid. Maybevoid redrawParent();
? That would have to be implemented in every class that implements the mixin (as well assetWidth(...);
andsetHeight(...);
; to me, re-declaringWidget
's methods directly is kind of hairy). If we realized that redrawing the parent was unnecessary, we would have to change all mixin implementations. In my example, we'd change theIsResizableImpl
, and all implementations that used theImpl
would benefit. \$\endgroup\$asmodean– asmodean2015年04月19日 18:46:24 +00:00Commented Apr 19, 2015 at 18:46 -
\$\begingroup\$ @asmodean This
void getParent().redraw();
was just a brainless copy-pasting. +++ "That would have to be implemented in every class that implements the mixin" - No, the mixin could do it itself. Or extend some otherRedrawParent
mixin. +++ "re-declaringWidget
's methods directly is kind of hairy" - Maybe it is and it may lead to conflicts, but if it does, you'll see the problem at compile time. +++ "If we realized that redrawing the parent was unnecessary" ... - then you'd have to change just a single default method. +++ I'm gonna look at your new code now. \$\endgroup\$maaartinus– maaartinus2015年04月19日 19:04:26 +00:00Commented Apr 19, 2015 at 19:04