Sunday, September 21, 2014

Memento design pattern in Java

Recently when recapping my design patterns I came across a Memento. In principle, it provides means for capturing and externalizing an object’s internal state without violating the encapsulation - only the Originator can manipulate the state saved in Memento. That would require a Memento class to implement two interfaces - a wide one for the Originator with appropriate get and set methods and a narrow one for other objects to be able to pass the Memento where necessary.

The GoF book presents one possible implementation of that pattern in C++ using a “friend” class. It defines two classes - an Originator and a Memento. The latter defines two methods marked as private - getState and setState. In addition to that it declares the Originator class as a friend thus letting it access its private members.

As Java being my primary programming language it made me think how we could implement the Memento using it. Especially given the fact that Java does not support the friend class concept.

The most straightforward solution is to put the Memento, its interfaces and the Originator in the same package. The wide interface will be package-private and the narrow one public:
package memento1;

public interface NarrowMemento {
}

interface WideMemento {
  int getState();
}

class MementoImpl implements WideMemento, NarrowMemento {

  private int state;

  MementoImpl(int state) {
    this.state = state;
  }

  @Override
  public int getState() {
    return state;
  }

}

public class Originator {
  private int state;

  public Originator(final int state) {
    this.state = state;
  }

  public NarrowMemento getMemento() {
    return new MementoImpl(state);
  }

  public void setMemento(NarrowMemento memento) {
    this.state = ((WideMemento) memento).getState();
  }
}

The important bits:
  1. Originator class is public, can be used in any package. 
  2. MementoImpl class is package private thus visible only within its package.
  3. MementoWide interface is package private. 
  4. MementoNarrow marker interface is public.
Therefore only Originator and MementoNarrow are visible outside of the package and as long as Originator encapsulates its state properly it won’t leak through Memento.
This solution works fine as long as we can ensure that the Originator is used in a different package. Otherwise, the MementoWide is visible and can be used to elicit the Originator state.

There is another approach to this problem that mitigates the packaging concerns. If we want to make sure that regardless of the packaging only the Originator can access (make a call) to methods defined in the wide interface we can use method signature security. We will alter the method from the MementoWide to include a dummy class which can be constructed only by the Originator:

package memento2;

import memento2.originator.Originator;

public interface Memento {
  int getState(Originator.Stamp stamp);
}

public class MementoImpl implements Memento {

  final int state;

  public MementoImpl(final int state) {
    this.state = state;
  }

  @Override
  public int getState(Originator.Stamp stamp) {
    Objects.requireNonNull(stamp);
    return state;
  }
}

And in a different package Originator:

package memento2.originator;

import memento2.Memento;
import memento2.MementoImpl;

public class Originator {
  private final static Stamp stamp = new Stamp();

  private int state;

  public Originator(final int state) {
    this.state = state;
  }

  public Memento getMemento() {
    return new MementoImpl(state);
  }

  public void setMemento(Memento memento) {
    this.state = memento.getState(stamp);
  }

  public static class Stamp {
    private Stamp() {
    }
  }
}
The first thing that you will probably notice is that Memento interface and an implementing class are public. Still, only the Originator will be able to make a call to the exposed method. That is because its parameter is an inner class of Originator but its constructor is private so only the Originator can create a Stamp instance and make a successful call. Of course it is possible to pass a null as an argument to the method but then such client will be smacked with an NPE.
The only way this solution can be broken is by publishing an instance of the Stamp by either Memento or Originator which requires modifying a class. Therefore it is less likely to be abused than the first approach which relies only on proper packaging.

The last solution gets as close as possible to the behaviour of the Memento implemented using C++ friend class - i.e. ensuring that only the class which state we externalize can access it.