我正在与一个团队合作开发一个新的Java API,用于我们的一个内部项目。我们可能无法花时间停止并散布Java接口的所有细节,并在开始时使它们100%完美。
我们有一些核心功能必须预先设置,其他的可能会在以后添加,但现在不重要,+现在花时间设计这些功能是我们没有的奢侈品。特别是因为我们还没有足够的信息来获得所有的设计细节。
API的Java方法是,一旦你发布了一个接口,它实际上是不可变的,你永远不应该改变它。
有没有办法计划API随时间的演变? 我读了 这个问题 我想我们可以做到这一点:
// first release
interface IDoSomething
{
public void hop();
public void skip();
public void jump();
}
// later
interface IDoSomething2 extends IDoSomething
{
public void waxFloor(Floor floor);
public void topDessert(Dessert dessert);
}
// later still
interface IDoSomething3 extends IDoSomething2
{
public void slice(Sliceable object);
public void dice(Diceable object);
}
然后升级我们的课程从支持 IDoSomething
至 IDoSomething2
接着 IDoSomething3
,但这似乎有一个代码气味问题。
然后我猜是有的 番石榴标记界面的方式 @Beta
因此,应用程序可以在冻结之前使用这些风险,但我不知道这是否正确。
如果您想要灵活的代码泛型可以提供帮助。
例如,而不是:
interface FloorWaxer
{
public void waxFloor(Floor floor);
}
你可以有:
interface Waxer<T>
{
void wax(T t);
}
class FloorWaxer implements Waxer<Floor>
{
void wax(Floor floor);
}
另外,Java 8带来了 default methods
在接口中,允许您在现有接口中添加方法;考虑到这一点,您可以使界面通用。这意味着您应该使您的接口尽可能通用;代替:
interface Washer<T>
{
void wash(T what);
}
然后再添加
interface Washer<T>
{
void wash(T what);
void wash(T what, WashSubstance washSubstance);
}
然后添加
interface Washer<T>
{
void wash(T what);
void wash(T what, WashSubstance washSubstance);
void wash(T what, WashSubstance washSubstance, Detergent detergent);
}
你可以从头开始添加
@FunctionalInterface
interface Washer<T>
{
void wash(T what, WashSubstance washSubstance, Detergent detergent);
default wash(T what, WashSubstance washSubstance)
{
wash(what, washSubstance, Detergent.DEFAULT_DETERGENT);
}
default wash(T what, Detergent detergent)
{
wash(what, WashSubstance.DEFAULT_WASH_SUBSTANCE, detergent);
}
default wash(T what)
{
wash(what, WashSubstance.DEFAULT_WASH_SUBSTANCE, Detergent.DEFAULT_DETERGENT);
}
}
此外,尝试使您的接口功能(只有一个抽象方法),这样您就可以从lambdas中获益。
你可以采取这种方法 挂毯5 已经采取了它称为“自适应API”(更多信息 这里)。
tapestry不使用锁定接口,而是使用注释和pojo。我不完全确定你的情况,但这可能适合也可能不适合。请注意,tapestry使用ASM(通过 塑料)在引擎盖下,以便没有运行时反射来实现这一点。
例如:
public class SomePojo {
@Slice
public void slice(Sliceable object) {
...
}
@Dice
public void dice(Diceable object) {
...
}
}
public class SomeOtherPojo {
@Slice
public void slice(Sliceable object) {
...
}
@Hop
public void hop(Hoppable object) {
...
}
}
你可以用一个 新版API的新软件包名称 - 这将允许新旧API并排,API用户可以一次将其组件转换为新的API。你可以提供一些 适配器 帮助他们在使用新旧API的对象通过类之间的边界传递的边界上进行繁重的工作。
另一种选择非常苛刻,但可以用于内部项目 - 只是 改变你的需求并使用户适应。
如果您只是添加,提供 默认实现 (在抽象类中)新方法可以使过程更加平滑。当然,这并不总是适用。
通过更改主要版本号来发送更改信号,在两种情况下提供有关如何将代码库升级到新版API的详细文档。
我建议看看这些 结构模式。我觉得 装饰图案 (也称为自适应模式)可以满足您的需求。请参阅链接的Wikipedia文章中的示例。
这是我处理这种情况的方式。
首先,我使用抽象类,以便您以后可以插入默认实现。随着JDK 1.1中内部和嵌套类的出现,接口几乎没有增加;几乎所有的用例都可以很容易地转换为使用纯抽象类(通常作为嵌套类)。
首发
abstract class DoSomething {
public abstract void hop();
public abstract void skip();
public abstract void jump();
}
第二次发布
abstract class DoSomething {
public abstract void hop();
public abstract void skip();
public abstract void jump();
abstract static class VersionTwo {
public abstract void waxFloor(Floor floor);
public abstract void topDessert(Dessert dessert);
}
public VersionTwo getVersionTwo() {
// make it easy for callers to determine whether new methods are supported
// they can do if (doSomething.getVersionTwo() == null)
return null;
// OR throw new UnsupportedOperationException(), depending on specifics
// OR return a default implementation, depending on specifics
}
// if you like the interface you proposed in the question, you can do this:
public final void waxFloor(Floor floor) {
getVersionTwo().waxFloor();
}
public final void topDessert(Dessert dessert) {
getVersionTwo().topDessert();
}
}
第三次发布将类似于第二次发布,所以为了简洁,我将省略它。
如果您还没有设计最终的API,请不要使用您想要的名称!
称之为V1RC1,V1RC2,...,当它完成后,你有V1。
人们会在他们的代码中看到他们仍在使用RC版本并且可以删除它以在准备好时获得真实的东西。
Rostistlav 基本上是说同样的,但他称之为所有真正的API版本,所以它将是V1,V2,V3,....认为这取决于你的口味。
您还可以尝试事件驱动的方法,并在API更改时添加新的事件类型,而不会影响向后兼容性。
例如:
public enum EventType<T> {
SLICE<Sliceable>(Sliceable.class),
DICE<Diceable>(Diceable.class),
HOP<Hoppable>(Hoppable.class);
private final Class<T> contextType;
private EventType<T>(Class<T> contextType) {
this.contextType = contextType;
}
public Class<T> getContextType() {
return this.contextType;
}
}
public interface EventHandler<T> {
void handleEvent(T context);
}
public interface EventHub {
<T> void subscribe(EventType<T> eventType, EventHandler<T> handler);
<T> void publish(EventType<T> eventType, T context);
}
public static void main(String[] args) {
EventHub eventHub = new EventHubImpl(); // TODO: Implement
eventHub.subscribe(EventType.SLICE, new EventHandler<Sliceable.class> { ... });
eventHub.subscribe(EventType.DICE, new EventHandler<Diceable.class> { ... });
eventHub.subscribe(EventType.HOP, new EventHandler<Hoppable.class> { ... });
Hoppable hoppable = new HoppableImpl("foo", "bar", "baz");
eventHub.publish(EventType.HOP, hoppable); // fires EventHandler<Hoppable.class>
}