Search code examples
scaladryfactory-pattern

Scala Factory pattern


In trying to write more testable Java code, I have been using the Model-View-Presenter pattern that Martin Fowler blogged about years ago (http://martinfowler.com/eaaDev/ModelViewPresenter.html -- yeah, I know he deprecated it, but I still like the simplicity).

I create a View interface for each JFrame, JDialog, etc. and use a Factory to actually generate them so that I can generate mocks for unit testing.

Below is a small set of sample classes and interfaces. Is there a better way in Scala than a straight syntax translation? In other words, how do I use traits, self-type references, etc. to better follow DRY principles and still write type-safe code?

import java.awt.Dialog.ModalityType;
import java.awt.Window;
import java.awt.event.ActionListener;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JButton;
import javax.swing.JDialog;

interface View {
    void okButtonAddActionListener(final ActionListener actionListener);
}

class Dialog
        extends JDialog
        implements View {
    private final JButton okButton = new JButton("OK");

    public Dialog(final Window owner,
                  final ModalityType modalityType) {
        super(owner, modalityType);
    }

    public void okButtonAddActionListener(final ActionListener actionListener) {
        okButton.addActionListener(actionListener);
    }
}

interface ViewFactory<I, C extends I> {
    I newView(final Window owner,
              final ModalityType modalityType)
            throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException;
}

class AbstractViewFactory<I, C extends I>
        implements ViewFactory<I, C> {
    private final Class<C> cls;

    public AbstractViewFactory(Class<C> cls) {
        this.cls = cls;
    }

    public I newView(final Window owner,
                     final ModalityType modalityType)
            throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        final Constructor<C> constructor = cls.getConstructor(Window.class, ModalityType.class);

        return constructor.newInstance(owner, modalityType);
    }
}

class DialogFactory
        extends AbstractViewFactory<View, Dialog> {
    private static final class InstanceHolder {
        public static ViewFactory<View, Dialog> instance = new DialogFactory();
    }

    public DialogFactory() {
        super(Dialog.class);
    }

    public static ViewFactory<View, Dialog> getInstance() {
        return InstanceHolder.instance;
    }

    public static void setInstance(final ViewFactory<View, Dialog> instance) {
        InstanceHolder.instance = instance;
    }
}

// Here is a typical usage in production
class DialogFactoryUser {
    private void userFactory() {
        final Window window = new Window(null);
        try {
            final View view = DialogFactory.getInstance().newView(window, ModalityType.APPLICATION_MODAL);
        } catch (final Exception ex) {
            ex.printStackTrace();
        }
    }
}

// Here is a typical usage in a unit test
class Test {
    public void test() {
        ...
        mockView = createMock(View.class);
        final Window window = new Window(null);
        mockViewFactory = createMock(ViewFactory.class);
        expect(mockViewFactory.newView(window, ModalityType.APPLICATION_MODAL)).andReturn(mockView);
        ...
        DialogFactory.setInstance(mockViewFactory);
    }
}

UPDATE:: I realized that I asked a similar question last year and got a different "best" answer. Check out the answer by sblundy -- very nice.


Solution

  • I'd take a look at the cake-pattern. It's typically used to do full dependency-injection as opposed to just abstracting out object construction but it can provide that as well. The basic idea is you bundle up your application configuration into a trait which you then mix together to produce your runtime and testing setups:

    trait GUI {
      trait View { /* ... */ }
      def buildView(): View
    }
    /**
     * Your "real" application
     */
    object RealGUI extends GUI {
      def buildView() = newView(/*...*/)
    }
    /**
     * Your mocked-up test application
     */
    object TestGUI extends GUI {
      def buildView() = createMock(classOf[View])
    }