2014-10-08 2 views
11

Ich arbeite mit einem Team an einer neuen Java-API für eines unserer internen Projekte. Wir werden uns wahrscheinlich nicht die Zeit nehmen können, alle Details der Java-Interfaces zu stoppen und zu hacken und sie am Anfang zu 100% perfekt zu machen.Was ist der geeignete Weg, um eine Java-API mit neuen Funktionen im Laufe der Zeit zu planen?

Wir haben einige Kernfunktionen, die vorne sein müssen, und andere, die wahrscheinlich später hinzugefügt werden, sind aber jetzt nicht wichtig, + die Zeit zu nehmen, diese Funktionen jetzt zu entwerfen, ist ein Luxus, den wir nicht ' t haben. Zumal wir noch nicht genug Informationen haben, um alle Designdetails richtig zu machen.

Der Java-Ansatz zu APIs ist, dass sobald Sie eine Schnittstelle veröffentlichen, es praktisch unveränderbar ist und Sie es nie ändern sollten.

Gibt es eine Möglichkeit, API Evolution im Laufe der Zeit zu planen? Ich habe this question gelesen und ich nehme an, wir könnten dies tun:

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

und dann unsere Klassen Upgrade IDoSomething-IDoSomething2 unterstützen und dann IDoSomething3, aber dies scheint ein Code Geruch Problem zu haben.

Dann denke ich, es gibt die Guava way of marking interfaces with @Beta so Anwendungen können diese in Gefahr, bevor sie eingefroren werden, aber ich weiß nicht, ob das auch richtig ist.

+2

, wenn Ihr Projekt intern ist, ich bin nicht sicher, ob diese Aussage viel gilt „Der Java-Ansatz für APIs ist, dass wenn Sie eine Schnittstelle veröffentlichen, es ist effektiv unveränderlich ...“. Als internes Projekt können Sie die Änderungen Ihrem Team mitteilen. Wenn diese Art der Kommunikation nicht möglich ist.Sie können Factories verwenden, um abhängig von der Clientversion die richtige API bereitzustellen (aus Gründen der Abwärtskompatibilität), sodass der Client wissen muss, dass er eine versionierte API verwendet und die in der Anforderung verwendete Version enthält (oder einen anderen Einstiegspunkt bereitstellt) für neue Versionen) – ochi

+0

Hinweis: Eines der Hauptprobleme mit Interfaces wurde mit Java 8 gemildert: Sie können jetzt 'defaults' Methoden zu Interfaces hinzufügen, was die Dinge sehr vereinfacht und die Einhaltung der Regel erleichtert. *" When in Zweifeln Sie, lassen Sie es aus "*, auf das Josh Bloch in seinem exzellenten Vortrag über" Wie man eine gute API entwickelt und warum es wichtig ist ", verweist http://www.infoq.com/presentations/effective-api-design. Abgesehen davon, ein weiterer Zeiger: Ich denke, dass das Wiki (und das Buch) von http://wiki.apidesign.org/wiki/TheAPIBook einen Blick wert ist. – Marco13

+0

danke Marco ... yeah, ich habe etwa 10 Minuten vor dem Stellen dieser Frage eine Kopie von Tulach's Buch bestellt. –

Antwort

2

Sie könnten den Ansatz nehmen, den tapestry-5 genommen hat, die es "adaptive API" (mehr Info here) abruft.

Anstelle von gesperrten Schnittstellen verwendet der Tapestry Anmerkungen und Pojos. Ich bin mir Ihrer Lebensumstände nicht ganz sicher, aber das mag gut passen oder nicht. Beachten Sie, dass Tapisserie ASM (über plastic) unter der Haube verwendet, so dass es keine Laufzeitreflexion gibt, um dies zu erreichen.

ZB:

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

interessant .... das ist ein neues Konzept für mich, also bin ich nicht sicher, ob wir Anmerkungen auf diese Weise verwenden können ... aber vielleicht würde es für uns funktionieren. –

3

Wenn Sie flexible Code Generika helfen können.

Zum Beispiel statt:

interface FloorWaxer 
{ 
    public void waxFloor(Floor floor); 
} 

können Sie haben:

interface Waxer<T> 
{ 
    void wax(T t); 
} 

class FloorWaxer implements Waxer<Floor> 
{ 
    void wax(Floor floor); 
} 

Auch Java 8 default methods in Schnittstellen gebracht, die Ihnen erlauben Methoden in bereits bestehenden Schnittstellen hinzuzufügen; In diesem Sinne können Sie Ihre Schnittstellen generisch machen. Dies bedeutet, dass Sie Ihre Schnittstellen so allgemein wie möglich gestalten sollten. Statt:

interface Washer<T> 
{ 
    void wash(T what); 
} 

und dann später hinzufügen

interface Washer<T> 
{ 
    void wash(T what); 
    void wash(T what, WashSubstance washSubstance); 
} 

und fügen Sie später

interface Washer<T> 
{ 
    void wash(T what); 
    void wash(T what, WashSubstance washSubstance); 
    void wash(T what, WashSubstance washSubstance, Detergent detergent); 
} 

Sie von Anfang an

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

auch hinzufügen können, versuchen zu machen Ihre Schnittstellen funktionieren (nur eine abstrakte Methode), damit Sie von lambdas Zuckern profitieren können.

1

Ich würde vorschlagen, einen Blick auf diesen Structural Pattern zu nehmen. Ich denke, die Decorator pattern (auch bekannt als Adaptive Muster) kann Ihre Bedürfnisse erfüllen. Siehe das Beispiel im verlinkten Wikipedia-Artikel.

+0

Ich denke, dass dies der erwähnte Tapestry 5 ist. –

+0

@RostislavMatl Vielleicht weiß ich Tapestry nicht. Aber vom Doc kann ich sehen, dass es eine "Adaptive API" ist, aber vielleicht ist es kein "Adaptives Muster". Ich weiß nicht, wie diese Anmerkungen genau funktionieren. –

2

Sie könnten einen neuen Paketnamen für die neue Version von API verwenden - dies würde ermöglichen, dass alte und neue API Seite an Seite leben und API-Benutzer ihre Komponenten einzeln auf die neue API umwandeln können. Sie können einige Adapter zur Verfügung stellen, die Ihnen helfen, an Grenzen zu arbeiten, wo Objekte über Grenzen zwischen Klassen mit neuen und alten APIs weitergegeben werden.

Die andere Option ist ziemlich hart, aber könnte für interne Projekt arbeiten - nur ändern, was Sie brauchen, und Benutzer zur Anpassung machen.

Wenn Sie nur hinzufügen, vorausgesetzt, Standardimplementierung (in einer abstrakten Klasse) der neuen Methoden können den Prozess reibungsloser machen. Natürlich ist das nicht immer anwendbar.

die Änderung Signalisierung durch eine Hauptversionsnummer detaillierte Dokumentation über das Ändern, wie die Code-Basis auf die neue Version von API ist in beiden Fällen eine gute Idee zu aktualisieren.

1

Hier ist die Art, wie ich diese Situation angehen.

Zuerst würde ich abstrakte Klassen verwenden, damit Sie später Standardimplementierungen anschließen können. Mit dem Aufkommen von inneren und verschachtelten Klassen in JDK 1.1 fügen Schnittstellen wenig hinzu; Fast alle Anwendungsfälle können bequem in reine abstrakte Klassen (oft als verschachtelte Klassen) konvertiert werden.

Erstveröffentlichung

abstract class DoSomething { 
    public abstract void hop(); 
    public abstract void skip(); 
    public abstract void jump(); 
} 

Zweiter Release

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

dritte Veröffentlichung wäre zweite ähnliche, also werde ich es der Kürze halber weggelassen werden.

+0

es ist mir im laufe der jahre in den kopf gebohrt worden, dass abstrakte klassen schlecht sind und die schnittstellen gut sind ... Ich werde eine weile brauchen, um deinen point zu verarbeiten + mich mit meinem früheren lernen zu versöhnen. –

+0

Es gibt viele Beispiele für solche Java-Wahnsinn, wie "Wildcard-Importe sind schlecht, weil ich wissen muss, indem ich auf die Spitze meiner Klasse genau welche Klassen abhängen" und verschiedene andere Kastanien, die nicht gut zu prüfen. Aber um es kurz zu formulieren: "Der Bogen des IT-Universums ist lang, aber er neigt sich zur Vernunft." :) –

0

Wenn Sie die endgültige API nicht entwickelt haben, verwenden Sie nicht den gewünschten Namen dafür!

Nennen Sie es so etwas wie V1RC1, V1RC2, .. und wenn es fertig ist, haben Sie V1.

Menschen werden in ihrem Code sehen, dass sie immer noch eine RC-Version verwenden und dass entfernen können, die reale Sache zu bekommen, wenn es fertig ist.

Rostistlav sagt im Grunde das gleiche, aber er nennt sie alle echte API-Versionen, also wäre es V1, V2, V3, .... Denken Sie, dass das nach Ihrem Geschmack ist.

0

Sie könnten auch einen ereignisgesteuerte Ansatz versuchen und neue Ereignistypen als API-Änderungen hinzufügen, ohne Abwärtskompatibilität zu beeinflussen.

zB:

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> 
} 
+0

Hmm. Das Ändern eines Aufzählungstyps zum Hinzufügen neuer Mitglieder ist fraglich. –

+0

Es könnte leicht angepasst werden, Strings als Themen zu verwenden. Die Enum ist gut für die Überprüfung der Kompilierzeit. –

Verwandte Themen