2013/7/4 JS <js.mdnq@gmail.com>
I'm trying to write a mixin that will solve the problem but since I can't seem to build up strings progressively it's a huge pain in the ass.

How about this?
Unfortunately this code doesn't work with git head, because it requires both one small std.typecons.wrap bug fix and its one small improvement. But I'll make a PR to fix them soon.

// -------------
// core side

import std.typecons : Proxy;
import std.typecons : wrap, unwrap;
import std.typecons : WhiteHole, NotImplementedError;
import std.exception : enforce;

public interface Interface
{
    int foo();
    int bar();
}

private class Pluggable
{
    Interface impl;
    mixin Proxy!impl;

    static Interface defaultImpl;
    static this() { defaultImpl = new WhiteHole!Interface(); }

    this() { impl = defaultImpl; }

    int foo() { return 1; }     // pre-defined default behavior
}

public Interface createPluggable()
{
    return new Pluggable().wrap!Interface;
}
public Interface setPlugin(Interface i, Interface plugin)
{
    Pluggable p = enforce(i.unwrap!Pluggable);
    p.impl = plugin ? plugin : Pluggable.defaultImpl;
    return i;
}

// -------------
// user side

class Plugin : Interface
{
    override int foo() { return 10; }
    override int bar() { return 20; }
}

void main()
{
    import std.exception : assertThrown;

    Interface i = createPluggable();

    assert(i.foo() == 1);
    assertThrown!NotImplementedError(i.bar());

    i.setPlugin(new Plugin());  // set plug-in

    assert(i.foo() == 1);
    assert(i.bar() == 20);

    i.setPlugin(null);          // remove plug-in

    assert(i.foo() == 1);
    assertThrown!NotImplementedError(i.bar());
}

Kenji Hara