0

我正在努力找出 Memento 模式的问题。虽然我理解它并且我能够实现它,但我必须遗漏一些东西,因为在我看来,如果应用于具有List类型属性的对象时会失败。

考虑以下类:

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 + "]";
    }
}

现在考虑以下代码:

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());
}

所以,简而言之,我有一个 LEDTV 类型,它有一个用于大小的 ValueType、一个可用输入列表和一个用于制造商的非 ValueType。Memento 对象接受 LEDTV 来保存其状态。代码片段设置 LEDTV,保存其状态,然后对其进行一些更改,再次设置并保存其状态,再次设置,最后回滚所有保存的状态,产生以下输出:

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]

一切都按预期工作,除了 List 属性,我显然可以理解为什么:列表不是 ValueType,所以它是通过引用传递的。对其所做的任何更改都会反映在 LEDTV 和 Memento 中,因为这两个对象中的列表相同。当然,我可以将列表内容复制到另一个列表中,从而创建一个新的列表对象,但保留构成列表的对象的所有引用,但这看起来很hacky。此外,对我来说,基本上拥有一个我想要跟踪状态的所有类的重复类似乎是不可行的,因为这基本上就是纪念品。一个理想的解决方案是一个通用的 Memento 实现,它列出一个类型的属性并保存每个属性,但是我必须通用地克服 List 问题,这乍一看似乎是不可能的。

任何帮助都将不胜感激,无论是以具体解决方案的形式,还是只是指出方式,或者只是告诉我我误解了整个概念。

4

1 回答 1

0

Memento 模式的优点之一是保留了发起者对象封装。但是您的对象一开始就没有很好地封装,因为它以输入列表的形式暴露了它自己的内部状态。

我认为这种模式的真正价值体现在对象状态可以由公开数据的子集表示或创建对象成本高昂的情况下。

例如,如果您的 LEDTV 源自数据库,则 Memento 可能仅包含一个记录 ID,该记录 ID 可用于从数据库中恢复数据。另一方面,LEDT 可能需要对数据库进行一些昂贵的查询才能创建,然后您可以通过将数据存储在 Memento 中来避免重新创建它。

纪念品当然可以通用,但我不确定这是否总是理想的。在少数情况下,我看到它使用它是为特定类量身定制的,因为需要操纵一个更高级别的类,而不是聚合到该更高级别类中的小类。

所以适当地使用它是一个很酷的模式,你可能已经找到了它的用途。但我会确保你的设计作为一个整体是可靠的,然后考虑在你的设计中在哪里使用该模式。

对于一些简单的事情,只需实现可克隆对象就足够了......

于 2020-10-08T02:44:40.923 回答