Pregunta

I am doing some research into the Memento Pattern and it seems that most of the examples I have come across seem to be relatively similar (Saving a String into an array and restoring it when needed) now correct me if I am wrong but I believe the method that i just described is "Object Cloning" but what are the other ways of implementing the Memento Pattern?

From what I have also picked up on Serialization can be used but there seems to be a grey area with people saying that it violates the encapsulation of the object and isn't a way to implement to Memento Pattern due to this.

So will anybody be able to shed some light on the ways to implement the pattern? My research has came up with a sort of mixture of all different things and has just made everything confusing.

Thanks

¿Fue útil?

Solución

The Java Collections framework defines Queue, which can help.

Candidate code:

public final class Memento<T>
{
    // List of saved values
    private final Queue<T> queue = new ArrayDeque<T>();

    // Last entered value, whether it has been saved or not
    private T currentValue;

    // No initial state, ie currentValue will be null on construction, hence
    // no constructor

    // Set a value, don't save it
    public void set(final T value)
    {
        currentValue = value;
    }

    // Persist the currently saved value
    public void persist()
    {
        queue.add(currentValue);
    }

    // Return the last saved value
    public T lastSaved()
    {
        return queue.element();
    }

    // Return the last entered value
    public T lastEntered()
    {
        return currentValue;
    }
}

Notably missing from this code are many things, but are easily implementable:

  • revert to the last saved value;
  • no check for nulls;
  • T does not implement Serializable;
  • convenience method (like, add a value and make it the last saved state);
  • code is not thread safe!

Etc.

Sample code:

public static void main(final String... args)
{
    final Memento<String> memento = new Memento<String>();

    memento.set("state1");
    System.out.println(memento.lastEntered()); // "state1"
    memento.persist();
    memento.set("state2");
    System.out.println(memento.lastEntered()); // "state2"
    System.out.println(memento.lastSaved()); // "state1"
}

In effect: this is a braindead implementation which can be improved, but which can be used as a basis -- extending it depends on your needs ;)

Otros consejos

A usual problem that may come with memento implementations is that often there is a need for a lot of classes that represent the internal state of different kind of objects. Or the memento implementation must serialise object state to some other form (e.g. serialised java objects).

Here is a sketch for a memento implementation that doesn't rely on a specific memento class per class, whose state is to be captured for undo/redo support.

There's a basic concept to be introduced first:

public interface Reference<T> {
    T get();
    void set(T value);
}

This is an abstraction of java.lang.ref.Reference, because that class is for garbage collection purposes. But we need to use it for business logic. Basically a reference encapsulates a field. So they are intended to be used like that:

public class Person {
    private final Reference<String> lastName;
    private final Reference<Date> dateOfBirth;

    // constructor ...

    public String getLastName() {
        return lastName.get();
    }

    public void setLastName(String lastName) {
        this.lastName.set(lastName);
    }

    public Date getDateOfBirt() {
        return dateOfBirth.get();
    }

    public void setDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth.set(dateOfBirth);
    }
}

Note that object instantiation with those references might not be that trivial, but we leave that out here.

Now here are the details for the memento implementation:

public interface Caretaker {

    void addChange(Change change);
    void undo();
    void redo();
    void checkpoint();   
}

public interface Change {

    Change createReversal();
    void revert();
}

Basically a Change represents a single identifiable change to the state of an identifiable object. A Change is revertable by invoking the revert method and the reversal of that change can itself be reverted by reverting the Change created by the createReversal method. The Caretaker accumlates changes to object states via the addChange method. By invoking the undoand redo methods the the Caretaker reverts or redoes (i.e. reverting the reversal of changes) all changes until the next checkpoint is reached. A checkpoint represents a point at which all observed changes will accumulate to a change that transforms all states of all changed objects from one valid to another valid configuration. Checkpoints are usually created past or before actions. Those are created via the checkpoint method.

And now here is how to make use of the Caretaker with Reference:

public class ReferenceChange<T> implements Change {

    private final Reference<T> reference;
    private final T oldValue;
    private final T currentReferenceValue;

    public ReferenceChange(Reference<T> reference, T oldValue,
            T currentReferenceValue) {
        super();
        this.reference = reference;
        this.oldValue = oldValue;
        this.currentReferenceValue = currentReferenceValue;
    }

    @Override
    public void revert() {
        reference.set(oldValue);
    }

    @Override
    public Change createReversal() {
        return new ReferenceChange<T>(reference, currentReferenceValue,
                oldValue);
    }
}

public class CaretakingReference<T> implements Reference<T> {

    private final Reference<T> delegate;
    private final Caretaker caretaker;

    public CaretakingReference(Reference<T> delegate, Caretaker caretaker) {
        super();
        this.delegate = delegate;
        this.caretaker = caretaker;
    }

    @Override
    public T get() {
        return delegate.get();
    }

    @Override
    public void set(T value) {
        T oldValue = delegate.get();
        delegate.set(value);
        caretaker.addChange(new ReferenceChange<T>(delegate, oldValue, value));
    }
}

There exists a Change that represents how the value of a Reference has changed. This Change is created when the CaretakingReference is set. In this implementation there is a need for a nested Reference within the CaretakingReference implementation, because a revert of the ReferenceChange shouldn't trigger a new addChange via the CaretakingReference.

Collection properties needn't use the Reference. A custom implementation triggering the caretaking should be used in that case. Primitives can be used with autoboxing.

This implementation infers an additional runtime and memory cost by always using the reference instead of fields directly.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top