I'm struggling to figure out an issue with the Memento pattern. Although I understand it and I'm able to implement it, I must be missing something because it seems to me that if fails when applied to an object that has List
type properties.
Consider the following classes:
public class LEDTV
{
public string Size { get; set; }
public List<Input> Inputs { get; set; }
public Manufacturer Manufacturer { get; set; }
public LEDTV(string size, List<Input> inputs, Manufacturer manufacturer)
{
Size = size;
Inputs = inputs;
Manufacturer = manufacturer;
}
public void AddInput(Input input)
{
Inputs.Add(input);
}
public string GetDetails()
{
string inputs = string.Join(";", Inputs);
return "LEDTV [Size=" + Size + ", Inputs=[" + inputs + "], Manufacturer=" + Manufacturer + "]";
}
public Memento SaveState()
{
return new Memento(this);
}
public void RestoreState(Memento memento)
{
Size = memento.Size;
Inputs = memento.Inputs;
Manufacturer = memento.Manufacturer;
}
}
public class Memento
{
public string Size { get; set; }
public List<Input> Inputs { get; set; }
public Manufacturer Manufacturer { get; set; }
public Memento(LEDTV ledTV)
{
Size = ledTV.Size;
Inputs = ledTV.Inputs;
Manufacturer = ledTV.Manufacturer;
}
public string GetDetails()
{
return "Memento [Size=" + Size + ", Inputs=[" + Inputs + "], Manufacturer=" + Manufacturer + "]";
}
}
Now consider the following piece of code:
LEDTV ledTV;
Stack<Memento> mementos = new Stack<Memento>();
// Led TV #1
ledTV = new LEDTV("42 inch", new List<Input>() { new Input("VGA", "") }, new Manufacturer("Samsung"));
mementos.Push(new Memento(ledTV));
Console.WriteLine("\nCurrent LedTV : " + ledTV.GetDetails());
// Make changes to Led TV #1
ledTV.Size = "36 inch";
ledTV.Manufacturer = new Manufacturer("Sony");
ledTV.AddInput(new Input("Digital Audio", ""));
// Led TV #2
ledTV = new LEDTV("46 inch", new List<Input>() { new Input("SCART", "") }, new Manufacturer("LG"));
mementos.Push(new Memento(ledTV));
Console.WriteLine("\nCurrent LedTV : " + ledTV.GetDetails());
// Ledt TV #3
ledTV = new LEDTV("50 inch", new List<Input>() { new Input("HDMI", "") }, new Manufacturer("Toshiba"));
Console.WriteLine("\nCurrent LedTV : " + ledTV.GetDetails());
while (mementos.Any())
{
Console.WriteLine("\nRestoring to previous LED TV");
ledTV.RestoreState(mementos.Pop());
Console.WriteLine("\nCurrent LedTV : " + ledTV.GetDetails());
}
So, in a nutshell, I have a LEDTV type that has a ValueType for the Size, a List of available Inputs and a non ValueType for the Manufacturer. The Memento object accepts a LEDTV to save its state. The code snippet sets a LEDTV, saves its state, then does some changes to it, sets it again and saves its state, sets it and again, and finally rollback all saved states, producing the following output:
Current LedTV : LEDTV [Size=42 inch, Inputs=[VGA], Manufacturer=Samsung]
Current LedTV : LEDTV [Size=46 inch, Inputs=[SCART], Manufacturer=LG]
Current LedTV : LEDTV [Size=50 inch, Inputs=[HDMI], Manufacturer=Toshiba]
Restoring to previous LED TV
Current LedTV : LEDTV [Size=46 inch, Inputs=[SCART], Manufacturer=LG]
Restoring to previous LED TV
Current LedTV : LEDTV [Size=42 inch, Inputs=[VGA;Digital Audio], Manufacturer=Samsung]
Everything works as expected, except for the List property, and I can obviously understand why: the list is not a ValueType, so it's passed along by reference. Any change made to it is reflected both in the LEDTV and Memento, because it's the same list in both objects. Surely I can copy the list content into another list, thus creating a new list object but keeping all the references of the object composing the list, but that seems so hacky. Besides, it doesn't seem doable to me to basically have a duplicate class of all the classes that I want to track states, because that's basically what a Memento is. A desirable solution would be a generic Memento implementation that lists the properties of a type and saves each one, but then I would have to overcome the List issue generically, which doesn't seem possible at first glance.
Any help would be appreciated, either in the form of a concrete solution or just pointing out the way, or simply telling me I misunderstood the entire concept.
>`. If you want a generic solution, you'll need to use refection, and look for properties that implement `IList` (and perhaps other collection interfaces). If done right,, it would likely still be cheaper than something like serialize/deserialize.
– Flydog57 Oct 08 '20 at 02:32