我正在为一个内部项目的新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
所以应用程序可以在被冻结之前使用这些风险,但我也不知道这是否正确。
答案 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>
}