© Dmitri Nesteruk 2020
D. NesterukDesign Patterns in .NET Core 3https://doi.org/10.1007/978-1-4842-6180-4_19

19. Memento

Dmitri Nesteruk1  
(1)
St. Petersburg, c.St-Petersburg, Russia
 

When we looked at the Command design pattern, we noted that recording a list of every single change theoretically allows you to roll back the system to any point in time – after all, you’ve kept a record of all the modifications.

Sometimes, though, you don’t really care about playing back the state of the system, but you do care about being able to roll back the system to a particular state, if need be.

This is precisely what the Memento pattern does: it typically stores the state of the system and returns it as a dedicated, read-only object with no behavior of its own. This “token,” if you will, can be used only for feeding it back into the system to restore it to the state it represents.

Let’s look at an example.

Bank Account

Let’s use an example of a bank account that we’ve made before…
public class BankAccount
{
  private int balance;
  public BankAccount(int balance)
  {
    this.balance = balance;
  }
  // todo: everything else :)
}
…but now we decide to make a bank account with a Deposit(). Instead of it being void as in previous examples, Deposit() will now be made to return a Memento:
public Memento Deposit(int amount)
{
  balance += amount;
  return new Memento(balance);
}
And the Memento will then be usable for rolling back the account to the previous state:
public void Restore(Memento m)
{
  balance = m.Balance;
}
As for the memento itself, we can go for a trivial implementation:
public class Memento
{
  public int Balance { get; }
  public Memento(int balance)
  {
    Balance = balance;
  }
}

You’ll notice that the Memento class is immutable. Imagine if you could, in fact, change the balance: you could roll back the account to a state it was never in!

And here is how one would go about using such a setup:
var ba = new BankAccount(100);
var m1 = ba.Deposit(50);
var m2 = ba.Deposit(25);
WriteLine(ba); // 175
// restore to m1
ba.Restore(m1);
WriteLine(ba); // 150
// restore back to m2
ba.Restore(m2);
WriteLine(ba); // 175

This implementation is good enough, through there are some things missing. For example, you never get a Memento representing the opening balance because a constructor cannot return a value. You could add an out parameter, of course, but that’s just too ugly.

Undo and Redo

What if you were to store every Memento generated by BankAccount? In this case, you’d have a situation similar to our implementation of the Command pattern, where undo and redo operations are a byproduct of this recording. Let’s see how we can get undo/redo functionality with a Memento.

We’ll introduce a new BankAccount class that’s going to keep hold of every single Memento it ever generates:
public class BankAccount
{
  private int balance;
  private List<Memento> changes = new List<Memento>();
  private int current;
  public BankAccount(int balance)
  {
    this.balance = balance;
    changes.Add(new Memento(balance));
  }
}

We have now solved the problem of returning to the initial balance: the memento for the initial change is stored as well. Of course, this memento isn’t actually returned, so in order to roll back to it, well, I suppose you could implement some Reset() function or something – totally up to you.

The BankAccount class has a current member that stores the index of the latest momento. Hold on, why do we need this? Isn’t it the case that current will always be one less than the list of changes? Only if you want to support undo/rollback operations; if you want redo operations too, you need this!

Now, here’s the implementation of the Deposit() method :
public Memento Deposit(int amount)
{
  balance += amount;
  var m = new Memento(balance);
  changes.Add(m);
  ++current;
  return m;
}
There are several things that happen here:
  • The balance is increased by the amount you wanted to deposit.

  • A new memento is constructed with the new balance and added to the list of changes.

  • We increase the current value (you can think of it as a pointer into the list of changes).

Now here comes the fun stuff. We add a method to restore the account state based on a memento:
public void Restore(Memento m)
{
  if (m != null)
  {
    balance = m.Balance;
    changes.Add(m);
    current = changes.Count - 1;
  }
}

The restoration process is significantly different to the one we’ve looked at earlier. First, we actually check that the memento is initialized – this is relevant because we now have a way of signaling no-ops: just return a default value. Also, when we restore a memento, we actually add that memento to the list of changes so an undo operation will work correctly on it.

Now, here is the (rather tricky) implementation of Undo():
public Memento Undo()
{
  if (current > 0)
  {
    var m = changes[--current];
    balance = m.Balance;
    return m;
  }
  return null;
}

We can only Undo() if current points to a change that is greater than zero. If that’s the case, we move the pointer back, grab the change at that position, apply it, and then return that change. If we cannot roll back to a previous memento, we return null, which should explain why we check for null in Restore().

The implementation of Redo() is very similar:
public Memento Redo()
{
  if (current + 1 < changes.Count)
  {
    var m = changes[++current];
    balance = m.Balance;
    return m;
  }
  return null;
}
Again, we need to be able to redo something: if we can, we do it safely – if not, we do nothing and return null. Putting it all together, we can now start using the undo/redo functionality:
var ba = new BankAccount(100);
ba.Deposit(50);
ba.Deposit(25);
WriteLine(ba);
ba.Undo();
WriteLine($"Undo 1: {ba}"); // Undo 1: 150
ba.Undo();
WriteLine($"Undo 2: {ba}"); // Undo 2: 100
ba.Redo();
WriteLine($"Redo 2: {ba}"); // Redo 2: 150

Using Memento for Interop

Sometimes, managed code is not enough. For example, you need to run some calculations on the GPU and those are (typically) programmed using CUDA C and the like. You end up having to use a C or C++ library from your C# code, so you make calls from the managed (.NET) side to the unmanaged (native code) side.

This isn’t really a problem if you want to pass simple bits of data, such as numbers or arrays, back and forth. .NET has functionality for pinning an array and sending it to the “unmanaged” side for processing. It works fine, most of the time.

The problems arise when you allocate some object-oriented construct (i.e., a class) inside unmanaged code and want to return that to the managed caller. Nowadays, this is typically handled by serializing (encoding) all the data on one side and then unpacking it on the other side. There are plenty of approaches here, including simple ones such as returning XML or JSON or complicated, industry-grade solutions such as Google’s Protocol Buffers.

In some cases, though, you don’t really need to return the full object itself. Instead, you simply want to return a handle so that this handle can be subsequently used on the unmanaged side again. You don’t even need the extra memory traffic passing objects back and forth. There are many reasons why you’d want to do this, but the main reason is that you want only one side to manage the object’s lifetime, since managing it on both sides is a nightmare that nobody really needs.

What you do in this case is you return a Memento. This can be anything – a string identifier, an integer, a Globally Unique Identifier (GUID) – anything that lets you refer to the object later on. The managed side then holds on to the token and uses that token to pass back to the unmanaged side when some operations on the underlying object are required.

This approach introduces an issue with lifetime management. Suppose we want the underlying object to live for as long as we have the token. How can we implement this? Well, this would mean that, on the unmanaged side, the token lives forever, whereas on the managed side, we wrap it in an IDisposable with the Dispose() method sending a message back to the unmanaged side that the token has been disposed. But what if we copy the token and have two or more instances of it? Then we end up having to build a reference-counted system for tokens: something that is quite possible but introduces extra complexity in our system.

There is also a symmetric problem: what if the managed side has destroyed the object that the token represents? If we try to use the token, additional checks need to be made to ensure the token is actually valid, and some sort of meaningful return value needs to be given to the unmanaged call in order to tell the managed side that the token has gone stale. Again, this is extra work.

Summary

The Memento pattern is all about handing out tokens that can be used to restore the system to a prior state. Typically, the token contains all the information necessary to move the system to a particular state, and if it’s small enough, you can also use it to record all the states of the system so as to allow not just the arbitrary resetting of the system to a prior state, but controlled navigation backward (undo) and forward (redo) of all the states the system was in.

One design decision that I made in the demos earlier is to make the memento a class. This allows me to use the null value to encode the absence of a memento to operate upon. If we wanted to make it a struct instead, we would have to redesign the API so that, instead of null, the Restore() method would be able to take either a Nullable<Memento>, some Option<Memento> type (.NET doesn’t have a built-in option type yet), or a memento possessing some easily identifiable trait (e.g., a balance of int.MinValue).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.244.201