2
\$\begingroup\$

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:

  1. 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.
  2. 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.
  3. Field names don't overlap, which is one of the problems with multiple inheritance.

The problems with this approach are:

  1. Possibly confusing. My solution is to document and hope the developer sees it.
  2. Method name overlapping, which is why multiple inheritance is bad (i.e., Widget may have a getSize() method, and IsResizeableMixin may also declare a getSize()). 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 IsBusyMixins 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.

\$\endgroup\$
1
\$\begingroup\$

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 a getSize() method, and IsResizeableMixin may also declare a getSize()). 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.

\$\endgroup\$
  • \$\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 a Mixin is in the first place. The Is 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 Apr 24 '15 at 12:45
  • \$\begingroup\$ @asmodean You're welcome. I wouldn't use Mixin in the interface name, but maybe in the implementation name (instead of Impl or Helper; 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 Apr 24 '15 at 13:43
0
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ I edited, adding an implementation of the state-holding example I'd hinted at. I don't think void getParent().redraw(); is valid. Maybe void redrawParent();? That would have to be implemented in every class that implements the mixin (as well as setWidth(...); and setHeight(...);; to me, re-declaring Widget'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 the IsResizableImpl, and all implementations that used the Impl would benefit. \$\endgroup\$ – asmodean Apr 19 '15 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 other RedrawParent mixin. +++ "re-declaring Widget'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 Apr 19 '15 at 19:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.