2015-05-04 19 views
13

Descrizione del problema: Desidero poter passare un elenco di metodi ad altre classi in cui i metodi sono stati definiti in una sola classe. Se i metodi, alcuni dei quali hanno parametri di input e tipi di ritorno non vuoto, sono definiti in una classe, voglio essere in grado di passare una lista di alcuni di essi, con possibili duplicati, come parametro per il costruttore di un'altra classe.Array of methods: Adapter Pattern?

Codice Descrizione: Il codice che segue è un esempio di greggio e può essere ignorato se si toglie l'obiettivo principale. Un altro esempio, oltre a quello sotto, sarebbe un caso in cui i metodi sono int Aggiungi (int n1, int n2), int Sottrai (int n1, int n2), Moltiplica, ecc. E l'interfaccia ha un metodo chiamato int MathOperation (int n1, int n2).

tentativo di risolvere il problema: Il modello adattatore sembra avere la funzionalità che sto cercando, ma ho visto solo esempi in cui i metodi nell'interfaccia non hanno parametri di ingresso o di uscita. Un'implementazione di esempio che ho scritto solo per questa domanda è pubblicata di seguito.

Analogia problema: Si dispone di un servizio Web generatore di immagini casuali. Ci sono 30 mutazioni che possono essere applicate a un'immagine. Il client si connette e fa clic su un pulsante "Genera" e un elenco casuale di alcune di queste funzioni viene passato a un'altra classe all'interno del servizio Web che procede quindi a eseguire tali funzioni con i propri dati raccogliendo e eventualmente riutilizzando il rendimento valori per generare un'immagine cat modificata. Non può semplicemente chiamare i metodi nell'altra classe in modo esplicito perché questo processo deve essere eseguito in modo casuale in fase di esecuzione. Questo è il motivo per cui mi propongo di generare un elenco casuale di metodi che vengono eseguiti in ordine quando si fa clic sul pulsante "genera".

Spero di essere stato chiaro.

public class SomeClass { 
    ... 
    public double UseWrench(double torque, boolean clockwise) { ... } 
    public double UsePliers(double torque, boolean clockwise) { ... } 
    public double UseScrewDriver(double torque, boolean clockwise) { ... } 
    public boolean UseWireCutters(double torque) { ... } 

    interface IToolActions { 
     double TurnFastener(double torque, boolean clockwise); 
     boolean CutWire(double torque); 
    } 

    private IToolActions[] toolActions = new IToolActions[] { 
     new IToolActions() { public double TurnFastener(double torque, boolean clockwise) { double UseWrench(double torque, boolean clockwise); } }, 
     new IToolActions() { public double TurnFastener(double torque, boolean clockwise) { double UsePliers(double torque, boolean clockwise); } }, 
     new IToolActions() { public double TurnFastener(double torque, boolean clockwise) { double UseScrewDriver(double torque, boolean clockwise); } }, 
     new IToolActions() { public boolean CutWire(double torque) { boolean UseWireCutters(double torque); } }, 
    }; 
} 

public class Worker<T> { 

    public List<? extends IToolActions> toolActions; 

    public Worker(List<? extends IToolActions> initialToolSet){ 
     toolActions = initialToolActions; 
    } 
} 
+0

Penso di vedere cosa state cercando, ma avrò bisogno di una descrizione del problema migliore o di un esempio migliore. Il hold-up sembra essere il parametro del metodo e il valore restituito. Dovrai definire un modo per gestirli, e le tue esigenze particolari imporranno il modo in cui deve funzionare. Non esiste una "X" generica, risolve sempre questo tipo di problema. Forse spiegare quale problema stai selezionando i parametri stessi, che potrebbe essere un inizio. – markspace

+0

Che ne dici di usare 'Lista '? –

+0

Se lo desideri veloce e dinamico, puoi usare Reflection e solo elencare tutti i metodi nella classe, ottenere i loro parametri e riempirli in un ciclo. Ma questo non sarà così pulito come la bella interfaccia risponde sotto – Falco

risposta

4

@John ecco come ho affrontato una soluzione al tuo problema.

Ho usato il caso di MathOperations per renderlo più semplice. Penso prima che sarei stato meglio avere l'interfaccia esterna di SomeClass come:

public interface MathOperable { 

    public int mathOperation(int n1, int n2); 

} 

Ho creato due esempi di classi di attuazione questa interfaccia e un'implementazione anonima all'interno SomeClass (ho fatto un componente aggiuntivo, si moltiplicano e un anonimo " sottrarre ")

public class Add implements MathOperable { 

    public int mathOperation(int n1, int n2) { 

     return n1 + n2; 
    } 

    public String toString() { 
     return "<addition>"; 
    } 

} 

la prioritario di toString() è semplicemente allo scopo di dare maggiore leggibilità agli esempi che io ti indicherò alla fine del mio post.

public class Multiply implements MathOperable { 

    public int mathOperation(int n1, int n2) { 
     // TODO Auto-generated method stub 
     return n1 * n2; 
    } 

    public String toString() { 
     return "<multiplication>"; 
    } 

} 

Qui è la mia classe SomeClass, è contans un getRandomListOfOperations, dove ho simulare ciò che accade quando il clic sul pulsante è fatto

public class SomeClass { 

    private static MathOperable addition = new Add(); 
    private static MathOperable multiplication = new Multiply(); 

    // Anonymous substraction 
    private static MathOperable substraction = new MathOperable() { 

     public int mathOperation(int n1, int n2) { 
      // TODO Auto-generated method stub 
      return n1-n2; 
     } 

     public String toString() { 
      return "<substraction>"; 
     } 

    }; 


    public List<MathOperable> getRandomListOfOperations() { 

     // We put the methods in an array so that we can pick them up later  randomly 
     MathOperable[] methods = new MathOperable[] {addition,  multiplication, substraction}; 
     Random r = new Random(); 

     // Since duplication is possible whe randomly generate the number of  methods to send 
     // among three so if numberOfMethods > 3 we are sure there will be  duplicates 
     int numberOfMethods = r.nextInt(10); 
     List<MathOperable> methodsList = new ArrayList<MathOperable>(); 

     // We pick randomly the methods with duplicates 
     for (int i = 0; i < numberOfMethods; i++) { 
      methodsList.add(methods[r.nextInt(3)]); 

     } 

     return methodsList;  
    } 

    public void contactSomeOtherClass() { 
     new SomeOtherClass(getRandomListOfOperations()); 
    } 
} 

Ora qui è la mia SomeOtherClass (che può corrispondere al lavoratore classe)

public class SomeOtherClass<T extends MathOperable> { 

    Random r = new Random(); 

    List<T> operations; 

    public SomeOtherClass(List<T> operations) { 
     this.operations = operations; 

     runIt(); 
    } 

    public void runIt() { 

     if (null == operations) { 
      return; 
     } 

     // Let's imagine for example that the new result is taken as  operand1 for the next operation 
     int result = 0; 

     // Here are examples of the web service own datas 
     int n10 = r.nextInt(100); 
     int n20 = r.nextInt(100); 

     for (int i = 0; i < operations.size(); i++) { 

      if (i == 0) { 
       result = operations.get(i).mathOperation(n10, n20); 
       System.out.println("Result for operation N " + i + " = " +  result); 
      } else { 

       // Now let's imagine another data from the web service  operated with the previous result 
       int n2 = r.nextInt(100); 
       result = operations.get(i).mathOperation(result, n2); 
       System.out.println("Current result for operation N " + i + "  which is " + operations.get(i) +" = " + result); 

      } 
     } 
    } 

}

ho un semplice te st classe che contiene un principale per collegare le due classi

public class SomeTestClass { 

    public static void main(String[] args) { 
     SomeClass classe = new SomeClass(); 
     classe.contactSomeOtherClass(); 
    } 

} 

Ora alcuni esempi di esecuzioni:

example1

E un altro esempio!

example 2

Spero che questo potrebbe essere utile!

+0

Solo una critica costruttiva: vedi per confronto la mia risposta, re: schema di comando; questo è in realtà simile a ciò che si ha con "interfaccia pubblica MathOperable". In particolare, si noti che un 'mathOperation' di due argomenti che prende' int' potrebbe essere un problema lungo la strada. Come cambiamento generale del progetto, è possibile passare arg tramite un ctor (come i comandi) e rendere 'mathOperation' no-arg. Il risultato "' int' "sarà ancora problematico; ma, potrebbe restituire un altro MathOp o qualche tipo di oggetto "risultato" (ma la composizione diventerà probabilmente disordinata ... OO la matematica è un problema difficile.) – michael

+0

@michael_n, grazie, questa è una grande osservazione! All'inizio ho iniziato a scavare nel pattern, ma poi volevo avere un approccio di lavoro quindi abbinarlo/adattarlo a uno dei pattern. – alainlompo

8

Mentre @alainlompo ha l'idea generale, Java 8 lo semplifica notevolmente utilizzando qualcosa come BiConsumer (per i doppi) o anche solo un Consumer per l'oggetto classe. In realtà, si può andare veramente pazzo, e hanno un metodo accept varargs lambda:

public class SomeClass 

    public double useWrench(double torque, boolean clockwise) { ... } 
    public double usePliers(double torque, boolean clockwise) { ... } 
    public double useScrewDriver(double torque, boolean clockwise) { ... } 
    public boolean useWireCutters(double torque) { ... } 

} 

public class Worker { 

    @SafeVarargs 
    public Worker(SomeClass example, Consumer<? extends SomeClass>... operations) { 
     for (Consumer bc : operations) { 
      bc.accept(example); 
     } 
    } 
} 

Quindi, questo è facilmente semplificata:

SomeClass c = new SomeClass(); 
new Worker(c, SomeClass::useWrench, SomeClass:usePliers, SomeClass::useScrewDriver, SomeClass::useWireCutters); 

Mentre sembra un po 'imbarazzante applicarlo come quello (a causa di esso che è un modello di adattatore), si può facilmente vedere come questo potrebbe applicarsi a un organismo di portata:

public class SomeClass 

    public double useWrench(double torque, boolean clockwise) { ... } 
    public double usePliers(double torque, boolean clockwise) { ... } 
    public double useScrewDriver(double torque, boolean clockwise) { ... } 
    public boolean useWireCutters(double torque) { ... } 

    @SafeVarargs 
    public void operate(Consumer<? extends SomeClass>... operations) { 
     for (Consumer<? extends SomeClass> bc : operations) { 
      bc.accept(example); 
     } 
    } 

} 

//Elsewheres 
SomeClass c = new SomeClass(); 
c.operate(SomeClass::useWrench, SomeClass:usePliers, SomeClass::useScrewDriver, SomeClass::useWireCutters); 

Naturalmente, non è necessario varargs, funzionerà ju st anche semplicemente passando un Collection

Ma aspetta c'è di più !!!

Se si voleva un risultato, si può anche utilizzare un metodo di auto-ritorno tramite un Function, ad esempio:

public class SomeClass { 

    public double chanceOfSuccess(Function<? super SomeClass, ? extends Double> modifier) { 
     double back = /* some pre-determined result */; 
     return modifier.apply(back); //apply our external modifier 
    } 

} 

//With our old 'c' 
double odds = c.chanceOfSuccess(d -> d * 2); //twice as likely! 

C'è molto di più flessibilità prevista dalla funzione API in Java 8, rendendo problemi complessi come questo incredibilmente semplificato da scrivere.

+1

Questo presumibilmente sarebbe un ['ToDoubleFunction'] (https://docs.oracle.com/javase/8/docs/api/java/util/function/ToDoubleFunction.html). –

+0

Questo è un buon esempio del perché più persone probabilmente inizieranno ad usare lingue come Scala (Clojure, et al). Java come linguaggio non può che evolversi così tanto (... e lo uso da jdk 1.0, si è evoluto molto, ha). – michael

2

Ok, ho intenzione di essere "quel ragazzo" ... colui che capisce la domanda, ma chiede comunque di riaffermare il problema perché penso che tu sia sulla strada sbagliata. Quindi, sopportami: se ti piace quello che vedi, grande; se no, capisco.

Fondamentalmente, si ha un diverso intento/motivazione/scopo rispetto a ciò per cui "adattatore" è adatto. Il modello di comando è più adatto.

Ma in primo luogo, più in generale, uno degli obiettivi della progettazione di "elementi di software riutilizzabile" (dal titolo del libro GOF originale design pattern) è che non si vuole modificare il codice quando si aggiunge Funzionalità; piuttosto, si desidera aggiungere il codice senza toccare la funzionalità esistente. Così, quando si dispone di:

public class Toolbox { 
    public void hammer() { ... } 
} 

e si desidera aggiungere un cacciavite per la vostra cassetta degli attrezzi, questo è male:

public class Toolbox { 
    public void hammer() { ... } 
    public void screwdriver() { ... } 
} 

Piuttosto, idealmente, tutto il codice esistente rimarrebbe invariato e si era appena aggiungere una nuova unità di compilazione Cacciavite (ad esempio, aggiungere un nuovo file) e un test di unità, quindi verificare il codice esistente per la regressione (che dovrebbe essere improbabile, poiché nessuno del codice esistente è stato modificato). Per esempio:

public class Toolbox { 
    public void useTool(Tool t) { t.execute(); ...etc... } 
} 

public interface Tool { // this is the Command interface 
    public void execute() // no args (see ctors) 
} 

pubic Hammer implements Tool { 
    public Hammer(Nail nail, Thing t) // args! 
    public void execute() { nail.into(t); ... } 
} 

pubic Screwdriver implements Tool { 
    public Screwdriver(Screw s, Thing t) 
    public void execute() { screw.into(t); ... } 
} 

Speriamo che dovrebbe diventare chiaro come estendere questo al vostro esempio. Il lavoratore diventa un elenco di strumenti di tipo diretto (o, per chiarezza, invece di "Strumento", basta chiamarlo "Comando").

public class Worker { 
    public List<Command> actionList; 
    .... 
    public void work() { 
     for(...) { 
     action.execute(); 
     } 
    } 
} 

Questo modello consente inoltre un facile "annullare" la funzionalità e la "riprovare", così come Memoizzazione (risultati cache in modo che non devono essere ri-run).