【问题标题】:How to implement Memento Pattern in Kotlin如何在 Kotlin 中实现备忘录模式
【发布时间】:2018-02-02 15:42:18
【问题描述】:

我目前正在尝试在 Kotlin 中实现一些设计模式作为练习,但我有点坚持使用“Memento”模式。我的参考资源是SourceMaking: Memento

我要实现这个结构:

在遵循他们的“清单”时

  1. 确定“看守人”和“发起人”的角色。
  2. 创建一个 Memento 类并将创建者声明为朋友。
  3. 管理员知道何时“检查”发起人。
  4. 创建者创建一个备忘录并将其状态复制到该备忘录。
  5. 看守握着(但无法窥视)纪念品。
  6. 管理员知道何时“回滚”发起者。
  7. 创建者使用 Memento 中保存的状态恢复自身。

我无法执行第 5 步。如何创建一个 Memento 对象,其字段可以从 Originator 实例内部读取,但对 Caretaker 完全不透明?

我已经在 J​​ava 中成功实现了如下:

public class Originator {

    private final int id;
    private String title;
    private String description;

    public Originator(int id) {
        this.id = id;
    }

    /* skipping title and description getter & setter */

    public Memento saveState() {
        return new Memento(new State(id, title, description));
    }

    public void restore(Memento memento) {
        id = memento.state.id;
        title = memento.state.title;
        description = memento.state.description;
    }

    private class State {

        private final int id;
        private final String title;
        private final String description;

        public State(int id, String title, String description) {
            this.id = id;
            this.title = title;
            this.description = description;
        }
    }

    public class Memento {

        private final State state;

        public Memento(State state) {
            this.state = state;
        }
    }
}

还有一个看护人

public class Caretaker {

    public Originator originator;

    public Caretaker(@NotNull Originator originator) {
        this.originator = originator;
    }

    public Originator.Memento save() {
        return originator.saveState();
    }

    public void restore(@NotNull Originator.Memento memento) {
        originator.restoreFromState(memento);
    }
}

因为它们是内部类,我可以从我的Originator 实例中读取MementoState 的私有字段,但是对于Caretaker 我的Memento 实例是完全不透明的(仅显示Objects成员函数)。

现在我该如何在 Kotlin 中实现这种精确的行为?基本上我错过了读取内部类私有字段的功能。

我能想到的最接近的事情是这样的:

class Originator(id: Long) {

    private var id: Long = id
    var description: String = ""
    var title: String = ""

    fun saveState() = Memento(State(id, title, description))

    fun restoreState(memento: Memento) {
        id = memento.state.id // <-- cannot access 'state': it is private in 'Memento'
        title = memento.state.title // <-- cannot access 'state': it is private in 'Memento'
        description = memento.state.description // <-- cannot access 'state': it is private in 'Memento'
    }

    inner class State(private val id: Long,
                  private val title: String,
                  private val description: String)

    inner class Memento(private val state: State)
}

这具有Memento 对我的Caretaker 实例完全不透明的预期效果,但我也无法从Originator 中读取字段。
顺便说一句,这段代码几乎与应用到我的 Java 代码的 IntelliJ 的“将 Java 转换为 Kotlin”功能生成的代码完全相同(而且它显然也不能编译)。

那么我在这里遗漏了什么明显(或神奇)的东西吗?也许不是类图中显示的结构?或者这些确切的规范不能在 Kotlin 中实现?

另一方面:Memento 对象的不透明性要求实际上是 Memento 模式的通俗接受的属性,还是 SourceMaking 提出了这个要求?

【问题讨论】:

    标签: java design-patterns kotlin memento


    【解决方案1】:

    您可以为Memento 定义一个公共父类,并为其定义一个私有继承类:

    class Originator {
        /* irrelevant declarations skipped */
    
        abstract inner class Memento
    
        private inner class MementoImpl(val state: State) : Memento()
    
        fun saveState(): Memento {
            return MementoImpl(State(id, title, description))
        }
    
        fun restore(memento: Memento) {
            memento as MementoImpl
            id = memento.state.id
            title = memento.state.title
            description = memento.state.description
        }
    }
    

    实现类是private,在Originator之外,实例只会被视为Memento(参见函数签名),因此状态将不可访问。

    【讨论】:

    • 不错!没有考虑到这一点。您如何评价该解决方案?这是您认为应该如何完成的,还是可能有不同的方式,从 java 示例中删除更多但可能更好的 Kotlin 代码?我想说的是,在代码审查中,你会竖起大拇指,还是会对那个空界面持怀疑态度?
    • 我最初的怀疑可能源于这样一个事实,即我更喜欢将接口视为描述某物做什么而不是是什么的东西.在这种情况下,它主要用于破解类型系统,不是吗?不想在这里成为一个聪明的驴子。这是一个实用的解决方案,效果很好,我可能会在生产中使用它。但由于我仍在学习并以此作为练习以更好地理解语言,我只想知道你是否会说这是你认为的“kotlin 方式”
    • 我宁愿同意这是对可见性系统的一种破解,它不允许某个类的成员对某些类可见(除了子类——还有protected,虽然在这里没用)——在Kotlin,该模式不需要 friend 可见性。在 Java 中,封闭类可以访问私有成员,这对于这种模式来说已经足够了。实际上,我看到的接口的一个更大问题是它可以由不同的类实现。我已经编辑了答案,现在它是一个abstract inner class,所以它不能在Originator 的范围之外被继承。
    • 是的!我自己只是在想那个。原始 java 示例的好处是只有 Originator 可以定义它的状态实例。现在它的行为完全相同。谢谢!
    • @DanielW。如果您的代码确实有效,并且您希望获得更多反馈,您应该前往Code Review
    【解决方案2】:

    您应该使用包级访问定义您的 Memento 类属性。

    【讨论】:

    • Kotlin 没有包级访问权限。只有internal 用于模块级访问。
    • 我不认为internal 是这样做的正确方法......而且也不会像Java 中的包私有访问。因为那时我必须根据我的现场可见性需求来组织我的包,或者更糟的是,模块结构。这对我来说似乎更像是一个黑客......
    猜你喜欢
    • 1970-01-01
    • 2012-02-18
    • 2023-03-31
    • 2011-01-06
    • 2013-12-01
    • 2016-07-08
    • 2012-07-09
    • 2021-01-22
    • 2013-12-20
    相关资源
    最近更新 更多