2013-04-08 10 views
5

Mam wielu słuchaczy zarejestrowanych przy użyciu metod setListener, a nie addListener. Aby umożliwić wielu słuchaczom zarejestrowanie się w obiekcie, muszę użyć multiplekserów. W porządku, ale teraz muszę stworzyć multiplekser dla każdego interfejsu słuchacza, który mam. Moje pytanie brzmi: czy możliwe jest zaimplementowanie Mux.create() zgodnie z wymaganiami poniższego kodu?Czy można napisać multiplekser ogólnego przeznaczenia w Javie?

AppleListener appleListener1 = new AppleProcessorA(); 
AppleListener appleListener2 = new AppleProcessorB(); 
AppleListener appleListenerMux = Mux.create(appleListener1, appleListener2); 
Apple apple = new Apple(); 
apple.setListener(appleListenerMux); 

OrangeListener orangeListener1 = new OrangeProcessorA(); 
OrangeListener orangeListener2 = new OrangeProcessorB(); 
OrangeListener orangeListenerMux = Mux.create(orangeListener1, orangeListener2); 
Orange apple = new Orange(); 
orange.setListener(orangeListenerMux); 

class Mux { 
    public static <T> T create(T... outputs) { } 
} 

Wyobrażam sobie, że to może być możliwe za pomocą refleksji. Czy jest jakiś powód, dla którego refleksja byłaby złym pomysłem? (wydajność przychodzi na myśl)

+0

Jeśli wszystkie słuchacze implementują interfejs "AppleListener", nie widzę potrzeby a) odbicia, ani b) generycznych. Po prostu weź je wszystkie i dodaj gdzieś do 'Listy ' w twoim 'Mux' i powtórz. Czy może czegoś brakuje? –

+0

Czy możesz wyjaśnić więcej na temat swojego multipleksera? Ponieważ to samo przyszło mi do głowy, jak powiedział Alistair Israel. –

+2

Chociaż znajduje się nieco poza tym, do czego są przeznaczone, można użyć klasy [Proxy] (http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html). To tworzy obiekt, który wygląda jak dana klasa, ale wywołuje program obsługi, aby przetworzyć jego wywołania, a przewodnik może następnie iterować przez słuchaczy. – BevynQ

Odpowiedz

9

Jest to możliwe przy użyciu dynamicznego Proxy.

Najprostszym sposobem jest również przekazanie pożądanego interfejsu jako pierwszego parametru do Twojego połączenia Mux.create(). W przeciwnym razie będziesz musiał użyć odbicia, aby spróbować odgadnąć pożądany interfejs ze wszystkich dostarczonych konkretnych instancji nasłuchiwania (trudne do określenia, czy wszystkie obiekty nasłuchiwania implementują kilka wspólnych interfejsów).

Oto krótka z nim:

public class Mux { 

    /** 
    * @param targetInterface 
    *   the interface to create a proxy for 
    * @param instances 
    *   the concrete instances to delegate to 
    * @return a proxy that'll delegate to all the arguments 
    */ 
    @SuppressWarnings("unchecked") 
    public static <T> T create(Class<T> targetInterface, final T... instances) { 
     ClassLoader classLoader = targetInterface.getClassLoader(); 
     InvocationHandler handler = new InvocationHandler() { 
      @Override 
      public Object invoke(Object proxy, Method m, Object[] args) 
        throws Throwable { 
       for (T instance : instances) { 
        m.invoke(instance, args); 
       } 
       return null; 
      } 
     }; 
     return (T) Proxy.newProxyInstance(classLoader, 
       new Class<?>[] { targetInterface }, handler); 
    } 
} 

które należałoby użyć, na przykład, w następujący sposób:

Apple apple = new Apple(); 
AppleListener l1 = new AppleListenerA(); 
AppleListener l2 = new AppleListenerB(); 
apple.setListener(Mux.create(AppleListener.class, l1, l2)); 
apple.doSomething(); // will notify all listeners 

Działa to po prostu tworząc dynamiczną Proxy że odlewa się do typu docelowego T. Ten serwer proxy używa numeru InvocationHandler, który po prostu przekazuje wszystkie wywołania metod do serwera proxy w celu podania konkretnych instancji.

pamiętać, że w ogóle mogę sfinalizować wszystkie parametry i zmienne lokalne w miarę możliwości, tylko ja sfinalizowane T... instances w tym przypadku zwrócić uwagę na fakt, że jeśli instances był nie końcowy, a następnie przedstawieniu go w anonimowej klasy wewnętrznej nie będzie być dozwolone (otrzymasz "Nie można odwołać się do nieostatecznej zmiennej args wewnątrz klasy wewnętrznej zdefiniowanej w innej metodzie").

Należy również zauważyć, że powyższe założenie zakłada, że ​​rzeczywiste wywołania metod nie zwracają żadnych znaczących (lub użytecznych) wartości, dlatego też zwraca również null dla wszystkich wywołań metod. Będziesz musiał dodać trochę więcej kodu, jeśli chcesz zebrać wartości zwracane i zwrócić je również w znaczący sposób.


Alternatywnie, można kontrolować wszystkie podane instances określenie popularne interfejsy wszyscy realizacji i przenieść wszystkie te, newProxyInstance(). To sprawia, że ​​Mux.create() jest o wiele bardziej wygodny w użyciu, z powodu utraty kontroli nad jego zachowaniem.

/** 
* @param instances 
*   the arguments 
* @return a proxy that'll delegate to all the arguments 
*/ 
@SuppressWarnings("unchecked") 
public static <T> T create(final T... instances) { 

    // Inspect common interfaces 
    final Set<Class<?>> commonInterfaces = new HashSet<Class<?>>(); 
    commonInterfaces.addAll(Arrays.asList(instances[0].getClass() 
      .getInterfaces())); 

    // Or skip instances[0] 
    for (final T instance : instances) { 
     commonInterfaces.retainAll(Arrays.asList(instance.getClass() 
       .getInterfaces())); 
    } 

    // Or use ClassLoader.getSystemClassLoader(); 
    final ClassLoader classLoader = instances[0].getClass().getClassLoader(); 

    // magic 
    final InvocationHandler handler = new InvocationHandler() { 
     @Override 
     public Object invoke(final Object proxy, final Method m, final Object[] args) 
       throws Throwable { 
      for (final T instance : instances) { 
       m.invoke(instance, args); 
      } 
      return null; 
     } 
    }; 

    final Class<?>[] targetInterfaces = commonInterfaces 
      .toArray(new Class<?>[commonInterfaces.size()]); 
    return (T) Proxy.newProxyInstance(classLoader, targetInterfaces, 
      handler); 
} 
+0

Dzięki, doceniam dokładność Twojej odpowiedzi. Jedno pytanie z ciekawości: czy możemy wywnioskować interfejs z parametru ogólnego "T", zamiast przekazywać go jako kolejny argument? –

+0

@DylanP Nie można wywnioskować niczego z ogólnego parametru 'T', ponieważ nie jest on" reifikowany "- to znaczy, że ten parametr nie jest tak naprawdę dostępny w czasie wykonywania. Istnieją jednak pewne skrajne przypadki lub triki, które umożliwiają wywnioskowanie ogólnych informacji o typie w czasie wykonywania: w [podklasach] (http://alistairisrael.wordpress.com/2009/05/28/introducing-magictest/) i używanie [tokeny typów] (http://www.jquantlib.org/index.php/Using_TypeTokens_to_retrieve_generic_parameters). –

+0

@DylanP W twoim przypadku jednak łatwiej byłoby wypróbować żądany interfejs z konkretnych przekazanych instancji. Po prostu sprawdź wszystkie i znajdź wspólny interfejs, który wszystkie one implementują, a następnie przeprowadź je wszystkie do wywołania "Proxy.newProxyInstance()". Na początku nie zdawałem sobie sprawy, że 'newProxyInstance()' akceptuje wiele interfejsów. Pozwól mi zaktualizować moją odpowiedź za pomocą tej dodatkowej techniki. –

0

Composite wzór działa dobrze dla swojej sprawy.

AppleListener appleListener1 = new AppleProcessorA(); 
AppleListener appleListener2 = new AppleProcessorB(); 
CompositeListener composite = CompositeListener.for(appleListener1, appleListener2); 
Apple apple = new Apple(); 
apple.setListener(composite); 

Można mieć byłaby AppleListener i OrangeListener zaimplementować interfejs Listener, który zawiera metodę przedmiotem powiadomienia słuchaczy. CompositeListener musiałby również rozszerzyć ten słuchacz, aby wprowadzić złożony wzór.

Powiązane problemy