【问题标题】:Inheriting and encapsulating collection classes in JavaJava中集合类的继承和封装
【发布时间】:2009-07-01 10:36:32
【问题描述】:

假设我有以下类型的数据:

class Customer {
  String id; // unique
  OtherCustData someOtherData;
}

class Service {
  String url; // unique
  OtherServiceData someOtherData;
}

class LastConnection {
  Date date;
  OtherConnData someOtherData; // like request or response
}

现在我需要记住每个客户何时连接到每个服务。
我会制作结构:

Map<Customer, Map<Service, LastConnection>> lastConnections;

或者,能够通过 id 进行搜索,而不必编写所有的 equal() 和 hashCode():

Map<String, Map<String, LastConnection>> lastConnections;

现在我可以通过

访问 LastConnection 数据了
LastConnection connection = lastConnections.get(custId).get(srvUrl);

所有这一切看起来都很难看,尤其是我必须将它作为参数传递给数十个期望 LastConnections 映射的方法,所以我正在考虑创建自己的类,看起来像这样:

class CustomerConnections extends HashMap<String, LastConnection> {
}

class AllConnections extends HashMap<String, CustomerConnections> {
    public LastConnection get(String custId, String srvUrl) {
        return get(custId).get(srvUrl);
    }
}

好的,我已经知道继承是 3v1l,所以让我们尝试组合:

class CustomerConnections {
    Map<String, LastConnection> customerConnections;
    LastConnection get(String srvUrl) { 
        return customerConnections.get(srvUrl);
    }
    ... // all other needed operations;
}

class AllConnections {
    Map<String, CustomerConnections> allConnections;
    public LastConnection get(String custId, String srvUrl) {
        return get(custId).get(srvUrl);
    }
    public CustomerConnection get(String custId) {
        return allConnections.get(custId);
    }
    ... // all other needed operations;
}

问题是我不确定尊重 SOLID 原则和所有最佳实践的最佳方法是什么。创建除了扩展已经存在的集合之外什么都不做的类似乎是不必要地增加实体,但会使我的代码更清晰(特别是当有下一个级别时 - 例如按月划分的 AllConnections 地图等)。有什么路线吗?

【问题讨论】:

    标签: java inheritance collections encapsulation


    【解决方案1】:

    创建什么都不做的类 除了扩展已经存在的 收藏似乎成倍增加 不必要的实体

    我会将扩展更改为封装。您正在隐藏有关如何存储此信息的详细信息。您班级的客户不需要知道您如何向他们提供客户连接历史记录。我认为这是一个好主意,因为您可以更改底层模型,而无需让 api 的客户端更改其代码。

    但会让我的代码更清晰

    这很好,也是这样做的一个很好的理由。 YourClass.getCustomerConnection(cId) 比 yourCollection.get(id).get(id).getConnection() 清晰得多。您需要让使用此代码的人的生活更轻松,即使您就是那个人。

    (特别是当有下一个级别时 - 例如按月划分的 AllConnections 地图等)

    很好,那么您正在提前计划并使您的代码可扩展。这是很好的 OO 实践。在我看来,你自己达成的结论是我会做的。

    【讨论】:

      【解决方案2】:

      我会创建一个专门的对象来存储这些信息。您正在创建的是一个 ma​​nager 对象,而不是一个简单的集合。

      不会让它派生自 Map 或其他著名的集合类,因为您存储此信息的方式的语义将来可能会发生变化。

      改为实现一个将客户及其连接联系在一起的类,并在该类中使用适当的集合类(您可以稍后更改而不影响接口和其余代码)

      您的客户/连接管理器类不仅仅是一个简单的容器。它可以存储元数据(例如,何时建立这种关系)。它可以对给定客户信息的连接执行搜索(如果需要)。它可以按照您的要求处理重复项,而不是底层集合类如何处理它们等等。您可以轻松地进行调试/日志记录/性能监控,以轻松了解正在发生的事情。

      【讨论】:

        【解决方案3】:

        我认为您还应该考虑如何使用集合以及如何获取数据:

        • 如果它们是显示在页面上的简单结果集(例如),那么使用标准集合似乎是合理的 - 然后您可以使用大量标准库来操作它们。
        • 另一方面,如果它们是可变的并且任何更改都需要持久化(例如),那么将它们封装在您自己的类中可能会更好(然后可以将更改写入数据库等)。

        您如何获取和保存数据?如果它存储在数据库中,那么也许您可以使用 SQL 按客户和/或服务和/或月份选择 LastConnections,而不必自己维护数据结构(并且只返回简单的列表或连接图或连接数)。或者您可能不想为每个请求运行查询,因此需要将整个数据结构保存在内存中。

        封装通常是一件好事,特别是因为它可以帮助您遵守Law of Demeter - 也许您可以将您将对集合执行的一些操作推回 AllConnections 类(这实际上是一个道)。这通常有助于单元测试。

        另外,考虑到您只想添加一个微不足道的辅助方法,为什么在这里扩展 HashMap 被认为是邪恶的?在您的 AllConnections 代码中,AllConnections 的行为方式始终与 HashMap 相同——它是多态可替换的。当然,您可能会将自己锁定为使用 HashMap(而不是 TreeMap 等),但这可能并不重要,因为它具有与 Map 相同的公共方法。但是,您是否真的想要这样做确实取决于您打算如何使用集合 - 我只是不认为您应该自动将实现继承视为 always 不好(它通常是!)

        class AllConnections extends HashMap<String, CustomerConnections> {
            public LastConnection get(String custId, String srvUrl) {
                return get(custId).get(srvUrl);
            }
        }
        

        【讨论】:

          【解决方案4】:

          为什么不在简单的Map&lt;String, LastConnection&gt; 中使用custId+"#"+srvurl 作为键?

          或者使用 Tuple 或 Pair 类作为包含两个 ID 并实现 hashCode()equals() 的键 - “最干净”的 OO 解决方案。

          【讨论】:

          • 使用 custId+"#"+srvUrl 作为键不会让我快速获得所有给定客户的连接。但是无论如何感谢元组库的想法,我会研究它。
          • 好的,这不是您最初的问题描述的一部分;在这种情况下,地图可能是最好的选择。
          【解决方案5】:

          您为什么要维护这些对象之外的对象之间的关系。

          我建议如下:

          public class Customer 
          {
            public List<LastConnection> getConnectionHistory() 
            {
              ... 
            }
          
            public List<LastConnection> getConnectionHistory(Service service) 
            {
              ...
            }
          
            public List<LastConnection> getConnectionHistory(Date since) 
            {
              ...
            }
          }
          
          public class LastConnection
          {
            public Date getConnectionTime() 
            {
              ...
            }
          
            public Service getService()
            {
              ...
            }
          }
          

          然后您将List&lt;Customer&gt; 传递给使用此信息的方法。

          【讨论】:

          • 但是我的 Customer 类需要了解 LastConnection 类。我最终可能会在各种包之间进行大量循环。再见,可重用性...假设我的代码中有四个包:Core、LastConnections、AddressBook 和 TransactionHistory(只是示例)。如果我让 mypackage.core.Customer 了解(并管理)LastConnections、AddressBook 和 TransactionHistory,它将造成混乱。我更喜欢 LastConnection 知道 Customer 和 AddressBook 知道 Customer 等等,而不是其他方式。这样实现下一个功能不会改变现有代码。
          • 通过包我假设你的意思是罐子?在这种情况下,我将在使用这些关系的应用程序中定义域类(如上所述)。域类可以专门化或封装打包类,我倾向于封装,因为您可以隐藏与您的应用程序无关的打包类的属性/特性。我通常在涉及“值对象”时使用它,但应用程序不应该看到“值对象”公开的设置器。另一个好处是,如果打包的类发生变化,您的应用程序就会被隔离。
          【解决方案6】:

          您可以创建一个实现 Map&lt;K,V&gt; 的类,并在内部委托给容器映射:

          class CustomerConnections implements Map<String,LastConnection> {
              private Map<String, LastConnection> customerConnections;
          
              @Override
              public LastConnection get(Object srvUrl) { 
                  return customerConnections.get(srvUrl);
              }
              // all other needed operations;
          }
          

          这种方法的好处是你可以传递一个Map,它有一个标准的、定义明确的契约,但你避免了扩展库类,这通常是不受欢迎的。

          编辑:正如下面所指出的,这确实有一个缺点,即要求您实现很多方法,这些方法除了委托给底层集合之外什么都不做

          【讨论】:

          • 除了“所有其他需要的操作”这个无辜的小评论隐藏了样板委托的度量标准。
          • 虽然你只需要这样做一次。 DelegatingMap 实现 Map 可以用作所有此类扩展的基类。
          猜你喜欢
          • 1970-01-01
          • 2010-11-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-12-02
          相关资源
          最近更新 更多