随着时间的推移,使用新功能规划Java API的适当方法是什么?

时间:2014-10-08 16:06:53

标签: java api interface

我正在为一个内部项目的新Java API上的团队工作。我们可能无法花时间停止并散布Java接口的所有细节,并在开始时使它们100%完美。

我们有一些核心功能必须预先设置,其他的可能会在以后添加,但现在不重要,+现在花时间设计这些功能是我们的奢侈品#39; t。特别是因为我们还没有足够的信息来获得所有的设计细节。

API的Java方法是,一旦你发布了一个界面,它就是不可改变的,你永远不应该改变它。

有没有办法计划API演变?我已阅读this question,我想我们可以这样做:

// 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,但这似乎有代码异味问题。

然后我想那里是Guava way of marking interfaces with @Beta所以应用程序可以在被冻结之前使用这些风险,但我也不知道这是否正确。

7 个答案:

答案 0 :(得分:3)

如果您想要灵活的代码泛型可以提供帮助。

例如,而不是:

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中获益。

答案 1 :(得分:2)

您可以采用tapestry-5所采用的方法“自适应API”(更多信息here)。

而不是锁定接口,tapestry使用注释和pojo。我不完全确定你的情况,但这可能适合也可能不适合。请注意,tapestry使用ASM(通过plastic),因此没有运行时反射来实现这一点。

例如:

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) {
      ...
   }
}

答案 2 :(得分:2)

您可以为新版API 使用新软件包名称 - 这将允许并行使用新旧API,API用户可以将其组件转换为新的API一时间您可以提供一些适配器来帮助他们在使用新旧API的对象之间跨越边界的边界上进行繁重的工作。

另一种选择非常苛刻但可以用于内部项目 - 只需改变您的需求并使用户适应

如果您只是添加,提供新方法的默认实现(在抽象类中)可以使过程更顺畅。当然,这并不总是适用。

通过更改主要版本号来表示更改,在两种情况下都提供有关如何将代码库升级到新版API的详细文档。

答案 3 :(得分:1)

我建议你看看这些Structural Pattern。我认为Decorator pattern(也称为自适应模式)可以满足您的需求。请参阅链接的维基百科文章中的示例。

答案 4 :(得分:1)

这是我处理这种情况的方式。

首先,我使用抽象类,以便您以后可以插入默认实现。随着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();
    }
}

第三次发布会与第二次发布类似,所以为了简洁,我会省略它。

答案 5 :(得分:0)

如果您还没有设计最终的API,请不要使用您想要的名称!

称之为类似V1RC1,V1RC2,......并且当它完成时,你有V1。

人们会在他们的代码中看到他们仍在使用RC版本并且可以删除它以便在准备就绪时获得真实的东西。

Rostistlav基本上是这样说的,但他称之为所有真正的API版本,所以它将是V1,V2,V3,......认为这符合您的口味。

答案 6 :(得分:0)

您还可以尝试使用事件驱动的方法,并在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>
}