1

我有三种类型的对象:

  • 组件:由包和名称标识
  • 模型:由包和名称标识
  • 功能:由名称标识

关系定义如下:

  • 一个组件可以有零个或多个模型 [0..*]
  • 一个模型可以有零个或多个函数 [0..*]
  • 一个函数可以在一个或多个模型中 [1..*]
  • 一个模型可以是零个或多个组件[0..*]

自然的设计是让 Component 拥有一组对 Model(s) 的引用,让 Model 拥有一组对 Function 的引用。通过这种设计,我可以轻松地以自上而下的方式导航关系(并轻松回答诸如“此模型中包含哪些功能?”之类的查询。

问题是我需要更多的灵活性。我想要一些易于导航的东西来回答这些查询:

  1. 给定一个函数名称,该函数在哪些模型中以及在哪些组件中被引用?
  2. 给定一个模型包+名称,该模型引用了哪些组件?
  3. 有没有被任何组件引用的模型?

HashMap<Component, Model>我曾想过将组件、模型和函数作为简单的 POJO,并使用多个 HashMap ( , Hashmap<Model, Component>, HashMap<Model, Function>, )跟踪它们之间的引用,HashMap<Function, Model>但这对我来说似乎效率低下。

你能给我推荐一些更好的设计吗?

4

3 回答 3

2

表示您的问题域的逻辑数据结构是一个,但这样做实际上不会为您提供回答您作为示例引用的查询的“有效”方法。了解这些查询是否仅仅是您从您想象的更大集合中想到的示例,或者它们是否构成了完整的需求规范,这将有所帮助。

我怀疑你不会喜欢这个答案,但我认为你在这里最能受益的是关系数据库。如果您希望避免使用此类数据库的一些正常复杂性,您可以嵌入一个将所有数据保存在内存中的数据库。SQLite 是一种需要考虑的关系数据库,但还有许多其他可用于 Java 的数据库。

我根据你的措辞得出这个结论。您提到在两个方向上导航图形边缘(或实体之间的聚合关系) 。这在问题的关系模型中表达起来很简单,但是当您使用内存中的结构(例如您提出的映射)时变得非常困难;前者对关系之间的外部引用没有隐含的方向性,而后者只能表示从一个实体到另一个实体的单向引用。

在关系模型中,您可以如下表达事实:

  • 有称为Components的实体,它们具有这些属性。
  • 有一些名为Models的实体,它们具有这些其他属性。
  • ComponentsModels之间存在链接,其中每个Model都可以从任意数量的Components中“访问” ,并且每个Component “可以访问”任意数量的Models。(请注意,我没有写“包含在”或“拥有”或任何其他暗示排他性的关系。)

关系模型允许人们根据这些关系评估查询,而不会对链接“指向”的方式有任何偏见。当然,链接是具有某种意义的断言——可能是定向的,使逻辑图成为有图而不是无向图——特定于您的问题域,但理解该含义的是您的应用程序,而不是数据库或关系模型它的运作。

除了逻辑模型之外,尽可能高效地回答您的查询将需要您指定数据库维护一些基于非约束的索引。一些记录可以有效地查找,而无需您要求超出完整性约束的任何特殊内容,因为数据库可能会自行构建索引以帮助有效执行所述约束。但是,虽然它可能能够快速告诉您给定的ComponentModel之间是否已经存在任何配对,但它无法回答哪些Components(如果有)引用了特定的Model

请求数据库维护此类索引类似于您维护您最初提出的一些内存中映射,但设计方法有所不同:在您的设计中,一些可能出现的查询无法回答根本没有,因为这些关系不会以一种可以导航的方式被捕获,即使是低效的。但是,对于数据库,适应新查询通常是定义附加索引以帮助加快对已经存在的数据的查询. 换句话说,数据库仍然可以回答您的新查询;它可能不得不以令人尴尬的方式挣扎。直到您定义了正确的索引,它才能像您预期的那样高效地处理这些查询。

我在这里再提一点。在这里使用关系数据库在技术上可能有点过分,但它是正确的教学选择。这是您的问题应得的解决方案。您可以构建更窄、更量身定制的东西,提供数据库功能的一小部分并满足您的需求,但这样做,我认为您错过了这里更大的设计课程。您将使用您的问题来学习有关如何实现数据库的知识,而不是学习如何使用数据库来建模您的问题。使后者成为可能和容易是业界提供这种数据库技术的原因。

于 2012-05-28T15:12:34.293 回答
1

允许您跟踪您在单个集合中描述的所有关系的数据结构是MultiMap. Java 教程的Map Interface部分讨论了 Java MultiMaps (您必须向下滚动到 MultiMaps 部分或点击链接并在页面上搜索 MultiMap;本教程的该部分没有直接锚点) . MultiMapJava有可用的实现:

使用 a MultiMap,您可以为您的对象类型创建一个映射,该映射可以包含或聚合其他对象类型:

//Associate multiple Models to one Component:
multiMap.put( componentD, modelN );
multiMap.put( componentD, modelO );
multiMap.put( componentD, modelP );
//Associate multiple Models to a Component (some different, some the same)
multiMap.put( componentE, modelQ );
multiMap.put( componentE, modelR );
multiMap.put( componentE, modelN ); //also associated with componentD
//And associate multiple Functions to one Model:
multiMap.put( modelQ, functionG );
multiMap.put( modelQ, functionH );
multiMap.put( modelQ, functionI );

您可以稍后检索与Collection任何映射键关联的那个。这是使用 Apache Commons Collections MultiHashMapapi-doc的示例:

Collection modelFunctions = multiMap.get( modelQ );

这种方法将使自顶向下从ComponenttoModel到to 遍历变得容易Function。但是,如果将每个关系的两端都添加到MultiMap. 例如,要建立 aModel和 a之间的关系Function,您可以:

multiMap.put( modelR, functionJ );
multiMap.put( functionJ, modelR );

因为这两种关系都已被映射,所以您可以轻松检索Function包含在 a 中的所有 s Model(如上例所示),或者同样轻松地检索所有Model包含 a 的 s Function

Collection functionModels = multiMap.get( functionJ );

当然,这也意味着如果你想打破关系,你必须记住从 MultiMap 中删除这两个映射,但这相当简单。我希望这可以帮助你 -

于 2012-05-28T16:29:29.650 回答
1

另一种选择是从创建查询中抽象出来,因此当它成为性能/易用性方面的瓶颈时,可以轻松地优化/扩展某些实现。

这样的界面如下所示:

public interface IEntityManager {

    // methods for retrieving entities by plain queries

    Component getComponent(String package, String componentName);

    Model getModel(String package, String modelName);

    Function getFunction(String name);

    // more specific queries

    List<Component> getComponents(Function function);

    List<Model> getModels(Function function);

    // even more specific queries ...
}

通过这种方式,可以在生产代码中使用此接口,同时提供具有所需性能级别的任意实现。

现在,关于具体实现 - 取决于是否 allComponent和实例及其关系Model略有不同:Function

  1. 在应用程序开始时的某处创建(例如,在桌面应用程序的方法开始处或在Web应用程序中),并且在应用程序执行期间不会更改mainServletContextListener.contextInitialised
  2. 在应用程序执行期间创建/删除/更新

让我们从第一种情况开始,因为它更简单:应该确保所有Component,ModelFunction实例(以及它们之间的关系)对于IEntityManager使用这些实体的逻辑之间共享的实例都是已知的。实现这一点最直接的方法之一是将实体类与实现放在同一个 Java 包中IEntityManager,并使它们的构造函数包私有,从而将创建逻辑移至具体IEntityManager实现:

package com.company.entities;

public class Component {
    public final String package;
    public final String name;

    Component(String package, String name) {
        this.package = package;
        this.name = name;
    }

    // ...
}

// similar Model and Function class declarations

public class EntityManager implements IEntityManager {

    private final Map<Pair<String, String>, Component> components = new HashMap<Pair<String, String>, Component>();
    private final Map<Pair<String, String>, Model> models = new HashMap<Pair<String, String>, Model>();
    private final Map<String, Function> functions = new HashMap<String, Function>();

    // variation of factory-method
    public Component addComponent(String package, String name) {
        // only this EntityManager can create instances
        // so one can be sure all instances are tracked by it
        final Component newComponent = new Component(package, name);
        components.put(new Pair(package, name), newComponent);
    }

    // ... addModel, addFunction methods

    public void addFunctionToModel(Function function, Model model) {
        // here one should store 'somehow' information that function is related to model
    }

    public void addModelToComponent(Model model, Component component) {
        // here one should store 'somehow' information that model is related to component
    }

    // ... other methods
}

请注意,在第一种情况下(所有实体都是在应用程序开始时创建的),也可以使用Builder模式来创建EntityManager类的实例——这将清楚地从使用中抽象出创建逻辑。但是,在第二种情况下,应该在类中具有类似addComponent,的方法addModelToComponent(对于单个实例的多线程使用,EntityManager应考虑使方法修改其状态thread-safe)。

最后,关于应该如何存储实体之间的关系:没有有效存储/检索具有这种关系的实体的灵丹妙药。我想说,如果一个实体的关系不超过 1000 个,那么搜索HashMap<String, Function>将很快,不应该成为瓶颈。如果它成为瓶颈,应该彻底检查哪种查询更频繁使用,哪些很少使用,并根据观察调整EntityManager内部实现 - 因此建议抽象所有IEntityManager接口。

Concerning instance of EntityManager in application - obviously, there should be only one instance of it (unless one has very special case). One can achieve this using Singleton pattern (less preferred solution, though it may work fine for some time), or simply instantiating EntityManager at the beginning of application and passing explicit instances to classes / methods which need it (more preferred solution).

Hope this helps ...

于 2012-05-29T00:34:45.593 回答