【问题标题】:Law of Demeter - why do I need to use a getter?得墨忒耳法则 - 为什么我需要使用吸气剂?
【发布时间】:2014-06-26 10:43:13
【问题描述】:

我有一个关于得墨忒耳定律与 Java 中其他对象中包含的列表相关的问题。我有以下课程。

public class Conversation
{
    Person person;
    List<Message> conversationList;

    public List<Message> getConversationList()
    {
        return conversationList;
    }
}

要向此类中的 conversationList 添加新的 Message 对象,我通常会执行以下操作。

Conversationc = new Conversation();
c.getConversationList().add(new Message());

经过一番阅读,这似乎违反了得墨忒耳法则,并向 Converstaion 添加如下方法,这将是解决此问题的“更好”方法。

public List<Message> addMessageToList(Message msg)
{
    conversationList.add(msg);
}

但是,这对我来说似乎完全是矫枉过正。在这种情况下,最佳做法是什么?

【问题讨论】:

  • 我会使用第二种方法。签名应为public void addMessage(Message msg)
  • 第二个提供了更好的抽象性和可读性(你应该认真考虑@LutzHorn 的建议......这是你作为使用 OO 语言的程序员想要的。
  • @TheLostMind 是的,即使只是使用模块化语言 :-)
  • 另外,如果您担心好的做法:您的消息列表被命名为会话列表,并且该类拼写错误两次,这看起来就像在实际代码中一样。
  • 一个额外的词(对于已经很好的 cmets 和答案):考虑设计!您希望进行包含消息的对话。使用列表的公共 getter,您设计了一个对话,其中包含一个由消息组成的列表。看出区别了吗?

标签: java list law-of-demeter


【解决方案1】:

但是,这对我来说似乎完全是矫枉过正。

啊,我记得当我刚开始时反复问自己这个问题 - 封装这个词是我为了测试而学到的,但并不真正理解它的“必要性”。

我这么说是因为 封装 是我们在这里谈论的内容 - 对您的问题的一个词的回答:

通过getter 访问conversationList - 这就是您在此处发布的第二种模式的名称 - 封装您的对话类中的对话列表。当您编写的程序具有三个或四个类的顶部并且具有既简单又固定的要求时,很难理解为什么这很重要:当您仍在思考这些小程序的工作原理时,整个概念“添加一个吸气剂”而不是直接访问字段似乎只是一件要做的事情,一件你不真正理解的事情向上。

当然,这种感觉会随着您变得更加自信而消失,而且会发生的其他事情是您将开始考虑和处理更大更复杂的应用程序,并且其要求会发生变化(两者实际上和对它们的看法)随着时间的推移,这通常意味着你的程序需要改变——这就是封装开始变得非常有意义的时候:

现在:假设您的老师更改了他明天分配给您的这项作业,为您一直在进行的该计划添加了一个无法预料的要求(顺便说一句,我认为这对老师来说是一件明智的事情)。想象一下,满足该要求的唯一方法是将对话列表保存在 Map 而不是 List 中,这样您就可以将它们中的每一个映射到其他值。

如果您一直在使用“您的第一种方法” - 调用 conversationsList.add() 以直接在许多不同的类中访问该字段,那么您将不得不检查并更改每个类以调用 conversations.put().add()

但是,如果您一直在使用第二种方法 - 通过 getter 访问您的集合 - 那么您可以更改该集合,无论您需要多么重要,您只需对该 getter 进行一次更改:

 public void addMessageToList(Message msg)
{
    this.myMessagesManager.put(msg, new ArrayList<SomeValue>);
    //or .put(msg, null) or whatever
}

你的其他类永远不会知道有什么变化,这很好,因为这意味着你编写的代码更少,尤其是很好,因为它让你不必做很多最糟糕的事情编码 - 当你对其中一个类进行这些小改动之一时,通常会涉及到捕捉由你所犯的小错误引起的错误。

(另请参阅my answer to this question 以获得更好的说明)

【讨论】:

  • 好答案。因此,如果我想引入一个返回消息列表的 getMessageList() 方法。我是否应该使这个返回的列表不可变,以便不能在 Conversation 类之外进行修改?还是我应该在这里做一些不同的事情?
  • Immutabilityaccess 是两个非常不同的东西。封装某些东西并不意味着使其不可变,它意味着阻止其他类(直接)访问它,我们通过在其上添加访问修饰符(例如privateprotected)来做到这一点。您应该查看这两个链接,尤其是后者。
  • 所以如果我没有“被允许”直接访问列表,那么我的对象的任何 get 方法也应该像 add 方法一样包装?
【解决方案2】:

使用第一种方法,您无法更改conversationList 的实现。

假设您决定使用一个全新的类来处理消息,比如说MessagesManager,那么您必须更改每个认为是List 的类中的每个调用。

但是用public void addMessage(Message msg);,那么你可以使用

public void addMessageToList(Message msg)
{
    this.myMessagesManager.addMesasge(msg);
}

你已经封装了你的数据模型,Conversation 用它的Messages 做它想做的事,没有人会关心。

【讨论】:

  • 我同意你的看法。为了保持 addMessage 接口的稳定,我们既不应该使用 [conversationList.add] 也不应该使用 [conversationMap.put],但我们可以将其委托给 MessageManager 类,该类管理消息.
【解决方案3】:

这是角色和责任的问题。 在第一种方法中,Role 负责将消息添加到会话列表,但它实际上应该是会话对象的责任。 在第二种方法中,您将该责任委托给 Conversation 对象。 您告诉 Conversation 对象存储一条消息,仅此而已,您不在乎它是如何完成的。

【讨论】:

    【解决方案4】:

    两者都不是。方法的名称应该代表将在对象实例上执行的操作。

    在您的情况下,以add 开头的对话方法有点模棱两可。所以问题是该消息的进一步行动是什么。

    对话有两个方面,你可以听或说。

    converstion.listen(Message)conversation.listenMessage(Message)

    当您设计一个 API(公共方法)时,请尝试使用它并查看它的行为。

    conversation.listen(whatTomSad());
    vs
    conversation.listenMessage(whatTomSad());
    

    在这个级别上很难决定。我会选择第一个。因为它也提供了灵活性和适当的 API 抽象。

    免得假设我们的 API 会增长,并且不仅会影响服务器 Message

    在解决方案的情况下,我们只重载方法,并让 API 相当扩展,但我们不强制用户采用。他可以很容易地改变他的方法的返回类型。

    conversation.listen(whatTomSad());
    conversation.listen(whatTomSing())
    vs
    conversation.listenMessage(whatTomSad());
    conversation.listenSong(whatTomSad();
    

    为了简化和总结。方法名称不仅应取决于对象,还应取决于其 API 用户(开发人员)将使用的上下文。它应该具有以下属性: - 自我表达 - 不奇怪 - 易于重构

    要平衡这是相当复杂的。最好的学习方法。观察其他开发人员的代码并从他们的错误中学习。

    【讨论】:

      猜你喜欢
      • 2023-04-04
      • 2016-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-12
      相关资源
      最近更新 更多