2014-12-16 24 views
16

Mi chiedo se sia possibile richiedere che un parametro del metodo java sia di qualsiasi tipo da un insieme finito di tipi. Ad esempio, sto utilizzando una libreria in cui due (o più) tipi hanno metodi comuni, ma il loro più basso antenato comune nella gerarchia di tipi è Object. Cosa intendo qui:Parametro del metodo multi-tipo Java?

public interface A { 
     void myMethod(); 
    } 

    public interface B { 
     void myMethod(); 
    } 
... 
    public void useMyMethod(A a) { 
     // code duplication 
    } 

    public void useMyMethod(B b) { 
     // code duplication 
    } 

Voglio evitare la duplicazione del codice. Quello che penso è qualcosa del genere:

public void useMyMethod(A|B obj){ 
     obj.myMethod(); 
    } 

C'è già un tipo simile di sintassi in java. Ad esempio:

try{ 
    //fail 
    } catch (IllegalArgumentException | IllegalStateException e){ 
    // use e safely here 
    } 

Ovviamente questo non è possibile. Come posso ottenere un codice ben progettato utilizzando questo tipo di gerarchia di tipo non modificabile?

+2

Come avrete un'interfaccia comune, come dovrebbe il vostro check compilatore che i due o più classi di avere una "interfaccia" comune? Il problema è anche che il codice che definisci come "duplicazione" non è duplicato in base al codice del programma poiché hai due gerarchie di classi diverse: il codice ha lo stesso aspetto ma funziona su strutture di dati completamente diverse. – Smutje

+0

Questo è facile in Scala, ma probabilmente non ti aiuta. Tutto quello che posso suggerire è creare un wrapper per un'interfaccia che implementa l'altro (o wrapper per entrambi che implementano la stessa interfaccia). – lmm

+4

Dato che lo hai etichettato per "modelli di progettazione", mi viene in mente il modello * adattatore *. Ma questo richiede di codificare un wrapper per ognuno di questi tipi. Senza farlo, ho paura che non ci sarà una soluzione al sicuro dal punto di vista statico. Almeno, sarei sorpreso se ci fossero. – 5gon12eder

risposta

8

Si potrebbe scrivere un interfaceMyInterface con un solo metodo myMethod. Poi, per ogni tipo che si desidera considerare come parte di un insieme finito, scrivere una classe wrapper, come questo:

class Wrapper1 implements MyInterface { 

    private final Type1 type1; 

    Wrapper1(Type1 type1) { 
     this.type1 = type1; 
    } 

    @Override 
    public void myMethod() { 
     type1.method1(); 
    } 
} 

Poi basta usare un MyInterface piuttosto che uno dei insieme finito di tipi, e verrà sempre chiamato il metodo appropriato dal tipo appropriato.

Si noti che per utilizzare effettivamente queste classi wrapper per chiamare il metodo myMethod si dovrà scrivere

myMethod(new Wrapper1(type1)); 

Questo sta per ottenere un po 'brutto come si sta andando ad avere per ricordare il nome della confezione classe per ogni tipo nel set. Per questo motivo, potresti preferire sostituire MyInterface con una classe astratta con diverse fabbriche statiche che producono i tipi di wrapper. Come questo:

abstract class MyWrapper { 

    static MyWrapper of(Type1 type1) { 
     return new Wrapper1(type1); 
    } 

    static MyWrapper of(Type2 type2) { 
     return new Wrapper2(type2); 
    } 

    abstract void myMethod(); 
} 

quindi è possibile chiamare il metodo utilizzando il codice

myMethod(MyWrapper.of(type1)); 

Il vantaggio di questo approccio è che il codice è lo stesso, non importa quale tipo si usa. Se si utilizza questo approccio, è necessario sostituire implements MyInterface nella dichiarazione Wrapper1 con extends MyWrapper.

+0

Potrebbe esserti persa la parte "Come posso ottenere un codice ben progettato utilizzando questo tipo di gerarchia di tipo non modificabile?" – Smutje

+0

@Smutje, non è necessario modificare la gerarchia di tipi (originale). Ecco perché questo è chiamato 'Wrapper'. –

+1

Probabilmente dovresti aggiungere un secondo codice snippato come questo wrapper viene effettivamente utilizzato con i due oggetti methodCall (new Wrapper (t1);) - Vorrei anche pensare a una singola classe wrapper con due costruttori, uno per ogni interfaccia – Falco

13

Provare a utilizzare il modello di disegno Adapter.

Oppure, se è possibile, aggiungere un po 'interfaccia di base:

public interface Base { 
    void myMethod(); 
} 

public interface A extends Base {} 
public interface B extends Base {} 
... 
public void useMyMethod(Base b) { 
    b.myMethod() 
} 

Inoltre, è possibile utilizzare qualcosa di simile a this

+3

Potrebbe esserti persa la parte "Come posso ottenere un codice ben progettato utilizzando un tipo di gerarchia di tipo non modificabile?" – Smutje

+3

Non funziona come mostrato perché Base non ha il metodo richiesto. – BarrySW19

+0

@ BarrySW19, grazie! Ho aggiornato la risposta. – pbespechnyi

5

Bene, il modo corretto per modellare il proprio requisito sarebbe quello di avere myMethod() dichiarato in un'interfaccia supertipo C che sia A che B si estendono; il tuo metodo accetta quindi il tipo C come parametro. Il fatto che tu abbia problemi nel fare ciò nella situazione che descrivi indica che non stai modellando la gerarchia delle classi in un modo che riflette in realtà come si comportano.

Ovviamente, se non è possibile modificare la struttura dell'interfaccia, è possibile farlo sempre con i riflessi.

public static void useMyMethod(Object classAorB) throws Exception { 
    classAorB.getClass().getMethod("myMethod").invoke(classAorB); 
} 
+3

Per rendere statisticamente sicuro il tipo almeno al sito del chiamante, prenderei in considerazione l'idea di rendere il metodo che accetta 'Object'' private' e fornire metodi 'public' in overload per ogni tipo che deve essere supportato. Questi semplicemente delegano al metodo che mostri. Ciò richiede una piccola duplicazione del codice ma solo one-liner tecniche, nessuna logica di business. – 5gon12eder

+0

@ 5gon12eder è esattamente l'approccio che adotterei: metodi pubblici che garantiscono la sicurezza del tipo e chiamano il metodo privato sottostante! – Falco

0

Il modo corretto è utilizzare Java Generics.

Vedi http://docs.oracle.com/javase/tutorial/java/generics/bounded.html

+0

Come si vincolerebbe il parametro di tipo generico a un insieme finito di tipi? – dcastro

+0

Beh, ovviamente hai un'interfaccia di base o un tipo di oggetto, ad esempio digita "AorB", che ha una definizione o un'implementazione del metodo. Quindi definisci un'interfaccia pubblica Functor {}. È possibile associare più T a una serie di tipi: StarShine

+0

L'OP non può modificare le interfacce esistenti per estendere un'interfaccia comune - "Come posso ottenere un codice ben progettato utilizzando questo tipo di ** gerarchia di tipo non modificabile? ** " – dcastro

14

cosa circa passando la funzione come parametro alla funzione useMyMethod?

Se si utilizza Java < 8:

public interface A { 
    void myMethod(); 
} 

public interface B { 
    void myMethod(); 
} 

public void useMyMethod(Callable<Void> myMethod) { 
    try { 
     myMethod.call(); 
    } catch(Exception e) { 
     // handle exception of callable interface 
    } 
} 

//Use 

public void test() { 
    interfaceA a = new ClassImplementingA(); 
    useMyMethod(new Callable<Void>() { 
     public call() { 
      a.myMethod(); 
      return null; 
     } 
    }); 

    interfaceB b = new ClassImplementingB(); 
    useMyMethod(new Callable<Void>() { 
     public call() { 
      b.myMethod(); 
      return null; 
     } 
    }); 
} 

Per Java> = 8, si potrebbe usare Lambda Expressions:

public interface IMyMethod { 
    void myMethod(); 
} 

public void useMyMethod(IMyMethod theMethod) { 
    theMethod.myMethod(); 
} 

//Use 

public void test() { 
    interfaceA a = new ClassImplementingA(); 
    useMyMethod(() -> a.myMethod()); 

    interfaceB b = new ClassImplementingB(); 
    useMyMethod(() -> b.myMethod()); 
} 
+2

Non male, ma ci costringe ad esporre al chiamante i metodi che invocheremo sull'argomento. Cambiare l'implementazione (semplicemente chiamando un altro metodo) potrebbe costringerci ad andare a modificare ogni utilizzo della funzione. E se vogliamo chiamare più di un metodo, diventa rapidamente noioso. – 5gon12eder

+2

Ehi @ 5gon12ender, i tuoi punti sono validi. L'utilizzo di un adattatore ha il vantaggio di incapsulare la chiamata al metodo, quindi se è necessario modificare il metodo, è necessario modificare solo la classe dell'adattatore. D'altra parte è necessario creare un adattatore per interfaccia, che può diventare ancora più noioso di una soluzione funzionale. Dipende davvero dalla portata del codice (è il codice interno di una piccola classe o una funzione ampiamente utilizzata?) E quanto è probabile che cambi l'implementazione, direi. – Yogster

+0

Lo direi anche io ... – 5gon12eder

2

questo sembra a me molto simile al modello modello:

public interface A { 

    void myMethod(); 
} 

public interface B { 

    void myMethod(); 
} 

public class C { 

    private abstract class AorBCaller { 

     abstract void myMethod(); 

    } 

    public void useMyMethod(A a) { 
     commonAndUseMyMethod(new AorBCaller() { 

      @Override 
      void myMethod() { 
       a.myMethod(); 
      } 
     }); 
    } 

    public void useMyMethod(B b) { 
     commonAndUseMyMethod(new AorBCaller() { 

      @Override 
      void myMethod() { 
       b.myMethod(); 
      } 
     }); 
    } 

    private void commonAndUseMyMethod(AorBCaller aOrB) { 
     // ... Loads of stuff. 
     aOrB.myMethod(); 
     // ... Loads more stuff 
    } 
} 

In Java 8 è molto più sintetico:

public class C { 

    // Expose an "A" form of the method. 
    public void useMyMethod(A a) { 
     commonAndUseMyMethod(() -> a.myMethod()); 
    } 

    // And a "B" form. 
    public void useMyMethod(B b) { 
     commonAndUseMyMethod(() -> b.myMethod()); 
    } 

    private void commonAndUseMyMethod(Runnable aOrB) { 
     // ... Loads of stuff -- no longer duplicated. 
     aOrB.run(); 
     // ... Loads more stuff 
    } 
} 
3

Questo potrebbe non costituire una best practice, ma potresti creare una nuova classe (chiamala C), che contiene le parti da A e B che sono duplicate, e creare un nuovo metodo che prende C, avere i tuoi metodi che prendono A e B creano un'istanza C e chiamano il nuovo metodo?

Per avere

class C { 
    // Stuff from both A and B 
} 

public void useMyMethod(A a) { 
    // Make a C 
    useMyMethod(c); 
} 

public void useMyMethod(B b) { 
    // Make a C 
    useMyMethod(c); 
} 

public void useMyMethod(C c) { 
    // previously duplicated code 
} 

che sarebbe anche lasciare che si tiene alcun codice non duplicato nei metodi di A e B (se c'è).

2

Un proxy dinamico può essere utilizzato per creare un bridge tra un'interfaccia comune definita dall'utente e gli oggetti che implementano le altre interfacce che sono conformi alla nuova interfaccia. Quindi, puoi avere il tuo useMyMethod s convertire il parametro nella nuova interfaccia (come proxy dinamico) e avere il tuo codice comune scritto solo in termini della nuova interfaccia.

Questa sarebbe la nuova interfaccia:

interface Common { 
    void myMethod(); 
} 

Poi, con questo gestore invocazione:

class ForwardInvocationHandler implements InvocationHandler { 
    private final Object wrapped; 
    public ForwardInvocationHandler(Object wrapped) { 
    this.wrapped = wrapped; 
    } 
    @Override 
    public Object invoke(Object proxy, Method method, Object[] args) 
     throws Throwable { 
    Method match = wrapped.getClass().getMethod(method.getName(), method.getParameterTypes()); 
    return match.invoke(wrapped, args); 
    } 
} 

Si possono avere i vostri metodi come questo:

public void useMyMethod(A a) { 
    useMyMethod(toCommon(a)); 
} 

public void useMyMethod(B b) { 
    useMyMethod(toCommon(b)); 
} 

public void useMyMethod(Common common) { 
    // ... 
} 

private Common toCommon(Object o) { 
    return (Common)Proxy.newProxyInstance(
    Common.class.getClassLoader(), 
    new Class[] { Common.class }, 
    new ForwardInvocationHandler(o)); 
} 

Si noti che a Semplifica le cose che potresti persino scegliere una delle tue interfacce esistenti (A o B) da utilizzare come interfaccia comune.

(Guarda un altro esempio here, e anche in altri idee intorno a questo argomento)