如何解决原始对象(如 Integer、String)不允许派生的限制?

How to work-around the restriction of primitive objects - like Integer, String - not being allowed to derive from?

我意识到无法从原始对象派生,因为它们被声明为 final。我该如何解决这个限制?我正在使用 JPA 标准 API 进行编程。几乎所有我处理的地方都使用我自己的方法,这些方法具有 Integer/String 参数来与表示数据库 table 行值的实体字段进行比较。对于这些参数中的任何一个,我都愿意接受 QueryParameter<Integer>QueryParameter<String>。这样做我将不得不第二次创建该方法来接受查询参数而不是文字。但是,考虑具有置换文字和查询参数的值列表(如 QueryBuilder 的 in(...) 方法),很难甚至不可能实现。

让我们假设我有一个实体 Car 和一个方法 withFeatures(StringRepresentation ... features) 并且会有文字和查询参数派生自相同的 super-class StringRepresentation它本身是从原始类型 String 派生的。我愿意这样做:

myCar.withFeatures("Seat Heating", "Metallic Color", "Trailer Hitch");
myCar.withFeatures(new QueryParam<String>("MyFavourit"));
myCar.withFeatures("Seat Heating", new QueryParam<String>("LoveThatColor"), "Trailer Hitch");

有没有人对此有方法或解决方案?

我会针对每种类型的标准使用一种方法的构建器模式。

class Car {
  private Set<String> features = new HashSet();

  public Car withFeature(String f) {
    features.add(f);
    return this;
  }

  public Car withFeature(QueryParameter<String> q) {
    features.add(q.getStringRepresentation()); // or whatever
    return this;
  }
  ...
}

所以你可以说:

myCar.withFeature("Seat Heating")
   .withFeature(new QueryParam<String>("MyFavourit");

with permutating literals and query parameters, makes it hard or even impossible to implement

您可以将 CharSequence 用于字符串,但我不确定这是个好主意...

import lombok.RequiredArgsConstructor;

public class Test {
    public static void main(String[] args) {
        withFeatures("test", new StringQueryParam("test2"));
    }

    @SafeVarargs
    public final static <T extends CharSequence> void withFeatures(T ...params) {
        // Wrap in StringQueryParam if not an instance of QueryParam<String>
    }

    interface QueryParam<T> {
    }

    @RequiredArgsConstructor
    static class StringQueryParam implements QueryParam<String>, CharSequence {
        private final CharSequence value;

        @Override
        public int length() {
            return value.length();
        }

        @Override
        public char charAt(int index) {
            return value.charAt(index);
        }

        @Override
        public CharSequence subSequence(int start, int end) {
            return value.subSequence(start, end);
        }
    }
}

减少冗长的静态工厂方法(例如,QueryParam.ofQueryParam.all 等查询参数)与构建器或有效组合它们的方法可能会有所帮助。

例如

// Assuming Lists.union util method
withFeatures(Lists.union(
    QueryParam.all("a", "b"),
    QueryParam.of("c")
));

// With static imports
withFeatures(union(params("a", "b"), param("c"));

// With ParamsBuilder
withFeatures(ParamsBuilder.of("a", "b").add(QueryParam.of("c").build())));

希望这能给您一些关于如何设计 API 的想法!您也可以使用更复杂但更灵活的路由,其中​​整个条件只是一个 AST,因此 QueryParam 实际上只是 AST 中的一种 Expression 允许创建复合等。如果您查看 QueryDSL 一切都是 DslExpression 并且您有访问者对树执行操作。

到目前为止,我花了一些时间从 Java 社区获取提示来解决这个问题。 当然,我是 Java 类型安全概念的追随者(感谢 plalx)。因此,我的解决方案可能与参数化类型有关。 而且我也像许多其他人一样欣赏设计模式的概念(感谢 tgdavies)。因此,我将构建器模式与一种方法一起用于每种类型的标准。我将接受为

实施汽车特征方法
  1. 使用字符串的普通旧文字
  2. 以及指定字符串参数

即:

myCar.withFeatures("Seat Heating", "Metallic Color", "Trailer Hitch");

以及通过使用静态方法 sp(...)

以稍微复杂的方式指定(比方说)某种类型的查询参数或字符串参数
myCar.withFeatures(sp("MyFavourit"));

当然还有两者的混合,引入另一个用于字符串表示的静态方法 sr(...):

myCar.withFeatures(sr("Seat Heating"), sp("LoveThatColor"), sr("Trailer Hitch"));

在我们想要在方法签名中使用可变参数来指定这些表示的情况下,两者的混合很重要,在本例中为汽车特征。 可以看出,这几乎就是我在发布这个问题时上面所说的用法。 我怎样才能做到这一点?

起初我设计了一个接口来实现我的不同字符串表示:

public interface ValueTypeRepresentation<T> {
    public Class<T> getClazz();
    public QueryParameter<T> getQueryParameter();
    public RepresentationType getRepresentationType();
    public T getValue();
}

方法是判断representation是文字还是参数,并分别获取文字的值。参数本身稍后使用它的名称。 clazz 成员是为了简化 Java 通用类型推断的目的,因为我将使用参数化类型来实现不同的类型表示。正如我所说,String 只是节目的开始。

然后我设计了一个抽象 class 来导出不同原始对象的具体 class 表示:

abstract class AbstractValueTypeRepresentation<T> implements ValueTypeRepresentation<T>  {
    private Class<T> clazz;
    private RepresentationType representationType = RepresentationType.VALUE;
    private QueryParameter<T> queryParameter;
    private T value;

    public AbstractValueTypeRepresentation(Class<T> clazz, T value) {
        this.clazz = clazz;
        this.representationType = RepresentationType.VALUE;
        this.value = value;
    }

    public AbstractValueTypeRepresentation(QueryParameter<T> qp) {
        this.clazz = qp.getClazz();
        this.representationType = RepresentationType.PARAM;
        this.queryParameter = qp;
    }

    @Override
    public Class<T> getClazz() {
        return clazz;
    }

    @Override
    public QueryParameter<T> getQueryParameter() {
        return queryParameter;
    }

    @Override
    public RepresentationType getRepresentationType() {
        return representationType;
    }

    @Override
    public T getValue() {
        return value;
    }
}

为了区分该类型的文字和该类型的查询参数,我引入了这个枚举:

public enum RepresentationType {
    PARAM, VALUE;
}

然后我设计了第一个具体表示,这里是我的StringRepresentation(源自上面的抽象class):

public class StringRepresentation extends AbstractValueTypeRepresentation<String>  {
    public static StringRepresentation sr(String s) {
        return new StringRepresentation(s);
    }

    public static StringRepresentation sp(String name) {
        return new StringRepresentation(new QueryParameter<String>(String.class, name));
    }

    public StringRepresentation(String value) {
        super(String.class, value);
    }

    public StringRepresentation(QueryParameter<String> queryParameter) {
        super(queryParameter);
    }
}

显然这很容易扩展到 Integer、Float、LocalDate 等的表示