避免具有大量 Java 类 共享字段的重复代码

Avoid duplicate code with numerous Java classes sharing fields

我正在开发一个在 NoSQL 数据库(准确地说是 Elasticsearch)上写入的应用程序,我必须管理十几个(数量随时间增长)不同的文档 classes, classes 有许多字段,它们的 getters 和 setters 以及将 class 转换为 JSONObject 的方法(每个字段都用 @JsonProperty(PROPERTY_NAME) 并且我们使用 JSON 解析器)。

所有这些classes都有一些共同的字段和方法,它们都包含在一个superclass(我们称之为DocZero)中,但它们都有自己的自定义字段,这让我明白了。
确实,这些字段是自定义的,但有些字段以非线性方式在不同的 classes 之间共享,也就是说,我有我的文档 classes Doc1,... DocN 并且我有一些字段集(目前大约有 10 个)由他们以非常疯狂的方式共享。

一些最能说明情况的示例:
Doc1 包含 Set1Set5;
Doc2 包含 Set1Set2Set5Set8
Doc3 包含 Set6Set7
Doc4 包含 Set5Set7
Doc5 包含 Set1Set2Set5Set7Set10

考虑到我需要获取和设置这些字段,并且不时地用它们操作文档,我从 Set# 中创建了接口,每个接口都包含(抽象)setter s 和 getters.
因此,当我声明一个 class

public class DocX implements SetA, SetB, SetC

我被提醒实施这些方法并因此添加必填字段,但这意味着所有实施相同集合的 classes 将需要具有相同的参数和相同的方法,这意味着我需要多次编写相同的代码(有时超过 getter 和 setter 方法)。

将所有字段添加到 DocZero 前面的不同 Doc# classes 是一个我不喜欢使用的解决方案,因为我更喜欢区分不同的文档类型并且因为这种情况以较低的幅度存在于代码的另一部分,其中 AnotherDocZeroAnotherDoc#AnotherSet# 由于其他限制而无法进行合并,我希望这样做也是一个可行的解决方案。
我觉得这是多重继承可以解决问题的情况之一,但不幸的是 Java 不允许这样做。

在这种情况下如何避免重复?你有什么建议来改进我对这个问题的处理吗?

我强烈建议保持您的数据 类 简单,即使这确实意味着您将需要重复许多字段定义 - POJO 绝对更容易维护和理解 "result" 数据对象看起来如果您将所有字段放在一个地方 - 多级继承将很快造成混乱

对于具有适当 getters 的约束,您应该像现在一样使用接口。您甚至可以为每个 getter 创建一个界面,然后将它们分组到另一个界面中,例如

public interface Set1To5 extends Set1, Set2, Set3, Set4, Set5 {}

为了避免 getters/setters 的重复,您可以使用一些额外的库,例如 lombok 或考虑根本不使用 getters/setters(只需将数据文档中的所有字段设为 类 public - 但如果您需要使用接口约束 类,这当然不是选项)

我认为默认方法是一种选择。 https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html

如果几种类型的字段经常组合在一起,这表明分组是程序域的自然组成部分,应该这样表示。

所以,如果您经常在 类

中发现它
   int xCoordinate;
   int yCoordinate;

你应该介绍

public final class Point ... {
   private final int x;
   private final int y;

   Point(int x, int y) {
      ...
   }

   ...
}

然后不要重复 xy,而是写

   Point position;

有一种模式需要探索。我不知道它已经存在或有一个特定的名称。

考虑:

  1. Java 8+接口可以有default methods。这些方法可以使用其他接口方法来定义附加/默认逻辑。 class 实现这样的接口会自动获取这些方法,而无需实现它们。

  2. 另外,一个class可以实现多个接口。

以上两个可以用来在Java中有"easy to compose"类型。

示例:

创建一个可以 store/retrieve 数据的基础接口。这可以很简单:

public interface Document {
    <T> T get(String key);
    void set(String key, Object value);
}

这是将由所有特定数据对象使用的基本功能。

现在,使用上面的接口定义两个只包含特定字段 getter/setters 的接口:

public interface Person extends Document {
    default String getName(){
        return get("name");
    }

    default void setName(String name){
        set("name", name);
    }
}

还有一个:

public interface Salaried extends Document {
    default double getSalary(){
        return get("salary");
    }

    default void setSalary(double salary){
        set("salary", salary);
    }
}

明白了吗?这是一个基于基本 get/set 功能构建的简单模式。您可能希望在实际应用程序中将字段名称定义为常量。

不过到目前为止,都是接口。它没有链接到真实的东西,比如数据库。因此,我们必须为 Document 定义一个使用数据库存储的实现:

public class DBDoc implements Document {
    private final Map<String,Object> data;

    public DBDoc(HashMap<String, Object> data) {
        this.data = new HashMap<>(data);
    }

    public DBDoc(){
        this.data = new HashMap<>();
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T get(String key) {
        return (T) this.data.get(key);
    }

    @Override
    public void set(String key, Object value) {
        this.data.put(key, value);
    }
}

我们使用了一个简单的映射来存储,但它也可以使用数据库连接或数据库特定文档来 get/set 数据。这取决于您使用的数据库或存储。

最后,我们有能力从这些接口中组合类型:

public class Employee extends DBDoc implements Person, Salaried { }

并使用它们:

public static void main(String[] args) {
   Employee employee = new Employee();
   employee.setName("Joe");
   employee.setSalary(1000.00);

   System.out.println(employee.getName());
   System.out.println(employee.getSalary());
}