【问题标题】:Save and load objects without breaking encapsulation在不破坏封装的情况下保存和加载对象
【发布时间】:2014-09-15 06:31:06
【问题描述】:

我想在不使用 ORM(如 Hibernate)的情况下将对象保存和加载到数据库。

假设我有以下课程:

public class Person {
    private int age;

    public void birthday(){
        age++;
    }
 }

这个类没有提供get方法,所以封装了内部状态(年龄)。

如果我想保存对象,我需要一个 getter 方法来执行以下操作:

insert into TABLE_PERSON (id, age) vales (1, person.getAge());

反之,我需要一个 setter 方法来加载对象:

int age = "Select age FROM Person";
person.setAge(age);

但我不想打破封装(通过实现额外的 setter 和 getter 方法)只是为了保存和加载对象。

有没有可能做到这一点?也许某种模式(纪念品?)或最佳实践?

提前谢谢你!

【问题讨论】:

    标签: java database design-patterns encapsulation memento


    【解决方案1】:

    你提到了纪念品。 Grand 记录了一个名为Snapshot 的变体。这是 UML 类图:

    Person 将是模式中的 Originator

    您可以将 Memento 实例转储到数据库中的二进制对象中。

    请注意,Memento 是 Originator 的内部类。 createMemento/setMemento 是一种单一的 get/set,如果你是一个纯粹主义者,它可能会破坏封装。但是,调用中使用的信息包(Memento)是一个没有方法的接口,所以保证Originator's状态的封装。如果您正确映射它,这甚至可能适用于 ORM。

    当然,这似乎需要做很多工作才能避免 get/set。 您的 Person/age 示例不太现实(不是一个很好的 Person 模型)。暴露一个人的年龄或出生日期是很正常的,并且暴露该属性以持久化对象是可以的。封装并不意味着不透露任何东西。这意味着不要暴露太多细节。

    例如,我们不要使用age,而是使用Person.birthday。这可以在内部存储为 YYYY-MM-DD 格式的字符串或使用 Date 对象。这样的细节不会被暴露(它会被封装)。这样做也是为了您可以更改它而不影响您班级的客户。您隐藏的任何内容都可以更改,而不会对客户产生负面影响。

    通过暴露一个人的生日,你说“我冒着这个人永远有生日的风险。”这部分是暴露的,因此很难在不破坏客户的情况下进行更改。

    在 cmets 之后编辑

    Person 中的公共方法(例如,saveload)可以处理保存/加载数据库操作。他们将有权访问私有字段 (age) 并可以完成工作。你说你没有使用 ORM,所以你自己做吧。

    【讨论】:

    • 我同意你的观点,纪念品对象隐藏了发起者的状态,这样封装就不会被破坏。我同意我可以将纪念品对象保存为数据库中的二进制对象。这个解决方案只有最后一件事我会错过:我想对存储在数据库中的对象执行 SQL 查询,这是不可能的,因为对象没有映射到表列。我没有在我的问题中指出这一点,很抱歉。
    • Select memento from person 会工作,对@Aeon?每个对象表都会有一个用于 Memento 的列。
    • 是的,没错,但我的意思是别的:我想执行像 Select Max(age) from person 这样的语句。
    • 如果您需要知道 SQL 查询的年龄,那么您为什么要首先隐藏它?
    • 我尝试对其他 java 对象隐藏年龄(或其他属性),因为我想确保封装。我需要通过 SQL 访问属性以进行不同的分析。 SQL 查询在 java 应用程序“外部”。
    【解决方案2】:

    Allen Holub 写了几篇关于这个主题的文章。他提出了一个 Builder 和他称之为 Reverse Builder 模式的东西。

    在您的情况下,Person 将负责生成自己的表示。也就是说,您定义了一个 PersonImporter 和 PersonExporter 接口来将数据移入和移出 Person 对象。这些类基本上是 Person 设计的一部分。所以:

    interface PersonImporter {
    
        public int getAge();
    
        public String getId();
    }
    
    interface PersonExporter {
    
        public void setDetails(String id, int age);
    
    }
    
    class Person {
    
        private int age;
        private String id;
    
        public Person(PersonImporter importer) {
            age = importer.getAge();
            id = importer.getId();
        }
    
        public void export(PersonExporter exporter) {
            exporter.setDetails(id, age);
        }
    
    }
    

    这并没有完全消除 getter 和 setter,而是使用接口来控制它。

    Holub's Article

    【讨论】:

      【解决方案3】:

      这是我的想法,可能并不完美。

      封装用于数据隐藏。也许,您只是不希望有人为age 属性设置错误的值。

      也许您可以引入一个包装类,供外部代码使用,并且只允许该类使用您的Person 类。

      如果你创建一个带有public 包装类的文件,比如说PersonWrapper,它提供了年龄的getter 和setter。但是这些 getter 和 setter 可以具有您想要的验证逻辑,例如可以为 age 设置哪些值,谁可以等。在同一个文件中,Person 类可以定义为 private,但使用普通的 getter 和 setter对于age 参数。您的 PersonWrapper 应该只在某些预定义条件下使用 Person 类 getter 和 setter。

      这样你也许可以有更好的封装。

      【讨论】:

        【解决方案4】:

        这里的一种方法是使用映射器类。创建一个将类映射到表的 PersonMAP,并将所有数据库操作封装在该类中。

        【讨论】:

          【解决方案5】:

          我真的不明白使用 getter/setter 是如何破坏封装的。 getter 和 setter 尊重封装。事实上,它们是实现它的一种手段。

          【讨论】:

          • 你否决了我并将我链接到一个问题,该问题的最佳答案的第一行通过说“Getter 和 setter 不违反封装,它们提供它。”与我一致?请解释一下。
          • 也许我不是那个投反对票的人?该问题的链接显示您的立场是有争议的(阅读顶部答案中的 cmets)。另请阅读我对链接中问题的评论。
          • 在这种情况下,我很抱歉匆忙下结论。但如果那些反对我的人真的花时间解释原因,我将不胜感激。对我来说,封装只是通过受控接口公开数据,而这正是使用 getter 和 setter 实现的目的。
          • 每次我对 stackoverflow 投了反对票,我都把它当作一个学习的机会(打开我的思维并考虑替代我的观点)。在我发布的链接上的讨论中有很多 cmets 应该回答“为什么”。
          猜你喜欢
          • 1970-01-01
          • 2018-05-09
          • 2021-02-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-03-09
          • 1970-01-01
          • 2023-03-25
          相关资源
          最近更新 更多