herança Java Listener
-
21-08-2019 - |
Pergunta
Eu tenho uma classe java que dispara eventos java personalizado. A estrutura do código é o seguinte:
public class AEvent extends EventObject {
...
}
public interface AListener extends EventListener {
public void event1(AEvent event);
}
public class A {
public synchronized void addAListener(AListener l) {
..
}
public synchronized void removeAListener(AListener l) {
..
}
protected void fireAListenerEvent1(AEvent event) {
..
}
}
Tudo funciona corretamente, mas eu gostaria de criar uma nova subclasse de A (chamá-lo B), que pode disparar um novo evento. Estou pensando na seguinte modificação:
public class BEvent extends AEvent {
...
}
public interface BListener extends AListener {
public void event2(BEvent event);
}
public class B extends A {
public synchronized void addBListener(BListener l) {
..
}
public synchronized void removeBListener(BListener l) {
..
}
protected void fireBListenerEvent2(AEvent event) {
..
}
}
Esta é a abordagem correta? Eu estava pesquisando na web para exemplos, mas não conseguiu encontrar nenhum.
Existem algumas coisas eu não gosto nesta solução:
-
BListener
tem dois métodos uma utilidadesAEvent
os outros usosBEvent
como parâmetro. - classe
B
tanto tem métodosaddAListener
eaddBListener
. Eu deveria me esconder addAListener com chave privada? [UPDATE: não é possível esconder com chave privada] - problema semelhante com métodos
fireAListenerEvent1
efireBListenerEvent1
.
Eu estou usando Java versão 1.5.
Solução
Eu não vejo uma razão pela qual BListener
deve estender AListener
.
Você realmente quer à força todos os interessados ??em eventos B
para também implementar event1()
?
Além disso, você não pode adicionar addAListener()
, já que uma classe derivada não pode reduzir a visibilidade de um método que está presente na classe pai. Além disso, você não precisa, ou você violaria a substituição de Liskov (cada B must ser capaz de fazer tudo o que um a pode fazer).
E como uma última observação, eu faria os métodos fire*()
protegida. Geralmente não há razão para mantê-los pública e reduzir o número de membros públicos mantém sua interface pública limpo.
Outras dicas
Não use inheritence, não é o que você quer e vai levar a uma frágil e difícil de design de mudança. Composição é uma mais flexível e uma melhor abordagem para o design. Sempre tente criar interfaces como granular possível, porque eles não deve ser alterado evento. Eles são o seu contrato com o resto do sistema. Se novas necessidades de funcionalidade a ser adicionada a primeira opção é adicionar mais informações para o evento. Se isso não for o caso, então você deve criar uma nova interface para entregar esse evento. Isto evita ter que alterar o código existente que não é afetado.
Aqui está o meu padrão favorito para isso, eu acredito que é comumente referido como um Observer.
Faça uma nova interface que define um métodos para esse tipo de evento (fooEvent () addFooEventListener () removeFooEventListener ()). Implementar essa interface na classe concreta que gera esses eventos. (I normalmente chama esse algo como SourcesFooEvent, FiresFooEvent, FooEventSource, etc)
Se você quiser reduzir a duplicação de código que você pode construir uma classe auxiliar que lida com registro dos ouvintes, os armazena em uma coleção, e fornece um método de fogo para publicar os eventos.
Os genéricos podem ajudar aqui. Primeiro, uma interface ouvinte genérico:
public interface Listener<T> {
void event(T event);
}
Em seguida, uma interface EventSource de harmonização:
public interface EventSource<T> {
void addListener(Listener<T> listener);
}
Finalmente uma classe base abstrata para construir rapidamente uma classe auxiliar para registro alça de ouvintes e expedição evento:
public abstract class EventDispatcher<T> {
private List<Listener<T>> listeners = new CopyOnWriteArrayList<T>();
void addListener(Listener<T> listener) {
listeners.add(listener);
}
void removeListener(Listener<T> listener) {
listeners.remove(listener);
}
void fireEvent(T event) {
for (Listener<T> listener : listeners) {
listener.event(event);
}
}
}
Você faria uso do EventDispatcher abstrato através de encapsulamento, permitindo que qualquer outra classe para implementar facilmente EventSource enquanto não a requerem para estender qualquer classe particular.
public class Message {
}
public class InBox implements EventSource<Message> {
private final EventDispatcher<Message> dispatcher = new EventDispatcher<Message>();
public void addListener(Listener<Message> listener) {
dispatcher.addListener(listener);
}
public void removeListener(Listener<Message> listener) {
dispatcher.removeListener(listener);
}
public pollForMail() {
// check for new messages here...
// pretend we get a new message...
dispatcher.fireEvent(newMessage);
}
}
Esperamos que este ilustra o bom equilíbrio entre a segurança tipo (importante), flexibilidade e reutilização de código.
Eu entendo de seu comentário a saua que disparando B dispara automaticamente A.
Por que não usar um único tipo de ouvinte e, em seguida, misturar um pouco de herança, delegação e genéricos?
class AEvent {}
class BEvent extends Event{}
interface EventListner<E extends AEvent>
{
onEvent(E e);
}
class ListenerManager<E extends AEvent>{
addListner(EventListener<? extends E>){}
removeListner(EventListener<? extends E>){}
fire(E e);
}
class A extends ListenerManager<AEvent>
{
}
class B extends ListenerManager<BEvent>
{
A delegatorA;
@Override addListener(EventListener<? extends BEvent> l)
{
super.addListner(l);
delegatorA.addListener(l);
}
@Override removeListener(EventListener<? extends BEvent> l)
{
super.removeListner(l);
delegatorA.removeListener(l);
}
@Override fire(BEvent b)
{
super.fire(b);
a.fire(b)
}
}
Explicação: o código para os ouvintes gestão é compartilhada, na classe base Listener Manager. B só pode receber BListeners por causa de genéricos em tempo de compilação verificação. Firing B dispara automaticamente A.
Parece-me que você pode manter as coisas muito simples.
Minha understading
-
Você tem uma classe básica A , que realiza alguma basicOperation
-
Você pode ter uma subclasse mais específica B que, além podem realizar mais specificOperations
Se for esse o caso, será necessário que ambos os eventos ser manipuladas ( básico para A e básico + específica para B)
Bem, você não precisa sobrecarregar os métodos para fazer isso, a única coisa que você precisa fazer é manipuladores add específicos (ou ouvintes) para eventos específicos.
Pode ser o caso, o evento é "básico", tudo bem.
Mas quando o evento é específico, você precisa reagir em conformidade. Então, o que eu gostaria de fazer é adicionar uma verificação em o ouvinte específico para discriminar o específica evento como este:
if( whichEvent instanceof SpecificEvent ) {
SpecificEvent s = ( SpecificEvent ) whichEvent;
// Do something specific here...
}
E é isso.
A sua descrição do problema é muito abstrata, por isso há soluções concretas podem ser sugeridas. No entanto, se é difícil explicar o que você quer alcançar, provavelmente você precisa re-analisar qual é o problema em primeiro lugar.
Se o meu entendimento acima está correta (que você precisa lidar com básico + específica algumas vezes) o seguinte código longa abaixo ajuda Maio.
Com os melhores cumprimentos
import java.util.*;
class A {
// All the listener will be kept here. No matter if basic or specific.
private List<Listener> listeners = new ArrayList<Listener>();
public void add( Listener listener ) {
listeners.add( listener );
}
public void remove( Listener listener ) {
listeners.remove( listener );
}
// In normal work, this class just perform a basic operation.
public void normalWork(){
performBasicOperation();
}
// Firing is just firing. The creation work and the
// operation should go elsewhere.
public void fireEvent( Event e ) {
for( Listener l : listeners ) {
l.eventHappened( e );
}
}
// A basic operation creates a basic event
public void performBasicOperation() {
Event e = new BasicEvent();
fireEvent( e );
}
}
// Specialized version of A.
// It may perform some basic operation, but also under some special circumstances
// it may perform an specific operation too
class B extends A {
// This is a new functionality added by this class.
// Hence an specifi event is fired.
public void performSpecificOperation() {
Event e = new SpecificEvent();
// No need to fire in different way
// an event is an event and that's it.
fireEvent( e );
}
// If planets are aligned, I will perform
// an specific operation.
public void normalWork(){
if( planetsAreAligned() ) {
performSpecificOperation();
} else {
performBasicOperation();
}
}
private boolean planetsAreAligned() {
//return new Random().nextInt() % 3 == 0;
return true;
}
}
// What's an event? Something from where you can get event info?
interface Event{
public Object getEventInfo();
}
// This is the basic event.
class BasicEvent implements Event{
public Object getEventInfo() {
// Too basic I guess.
return "\"Doh\"";
}
}
// This is an specific event. In this case, an SpecificEvent IS-A BasicEvent.
// So , the event info is the same as its parent. "Doh".
// But, since this is an SpecificEvent, it also has some "Specific" features.
class SpecificEvent extends BasicEvent {
// This method is something more specific.
// There is no need to overload or create
// different interfaces. Just add the new specific stuff
public Object otherMethod() {
return "\"All I can say is , this was an specific event\"";
}
}
// Hey something just happened.
interface Listener {
public void eventHappened( Event whichEvent );
}
// The basic listner gets information
// from the basic event.
class BasicEventListener implements Listener {
public void eventHappened( Event e ) {
System.out.println(this.getClass().getSimpleName() + ": getting basic functionality: " + e.getEventInfo());
}
}
// But the specific listner may handle both.
// basic and specific events.
class SpecificListener extends BasicEventListener {
public void eventHappened( Event whichEvent ) {
// Let the base to his work
super.eventHappened( whichEvent );
// ONLY if the event if of interest to THIS object
// it will perform something extra ( that's why it is specific )
if( whichEvent instanceof SpecificEvent ) {
SpecificEvent s = ( SpecificEvent ) whichEvent;
System.out.println(this.getClass().getSimpleName() + ": aaand getting specific functionality too: " + s.otherMethod() );
// do something specific with s
}
}
}
// See it run.
// Swap from new A() to new B() and see what happens.
class Client {
public static void main( String [] args ) {
A a = new B();
//A a = new A();
a.add( new BasicEventListener() );
a.add( new SpecificListener() );
a.normalWork();
}
}
Exemplo de saída:
BasicEventListener: getting basic functionality: "Doh"
SpecificListener: getting basic functionality: "Doh"
SpecificListener: aaand getting specific functionality too: "All I can say is , this was an specific event"
Tomar-lo ainda mais, você ainda pode se livrar das interfaces para mantê-lo mais simples
Se
public class BEvent extends AEvent {
...
}
public interface BListener extends AListener {
public void event2(BEvent event);
}
que você não pode fazer algo como:
public class B extends A {
@Override
public synchronized void addAListener(AListener l) {
if (l instanceof BListener) {
...
} else {
super.addAListener(l);
}
}
...
}
Como eu disse no comentário, eu não tenho certeza do que você realmente deseja alcançar? Quem é chamado a partir de onde, eo que ele precisa fazer quando se obter chamado?
Com base no que pouca informação que temos sobre a relação entre A
& B
, eu acho que é confuso para fazer BListener
uma subinterface de AListener
. Como o nome sugere, um BListener
é supor para ouvir BEvent
s, que são já uma subclasse de AEvent
s. Para maior clareza, os ouvintes devem ter efeitos mais exigentes; eles não devem sobrepor-se desnecessariamente. Além disso, não há necessidade de tais ouvintes sobrepostas desde que você já definir métodos separados em B
classe para lidar com diferentes tipos de ouvintes.
Para ilustrar meu ponto, considere este exemplo, denominado após seu código:
public class MovableMouseEvent extends EventObject
public class ClickableMouseEvent extends MovableMouseEvent
public interface MovableMouseListener extends EventListener
// mouseMoved(MovableMouseEvent)
public interface ClickableMouseListener extends MovableMouseListener
// mouseClicked(ClickableMouseEvent)
public class MovableMouseWidget
// {addMovableMouseListener,removeMovableMouseListener}(MovableMouseListener)
// fireMovableMouseEvent(MovableMouseEvent)
public class ClickableMouseWidget extends MovableMouseWidget
// {addClickableMouseListener,removeClickableMouseListener}(ClickableMouseListener)
// fireClickableMouseEvent(ClickableMouseEvent)
Este projeto funciona, mas é confuso porque alças ClickableMouseListener
dois tipos de eventos, e lida com ClickableMouseWidget
dois tipos de ouvintes, como você justamente salientou. Agora, considere o seguinte alternativa que usa a composição em vez de herança:
public class MouseMoveEvent extends EventObject // note the name change
public class MouseClickEvent extends EventObject // don't extend MouseMoveEvent
public interface MouseMoveListener extends EventListener
// mouseMoved(MouseMoveEvent)
public interface MouseClickListener extends EventListener // don't extend MouseMoveListener
// mouseClicked(MouseClickEvent)
public interface MouseMoveObserver
// {addMouseMoveListener,removeMouseMoveListener}(MouseMoveListener)
// fireMouseMoveEvent(MouseMoveEvent)
public interface MouseClickObserver
// {addMouseClickListener,removeMouseClickListener}(MouseClickListener)
// fireMouseClickEvent(MouseClickEvent)
public class MovableMouseWidget implements MouseMoveObserver
public class ClickableMouseWidget implements MouseMoveObserver, MouseClickObserver