【问题标题】:How to easily access widely different subsets of fields of related objects/DB tables?如何轻松访问相关对象/数据库表的广泛不同的字段子集?
【发布时间】:2014-10-21 20:37:25
【问题描述】:

假设我们有许多相关的对象(相当于 DB 表),例如:

public class Person {
  private String name;
  private Date birthday;
  private int height;
  private Job job;
  private House house;
  ..
}

public class Job {
  private String company;
  private int salary;
  ..
}

public class House {
  private Address address;
  private int age;
  private int numRooms;
  ..
}

public class Address {
  private String town;
  private String street;
  ..
}

如何最好地设计一个系统,以便轻松定义和访问这些对象/表上广泛变化的数据子集?设计模式,优点和缺点,都非常受欢迎。我正在使用 Java,但这是一个更普遍的问题。

例如,我想轻松地说:

  • 我想要一些带有 (Person.name, Person.height, Job.company, Address.street) 的对象
  • 我想要一些带有 (Job.company, House.numRooms, Address.town) 的对象

其他假设:

  • 我们可以假设我们总是在输入中获得已知的对象结构,例如一个人及其工作、住所和地址。
  • 生成的对象不一定需要知道构造它的字段的名称,即对于定义为 (Person.name, Person.height, Job.company, Address.street) 的子集,它可以是数组对象 {"Joe Doe", 180, "ACompany Inc.", "Main Street"}。
  • 对象/表层次结构复杂,数据字段数以百计。
  • 可能需要定义数百个子集。
  • 可以根据实际字段计算少数要获取的字段,例如我可能想得到一个人的年龄,计算为 (now().getYear() - Person.birtday.getYear())。

以下是我看到的一些选项:

  1. 每个子集的 SQL 视图。 缺点:
    • 对于相似的子集,它们几乎相同。这仅适用于字段名称,但不适用于连接部分,理想情况下可以将其重构到一个公共位置。
    • 比代码解决方案更难测试。
  2. 使用 DTO 汇编器,例如http://www.genericdtoassembler.org/ 这可用于将输入对象的复杂结构扁平化为单个 DTO。 缺点:
    • 我不确定接下来如何轻松定义此 DTO 上的字段子集。也许如果我能以某种方式将与当前子集无关的那些设置为空?不知道怎么做。
    • 不确定我是否可以通过这种方式轻松完成计算字段。
  3. 我想出的自定义映射器。

相关代码:

// The enum has a value for each field in the Person objects hierarchy
// that we may be interested in.
public enum DataField {
  PERSON_NAME(new PersonNameExtractor()),
  ..
  PERSON_AGE(new PersonAgeExtractor()),
  ..
  COMPANY(new CompanyExtractor()),

  ..
}

// This is the container for field-value pairs from a given instance of
// the object hierarchy.
public class Vector {
  private Map<DataField, Object> fields;
  ..
}

// Extractors know how to get the value for a given DataField
// from the object hierarchy. There's one extractor per each field.
public interface Extractor<T> {
  public T extract(Person person);
}

public class PersonNameExtractor implements Extractor<String> {
  public String extract(Person person) {
    return person.getName();
  }
}

public class PersonAgeExtractor implements Extractor<Integer> {
  public int extract(Person person) {
    return now().getYear() - person.getBirthday().getYear();
  }
}

public class CompanyExtractor implements Extractor<String> {
  public String extract(Person person) {
    return person.getJob().getCompany();
  }
}

// Building the Vector using all the fields from the DataField enum
// and the extractors.
public class FullVectorBuilder {
  public Vector buildVector(Person person) {
    Vector vector = new Vector();
    for (DataField field : DataField.values()) {
      vector.addField(field, field.getExtractor().extract(person));
    }
    return vector;
  }
}


// Definition of a subset of fields on the Vector.
public interface Selector {
  public List<DataField> getFields();
}

public class SampleSubsetSelector implements Selector {
  private List<DataField> fields = ImmutableList.of(PERSON_NAME, COMPANY);
  ...  
}

// Finally, a builder for the subset Vector, choosing only
// fields pointed to by the selector.
public class SubsetVectorBuilder {
  public Vector buildSubsetVector(Vector fullVector, Selector selector) {
    Vector subsetVector = new Vector();
    for (DataField field : selector.getFields()) {
      subsetVector.addField(field, fullVector.getValue(field));
    }
    return subsetVector;
  }
}

缺点:

  • 需要为数百个数据字段中的每一个创建一个小型提取器类。
  • 这是我想出的自定义解决方案,似乎可行并且我喜欢它,但 我觉得这个问题之前一定遇到过并解决过,可能会以更好的方式解决.. 有吗?乙>

编辑

  1. 每个对象都知道如何将自己变成一个字段映射,以所有字段的枚举为键。

例如

public enum DataField {
  PERSON_NAME,
  ..
  PERSON_AGE,
  ..
  COMPANY,
  ..
} 

public class Person {
  private String name;
  private Date birthday;
  private int height;
  private Job job;
  private House house;
  ..

  public Map<DataField, Object> toMap() {
    return ImmutableMap
      .add(DataField.PERSON_NAME, name)
      .add(DataField.BIRTHDAY, birthday)
      .add(DataField.HEIGHT, height)
      .add(DataField.AGE, now().getYear() - birthday.getYear())
      .build();
  }
}

然后,我可以构建一个结合所有地图的向量,并从中选择子集,就像在 3 中一样。

缺点:

  • 枚举名称冲突,例如如果 Job 有一个 Address 而 House 有一个 Address,那么我希望能够指定一个子集来获取两者的街道名称。但是如何在 Address 类中定义 toMap() 方法呢?
  • 没有明显的地方可以放置需要来自多个对象的数据的计算字段的代码,例如从房屋地址到公司地址的物理距离。

非常感谢!

【问题讨论】:

  • 不在答案中,因为我觉得我可能误解了这个问题,但是,ORM 解决方案不能解决提取器问题吗?至少对于非计算字段?为了枚举,一些 ORM 解决方案可能会生成列号常量的一些变化。
  • 谢谢!你有一个例子吗?我们快速浏览了一下,发现最接近的是 JDO FetchGroups (db.apache.org/jdo/fetchgroups.html)。但是,我找不到以这种方式定义的查询结果的完整示例。有人知道吗?

标签: sql oop design-patterns dto


【解决方案1】:

相对于应用程序中的内存对象映射,我更倾向于数据库处理数据以获得更好的性能。视图,或更精细的 OLAP/数据仓库工具可以解决问题。如果计算的字段保持基本状态,如“年龄 = 现在 - 出生”,我认为在数据库中使用该逻辑没有任何问题。

在代码方面,鉴于您必须处理大量 DTO,您可以使用无类动态(在某些 JVM 语言中可用)或 JSON 对象。这个想法是,当数据结构发生变化时,您只需要修改 DB 和 UI,从而节省您在两者之间更改一大堆类的成本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-04
    • 2014-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-27
    相关资源
    最近更新 更多