En java, peut-on créer une hiérarchie de classe extensible courante avec des méthodes qui peuvent être invoquées dans n'importe quel ordre?

peut-on créer une hiérarchie de classe extensible en java dont les méthodes sont couramment utilisées et peuvent être invoquées dans n'importe quel ordre? (Oui! voir la réponse ci-dessous), même pour les classes existantes lorsque vous n'avez pas accès à la source, à condition que les méthodes soient fluides!

j'ajuste une hiérarchie existante et j'espère utiliser une usine ou au moins un constructeur générique et (éventuellement) des modèles de constructeurs immuables (JB p. 14). les méthodes qui fixent les champs retournent void - il serait mieux pour eux de retourner un T générique à la place - de cette façon nous gagnerons la capacité de faire le chaînage de méthode (ils appellent tous super maintenant).

Objectifs:

1. éviter d'avoir à créer une méthode getfactory statique() dans chaque classe.

2. de Simples signatures de méthode.

3. crée une méthode d'usine qui est générique, mais qui aura des problèmes au moment de la compilation.

4. récupérez les erreurs de compilation au lieu des erreurs d'exécution lorsque des erreurs sont commises.

Comme demandé, le code non générique est très simple, mais ne fonctionne pas.

public class A {
    private String a = null;
    protected A setA(String a){
        this.a = a;
        return this;//<== DESIRE THIS TO BE CHAINABLE
    }
    protected static A factory(){
       return new A();
    }
}  

.

public class B extends A {
    private String b = null;
    protected Foo setB(String b){
        this.b = b;
        return this;//<== DESIRE THIS TO BE CHAINABLE
    }
    protected static B factory(){
        return new B();
    }
}

maintenant un appelant pourrait essayer d'appeler B.factory().setA("a").setB("b")//won't compile

mais cela ne peut pas être compilé car setA() renvoie un A , pas un B . Vous pouvez faire en sorte que cela fonctionne en remplaçant le setA() dans B , en appelant setB() et en retournant B au lieu du A . Pour éviter de déléguer pour chacune de ces méthodes est le point. Je veux simplement un groupe extensible de méthodes de classe qui peuvent être invoquées dans n'importe quel ordre. B.getFactory().B("b").A("a") fonctionne évidemment.

2
demandé sur ggb667 2013-08-03 03:15:05

4 réponses

la réponse (à ma surprise et satisfaction) est oui. J'ai répondu à cette question moi-même: Vous pouvez le faire avec un peu de travail si la méthode invocations renvoie des instances de la classe en question (voir ci-dessous). J'ai également trouvé un moyen encore plus facile de le faire si vous pouvez modifier le niveau supérieur source:

dans la classe supérieure (A):

protected final <T> T a(T type) {
    return type
}

en supposant que C s'étend B et B s'étend A.

invoquant:

C c = new C();
//Any order is fine and you have compile time safety and IDE assistance.
c.setA("a").a(c).setB("b").a(c).setC("c");

exemples 1 et 3 sont des façons de rendre une hiérarchie de classe existante fluide et de permettre aux méthodes d'être appelées dans n'importe quel ordre tant que les classes existantes sont fluides (mais vous n'avez pas accès à ou ne pouvez pas changer la source). WAY2 est un exemple où vous avez accès à la source, et souhaitez que les appels soient aussi simples que possible.

Full SSCCE:

import static java.lang.System.out;

public class AATester {
    public static void main(String[] args){

        //Test 1:
        for(int x: new int[]{ 0, 1, 2 } ){
            A w = getA(x);
            //I agree this is a nasty way to do it... but you CAN do it.
            Chain.a(w.setA("a1")).a(w instanceof C ? ((C) w).setC("c1") : null );
            out.println(w);
        }

        //Test for WAY 2: Hope this wins Paul Bellora's approval 
        //for conciseness, ease of use and syntactic sugar.
        C c = new C();
        //Invoke methods in any order with compile time type safety!
        c.setA("a2").a(c).setB("b2").a(c).set("C2");
        out.println(w);

        //Example 3, which is Example 1, but where the top level class IS known to be a "C"
        //but you don't have access to the source and can't add the "a" method to the 
        //top level class.  The method invocations don't have to be as nasty as Example 1.
        c = new C();
        Chain.a(c.setA("a3")).a(c.setB("b3")).a(c.setC("c3"));//Not much larger than Example 2.
        out.println(w);
    }
    public static getA(int a){//A factory method.
        A retval;//I don't like multiple returns.
        switch(a){
            case 0:  retval = new A(); break;
            case 1:  retval = new B(); break;
            default: retval = new C(); break;
        }
        return retval;
    }
}

essai Classe A

public class A {
   private String a;
   protected String getA() { return a; }

   //WAY 2 - where you have access to the top level source class.
   protected final <T> T a(T type) { return type; }//This is awesome!       

   protected A setA(String a) { this.a=a; return this; }//Fluent method
   @Override
   public String toString() {
      return "A[getA()=" + getA() + "]";
   }
}

Classe D'épreuve B

public class B extends A {
   private String b;
   protected String getB() { return b; }
   protected B setB(String b) { this.b=b; return this; }//Fluent method
   @Override
   public String toString() {
      return "B[getA()=" + getA() + ", getB()=" + getB() + "]\n  " 
      + super.toString();
  }
}

Classe D'Épreuve C

public class C extends B {
   private String c;
   protected String getC() { return c; }
   protected C setC(String c) { this.c=c; return this; }//Fluent method
   @Override
   public String toString() {
      return "C [getA()=" + getA() + ", getB()=" + getB() + ", getC()=" 
             + getC() + "]\n  " + super.toString();
   }
}

La Chaîne de la classe

/**
 * Allows chaining with any class, even one you didn't write and don't have 
 * access to the source code for, so long as that class is fluent.
 * @author Gregory G. Bishop ggb667@gmail.com (C) 11/5/2013 all rights reserved. 
 */
public final class Chain {
   public static <K> _<K> a(K value) {//Note that this is static
      return new _<K>(value);//So the IDE names aren't nasty
   }
}

classe helper Chain.

/** 
 * An instance method cannot override the static method from Chain, 
 * which is why this class exists (i.e. to suppress IDE warnings, 
 * and provide fluent usage). 
 *
 * @author Gregory G. Bishop ggb667@gmail.com (C) 11/5/2013 all rights reserved.
 */
final class _<T> {
   public T a;//So we may get a return value from the final link in the chain.
   protected _(T t) { this.a = t }//Required by Chain above
   public <K> _<K> a(K value) {
      return new _<K>(value);
   }
}

sortie:

A [get(A)=a]
B [get(A)=a, getB()=null]
  A [getA()=a]
C [getA()=a, getB()=null, getC()=c)]
  B [get(A)=a, getB()=null]
  A [get(A)=a]

QED. :)

Je n'ai jamais vu personne faire cela; je pense que cela pourrait être une technique nouvelle et potentiellement précieuse.

P. S. en ce qui concerne l '"utilisation comme elvis", il est de 1 ou 2 lignes vs 8 ou plus.

Book b = null; 
Publisher p = null; 
List books = null; 
String id = "Elric of Melnibone";

books = Chain.a(b = findBook(id)).a(b != null ? p = b.getPublisher() : null)
                                 .a(p != null ? p.getPublishedBooks(): null).a;

out.println(books==null ? null : Arrays.toString(books.toArray()));

vs:

Book b = null; 
Publisher p = null; 
List books = null; 
String id = "Elric of Melnibone";

b = findBook(id);
Array[] books = null;
if( b != null ) {
    p = b.getPublisher();
    if(p != null) {
        books = p.getPublishedBooks();
    }
} 

out.println(books==null ? null : Arrays.toString(books.toArray()));

pas NPE, et si la chaîne complète vous obtenez tous les livres publiés par L'éditeur de "Elric of Melnibone" (i.e. tous les livres "Ace" publishers a publié), et si non vous obtenez un null.

3
répondu ggb667 2017-05-23 10:29:30

je crois qu'il existe un moyen de le faire avec des génériques... La syntaxe est un peu moins propre que le désiré...

voici le code client...

    B<B> b = B.factoryB();
    b.setA("a").setB("b");

    A<A> ba = A.factoryA();

    ba.setA("a");

Haut niveau (réel) de la classe

public  class A<S extends A> extends Chained<S> {
    private String a = null;

    protected A() {
        }

    public S setA(String a) {
        this.a = a;
        return me();
    }
    public static A<A> factoryA() {
        return new A<A>();
    }
}

Exemple De Sous-Classe

public  class B<S extends B> extends A<S> {
    private String b = null;

    B() {
    }

    public S setB(String b) {
        this.b = b;
        return me();
    }
    public static B<B> factoryB() {
        return new B<B>();

    }

}

Helper

public  abstract class Chained<S extends Chained> {
    // class should be extended like:
    // ... class A<S extends A> extends Chained<S>

    public Chained() {
    }

    public final S me() {
        return (S) this;
    }
}

c'est loin d'être parfait et peut être fait pour ne pas travailler (si vous voulez vraiment)

3
répondu Alan Spencer 2013-11-07 17:11:10

si le code source est accessible, en étendant ce Qu'a écrit Alan, j'ajouterais des classes supplémentaires pour cacher les génériques tout en permettant l'héritage et la syntaxe très compacte. BaseA et BaseB font la hiérarchie tandis que A et B cachent les génériques.

BaseA
 +- A
 +- BaseB
     +- B


public class BaseA<S extends BaseA<?>> {
    private String a = null;

    protected BaseA() {
    }

    @SuppressWarnings("unchecked")
    public S setA(String a) {
        this.a = a;
        return (S) this;
    }

}

public class A extends BaseA<A> {
    public static A factoryA() {
        return new A();
    }
}

public class BaseB<S extends BaseB<?>> extends BaseA<S> {
    private String b = null;

    protected BaseB() {
    }

    @SuppressWarnings("unchecked")
    public S setB(String b) {
        this.b = b;
        return (S) this;
    }

}

public class B extends BaseB<B> {
    public static B factoryB() {
        return new B();
    }
}

public class Main {
    public static void main(String[] args) {
        B.factoryB().setA("").setB("").setB("").setA("").setA("");
    }
}
0
répondu sebtic 2013-11-08 17:10:48

une interface fluide est une préoccupation différente de l'ensemble normal des méthodes commande-requête que vous avez déjà. La séparation des préoccupations en fait une bonne idée.

puisque vous avez une hiérarchie de code existante: écrivez une façade fluide qui fait le sale travail pour vous.

Voir aussi Martin Fowler: les Langages Spécifiques au Domaine, 4.2: La nécessité d'une Analyse de la Couche.

-1
répondu flup 2013-11-07 20:42:59