【问题标题】:Memento pattern and violation of encapsulation备忘录模式和违反封装
【发布时间】:2020-07-05 22:26:19
【问题描述】:

在互联网上,我遇到了 memento 模式的实现示例 我认为这是完全不正确的。 它们可以用 Java 和 C# 编写。

这里有几个

  1. Incorrect implementation of Memento pattern 1
  2. Incorrect implementation of Memento pattern 2
  3. Incorrect implementation of Memento pattern 3

代码:

public class Originator
{
    private string _state; //the private field of originator that shouldn't be exposed!!!!

    public Memento CreateMemento()
    {
        return new Memento(_state);
    }

    public void SetMemento(Memento memento)
    {
        _state = memento.GetState();
    }
}


public class Memento
{
    private string _state;

    public Memento(string state)
    {
        _state = state;
    }

    public string GetState()
    {
        return _state;  // here caretaker can access to private field of originator!!!
    }
}


public class Caretaker
{
    public Memento Memento { get; set; }
}

在代码中我留下了应该描述情况的 cmets。

看守类可以通过备忘录读取发起者的私有字段,这违反了备忘录模式的主要原则之一:

不得违反对象的封装性。

所以问题是我是对的吗?

【问题讨论】:

  • int 将被复制到Memento 构造函数,所以Memento._stateOriginator._state 是两个不同的,独立的ints(与string 相同)
  • @vborutenko,不要尝试object.ReferenceEquals(Memento._state, Originator._state)。此外,更改为Originator._state 不会更改Memento._state,反之亦然
  • 在引用类型和值类型上看起来有点误解
  • @vborutenko,不,它无权访问Originator 私有字段。它存储前一段时间在该字段中的 string/int 的副本)。所以,如果Originator 以某种方式改变了他的_stateMemento 将永远不会知道这一点。同时,如果Momento 以某种方式更改他的_state(他自己的那个string/int 值的副本) - 这将永远不会对Originators _state 做任何事情
  • @vborutenko,好的,现在我知道你有什么困扰了。公开私有字段的(副本)并不违反封装。以您的示例为例,应该有 "嗨。我是看守人,我知道发起者的私有字段 '_state' 的值等于" + memento.GetState() + " 不久前,现在可能会改变,IDK。”

标签: c# oop design-patterns memento


【解决方案1】:

封装并不意味着您根本不能访问数据,您只是不能直接访问它。您应该只能通过公共方法访问它:)
这个链接可能比我能解释得更好:

https://www.geeksforgeeks.org/encapsulation-in-java/

所以封装保留在您的代码示例中,因为状态只能通过公共 get 方法检索。

【讨论】:

  • 在创建者类中,我们“隐藏”状态的值使其成为私有。我们不希望有人知道该字段的值。但在示例中我们看到看守者可以知道这个值
  • 如果您不想让其他类知道私有属性,请不要添加获取其值的公共 getter。
  • 它是来自互联网的纪念品模式的实现。我提供了几个链接。
  • 但是你做了一个错误的假设,因为你没有注意到值和引用类型之间的区别
  • 不,请查看最后的问题
【解决方案2】:

如果我们添加一些逻辑来检查变量如何演变,您会注意到 OriginatorMemento 如何共享相同的数据。

public class Originator
{
    private string _state = "originator_initial_state";

    public Memento CreateMemento()
    {
        return new Memento(_state);
    }

    public void DoSomeLogic()
    {
        _state = _state + " modified";
    }

    public void SetMemento(Memento memento)
    {
        _state = memento.GetState();
    }
}

public class Memento
{
    private string _state;

    public Memento(string state)
    {
        _state = state;
    }

    public string GetState()
    {
        return _state;
    }
}

现在让我们与他们合作:

Originator myOriginator = new Originator();
Memento memento1 = myOriginator.CreateMemento();

myOriginator.DoSomeLogic();

Memento memento2 = myOriginator.CreateMemento();

string memento1_state = memento1.GetState();
string memento2_state = memento2.GetState();

Console.WriteLine(memento1_state);
Console.WriteLine(memento2_state);

您会注意到memento1_statememento2_state 是不同的,因为它们共享相同的底层对象。

【讨论】:

  • 我知道他们不共享同一个对象。请查看问题的最后一个问题
  • 如果类提供了getter方法,则不违反封装。
【解决方案3】:

所以问题是我是对的吗?

是的,你是对的:这些示例都是 Memento 设计模式的错误实现,因为 Memento 类通过公共方法暴露了其内部依赖关系。如您所述,这允许Caretaker(或任何其他类)获取有关Originator 的私人信息。

以前的答案集中在内部字段的状态以及Memento 状态是否可以独立于Originator 而变化。但是,与暴露状态相比,违反封装的方式更多。暴露依赖关系也可能违反它。公共 getter 方法公开了 OriginatorString 的依赖关系。

我希望我可以说关于设计模式的错误信息是不寻常的;但是您会在 Internet 上找到关于设计模式的不准确和误导性文章,包括 SO 和 Wikipedia

我碰巧喜欢https://refactoring.guru/design-patterns/memento,但交叉核对 GoF 书籍从来都不是一个坏主意。

【讨论】:

    猜你喜欢
    • 2012-07-09
    • 1970-01-01
    • 2013-12-20
    • 1970-01-01
    • 2020-03-06
    • 1970-01-01
    • 2021-08-05
    • 2013-08-27
    • 2013-12-01
    相关资源
    最近更新 更多