0

要求:我希望接口的所有实现都具有明确定义的名称。

一开始,我想:

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();
    }
}

2)强制实现者Fruit用一些东西注释名称:

class Apple implements Fruit {
    @FruitName
    public static final NAME = "apple";
    ...    
}

显然这个实现比(1)干净得多,但我不确定这在 Java 中是否可行?如果@FruitName 不存在,您如何使编译/构建失败?

4

4 回答 4

3

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

  • 在构建时,您可以为寻找满足您要求的字段的每个类编写测试Fruit
  • 在构建时,您可以编写一个测试来遍历整个类路径并验证每个Fruit类是否满足您的要求。像Reflections这样的库可以帮助您实现这一目标。
  • 在编译时,您可以处理 Annotation。我不确定您将如何确保您的每个类都有一个注释(相反,每个包含注释的类都是您集中的类之一。)
  • 在实现时,作为您请求的细微变化,您可以使用抽象类而不是接口,并要求所有实现者在构造函数中向您提供固定数据。这样,您就可以绝对控制行为。
  • 在运行时,当应用程序启动时,您可以检查所有实现类是否满足您的要求,就像集成测试一样。在第三方为您的 API 做出贡献的情况下,如果您绝对必须检查它,这可能是最后一站的选择。

我认为最好为此使用测试。通过更好的反馈和更少的努力,您将获得所需的所有确定性。如果测试不是一个选项,因为您无法控制实施者,我会选择在启动期间强制执行的抽象类作为最后的手段。

于 2012-07-17T20:11:13.827 回答
3

一种简单的方法——无需 aop、编译时编织、运行时注释、运行时扫描……等就是将此行为封装在一个抽象类中:

interface Fruit {
  public String getName();
}

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

  public final String getFruitName(){
    return name;
  }

}

因此,在构建时,每个实现都将被迫传递其名称,并且无法更改它(除非用户是故意恶意的)。这符合问题的措辞所暗示的内容。

但是有区别,因为一些建议似乎假设接口的所有实现都将具有相同的名称 - 尽管问题没有说明这一点。这些实现将是单例的想法吗?

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

class FruitWrapper implements Fruit{
  private final String name;
  public FruitWrapper(Fruit fruit) {
    this.name = fruit.getFruitName();
  }

  public final String getFruitName(){
    return name;
  }

}

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

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

于 2012-07-18T06:26:29.663 回答
1

你不是很困惑staticfinal

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

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

@FruitName("apple")
class Apple implements Fruit

并考虑使用简单的类名:

Fruit fruit = new Apple();
fruit.getClass().getSimpleName();  //"Apple"

但是如果你在某个地方依赖类名,简单的重构就会毁掉代码的其他部分。所以我会认为注释更稳定。


奖励:您的问题很容易在中解决:

trait Fruit {
  val name: String  //abstract AND final
}

class Apple extends Fruit {
  val name = "apple"  //you MUST implement this
}

如果你不“实现” val name(实际上它是一个不可变的字段),编译器会坚持标记Apple抽象。

于 2012-07-17T20:07:14.213 回答
0

你应该可以用 aspectj 和编译时间来做

于 2012-07-17T20:26:05.063 回答