5

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

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 地图等)。有什么方向吗?

4

6 回答 6

5

创建除了扩展已经存在的集合之外什么都不做的类似乎是不必要地增加实体

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

但会让我的代码更清晰

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

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

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

于 2009-07-01T10:45:53.310 回答
1

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

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

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

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

于 2009-07-01T10:48:08.937 回答
1

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

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

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

不过,封装通常是一件好事,特别是因为它可以帮助您遵守得墨忒耳法则——也许您可以将您将对集合执行的一些操作推回 AllConnections 类(这实际上是一个 DAO)。这通常可以帮助进行单元测试。

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

class AllConnections extends HashMap<String, CustomerConnections> {
    public LastConnection get(String custId, String srvUrl) {
        return get(custId).get(srvUrl);
    }
}
于 2009-07-01T18:44:49.470 回答
0

为什么不custId+"#"+srvurl作为一个简单的键使用Map<String, LastConnection>

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

于 2009-07-01T10:46:34.197 回答
0

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

我建议如下:

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<Customer>给使用此信息的方法。

于 2009-07-01T10:48:29.187 回答
-1

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

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具有标准、定义明确的协定的 a,但您避免扩展库类,这通常是不受欢迎的。

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

于 2009-07-01T10:42:49.803 回答