Java:强制接口的实现者拥有一个不可变的名称字段?

Java:强制接口的实现者拥有一个不可变的名称字段?,java,scala,Java,Scala,要求:我希望接口的所有实现都有一个定义良好的名称 起初,我想: interface Fruit { public String getName(); } 但这允许用户拥有在运行时修改的字段。我希望有一个在编译/构建时之前定义的不可变名称 我一直在尝试其他几种方法,但每种方法都有局限性 1为名称指定一个类型,该类型比自由格式字符串具有略多的控制权: interface Fruit { public FruitName getName(); } abstract class Fr

要求:我希望接口的所有实现都有一个定义良好的名称

起初,我想:

interface Fruit {
    public String getName();
}
但这允许用户拥有在运行时修改的字段。我希望有一个在编译/构建时之前定义的不可变名称

我一直在尝试其他几种方法,但每种方法都有局限性

1为名称指定一个类型,该类型比自由格式字符串具有略多的控制权:

interface Fruit {
    public FruitName getName();
}

abstract class FruitName  {
    public final String NAME;
    public FruitName(name) {
        this.NAME = name;
    }
}
此类用户的外观如下所示:

class AppleFruitName extends FruitName {
    public AppleFruitName() {
        super("apple");
    }
}

class Apple implements Fruit {
    public FruitName getName() {
        return new AppleFruitName();
    }
}
class FruitWrapper implements Fruit{
  private final String name;
  public FruitWrapper(Fruit fruit) {
    this.name = fruit.getFruitName();
  }

  public final String getFruitName(){
    return name;
  }
2.强制Fruit的实现者在名称上添加注释:

class Apple implements Fruit {
    @FruitName
    public static final NAME = "apple";
    ...    
}
很明显,这个实现远比1干净,但我不确定这在Java中是否可行?如果@FROUTNAME不存在,您如何使编译/构建失败?

您是否混淆了静态和最终版本

就接口/类而言,这是最好的。也可以使用自定义注释,但方式略有不同:

@FruitName("apple")
class Apple implements Fruit

并考虑使用简单类名:

Fruit fruit = new Apple();
fruit.getClass().getSimpleName();  //"Apple"
但是,如果您依赖于某个类名,简单的重构将破坏代码的其他部分。所以我认为注释更稳定。< /P> 好处:您的问题可以通过以下方式轻松解决:


如果您不实现val name,实际上它是一个不可变字段,编译器将坚持标记Apple abstract。

有几个选项可以强制执行此操作

在构建时,您可以为每个水果类编写测试,以查找满足您需求的字段。 在构建时,您可以编写一个测试来遍历整个类路径,并验证每个水果类是否满足您的需求。像这样的库可以帮助您实现这一点。 在编译时,您可以。我不知道如何确保每个类都有注释,而不是每个包含注释的类都是集合中的一个类。 在实现时,作为对您的请求的一个微小变化,您可以使用抽象类而不是接口,并要求所有实现者将构造函数中的固定数据交给您。这样,你就可以完全控制自己的行为。 在运行时,当应用程序启动时,您可以检查所有实现类是否满足您的需求,方法与集成测试相同。在第三方为您的API提供帮助的场景中,如果您必须检查,这可能是最后一个停止选项。 我认为最好使用测试来实现这一点。通过更好的反馈和更少的努力,您将获得所需的所有确定性。
如果测试不是一个选项,因为您无法控制实现者,我会选择在启动期间强制执行的抽象类作为最后手段。

您应该能够使用aspectj和编译时来实现这一点。

这是一种简单的方法,无需aop、编译时编织、运行时注释和运行时扫描。。etc将此行为封装在一个抽象类中:

interface Fruit {
  public String getName();
}

abstract class FruitImpl  {
  private final String name;
  public FruitImpl(name) {
    this.name = name;
  }

  public final String getFruitName(){
    return name;
  }
}

因此,在构建时,每个实现都将被迫以其名称传递,并且除非用户有意恶意,否则它将无法更改它。这符合问题措辞的建议

尽管存在差异,因为一些建议似乎假设接口的所有实现都将具有相同的名称——尽管问题并没有说明这一点。这些实现是单例的吗

或者,您可以使用decorator模式包装实现并检索字段值一次,然后始终稍后返回该值,如下所示:

class AppleFruitName extends FruitName {
    public AppleFruitName() {
        super("apple");
    }
}

class Apple implements Fruit {
    public FruitName getName() {
        return new AppleFruitName();
    }
}
class FruitWrapper implements Fruit{
  private final String name;
  public FruitWrapper(Fruit fruit) {
    this.name = fruit.getFruitName();
  }

  public final String getFruitName(){
    return name;
  }
}

因此,你可以在任何地方使用它,你可以使用水果,它将保证始终获得相同的价值


通过这种方式,您可以将不变性移到您控制的类中。

使用类的名称如何?例如:someImplementation.getClass.getCanonicalNameGood suggestion。这只是一个说明我的问题的例子,实际上名称和类名可能会不同。第一个建议只确保FruitName中的字符串是最终的。具有水果名的实现者仍然可以在每次调用getName时提供完全不同的水果名-这肯定与您试图实现的目标背道而驰?@plasma147这是真的,但至少有一些控制措施。返回任意字符串要比返回类的任意实现容易得多。有效的一点,实际上是相同的新水果名apple vs=apple:-D它添加了一个额外的类,该类没有其他函数来提供错误的安全感,从而使Api变得复杂。最明智的方法是对其进行javadoc,并依靠人来实现接口定义的契约。否则,您是否打算阻止他们使用aop代理或字节码操作返回随机值?您是否要使用相同的长度来检查任何哈希代码或相等值
他们实施的方法?如果他们不履行这些合同,而你在集合中使用这些合同,这也可能会破坏你的应用程序。关于你的第二部分,你如何确保Fruit的实现者在编译时有注释@FruitName?@jabalsad:我能想到的唯一编译时检查是在编译期间工作。使用AspectJ,你可以做很多很多事情,不一定要在编译时完成。我的意思是在运行时之前如何检查注释。对不起,在我看来,我没有区分编译时和构建时。我得考虑一下区别。如果FROUT的实现者是一个单独项目的一部分呢?你们图书馆的消费者这对我来说是一个微妙的不同。在这个模型中,构建时包括几个阶段,其中编译时只是一个特定的阶段。如果你不区分它们,这对你来说可能没什么关系,你也能顺利地完成测试。如果所有这些都是独立项目的一部分,那么您要么必须为每个项目设置一个测试,要么创建一个集成测试来加载所有模块并在那里执行其验证。是的,我知道构建和编译之间有细微的区别。在这种情况下,我不确定这有多重要——我对运行前的任何解决方案都持开放态度。问题是我不一定是接口的唯一使用者,我不能强迫外部使用者编写测试。好的,我已经用另一个建议更新了答案,以解决该问题。谢谢你的回答,我怀疑抽象类是最简单的方法。你答案的第一部分是我最终的目的。我不希望接口的实现具有相同的名称-它们应该是唯一的,但这完全是另一回事。