У меня есть класс Java, который запускает пользовательские события Java. Структура кода следующая:
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) {
..
}
}
Все работает правильно, но я хотел бы создать новый подкласс A (назовите его B), который может запускать новое событие. Думаю о следующей модификации:
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) {
..
}
}
Это правильный подход? Я искал в Интернете примеры, но не нашел.
В этом решении есть несколько вещей, которые мне не нравятся:
BListener имеет два метода: один использует AEvent, а другой использует BEvent в качестве параметра.B имеет методы addAListener и addBListener. Должен ли я скрыть addAListener с частным ключевым словом? [ОБНОВЛЕНИЕ: невозможно скрыть частным ключевым словом]fireAListenerEvent1 и fireBListenerEvent1.Я использую Java версии 1.5.
B добавляет новую функциональность к классу A. Это означает, что событие, инициированное A (event1), является допустимым событием для класса B. Из-за новой функциональности новое событие (event2) также доступно для B. Я хотел бы обработать два события (event1 + event2) вместе, поскольку они связаны.
Боковое примечание: исключение ConcurrentModificationException может возникать в методах fireXXX, если вы их тоже не синхронизируете (дорого!). Для типичных случаев использования быстрее и менее подвержено ошибкам использовать java.util.concurrent.CopyOnWriteArrayList для вашего списка слушателей, так что синхронизация не требуется.




Я не вижу причин, по которым BListener должен расширять AListener.
Вы действительно хотите заставить всех, кто интересуется событиями B, также реализовать event1()?
Также вы не можете добавить addAListener(), поскольку производный класс не может уменьшить видимость метода, присутствующего в родительском классе. Кроме того, в этом нет необходимости, иначе вы нарушите Принцип подстановки Лискова (каждый B должен уметь делать все, что может сделать A).
И в качестве последнего замечания, я бы сделал методы fire*() защищенными. Обычно нет никаких причин делать их общедоступными, а сокращение числа публичных участников сохраняет ваш публичный интерфейс в чистоте.
Спасибо за комментарии, я изменил вопрос. Да, я бы хотел заставить всех реализовать event1 (если возможно). B добавляет функциональность к A, а B будет запускать не только event2, но и event1.
Но почему? Кто вам скажет, что все, кого интересует event2, в то же время заинтересованы в event1? Когда вы добавляете поддержку слушателя к компоненту, вы обычно не знаете, для чего он будет использоваться, поэтому вам не следует форсировать эту комбинацию.
Из вашего комментария к saua я понимаю, что при стрельбе B автоматически активируется A.
Почему бы не использовать один тип слушателя, а затем смешать наследование, делегирование и дженерики?
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)
}
}
Объяснение: код для управления слушателями является общим в диспетчере слушателей базового класса. B может получать BListeners только из-за проверки обобщенных типов во время компиляции. Выстрел B автоматически выстрелит A.
Использование универсального звучит многообещающе, но в этом решении B не является подклассом A, что является серьезным недостатком.
Оба они являются подклассами ListenerManager, в котором определены все методы. Разве это не то же самое, что наследование от A?
Если
public class BEvent extends AEvent {
...
}
public interface BListener extends AListener {
public void event2(BEvent event);
}
ты не можешь сделать что-нибудь вроде:
public class B extends A {
@Override
public synchronized void addAListener(AListener l) {
if (l instanceof BListener) {
...
} else {
super.addAListener(l);
}
}
...
}
Как я уже сказал в комментарии, я не уверен, чего вы на самом деле хотите достичь? Кому откуда звонят и что ему нужно делать при звонке?
Мне кажется, что все можно упростить.
Мое понимание
У вас есть базовый класс А, который выполняет основная операция
У вас может быть более конкретный подкласс B, который в дополнение может выполнять еще несколько specificOperations
В этом случае необходимо, чтобы оба события обрабатывались (базовый для A и базовый + специфический для B)
Что ж, вам не нужно перегружать методы для этого, единственное, что вам нужно сделать, это добавить определенные обработчики (или прослушиватели) для определенных событий.
Может быть ивент «базовый», ничего страшного.
Но когда событие носит конкретный характер, нужно реагировать соответствующим образом. Итак, я бы добавил проверку в конкретного слушателя, чтобы различать событие специфический следующим образом:
if ( whichEvent instanceof SpecificEvent ) {
SpecificEvent s = ( SpecificEvent ) whichEvent;
// Do something specific here...
}
Вот и все.
Ваше описание проблемы слишком абстрактно, поэтому не может быть предложено никаких конкретных решений. Тем не менее, если сложно объяснить, чего вы хотите достичь, возможно, вам придется заново проанализировать, в чем заключается проблема.
Если мое понимание выше верно (что вам нужно обрабатывать базовый + специфический несколько раз), следующий длинный код ниже может помочь.
С наилучшими пожеланиями
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();
}
}
Пример вывода:
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"
Двигаясь дальше, вы даже можете избавиться от интерфейсов, чтобы упростить его.
Основываясь на той небольшой информации, которая у нас есть о взаимосвязи между A и B, я думаю, что делать BListener субинтерфейсом AListener сбивает с толку. Как следует из названия, предполагается, что BListener прослушивает BEvent, которые являются уже подклассом AEvent. Для ясности, слушатели должны иметь проницательные цели; они не должны пересекаться без надобности. Кроме того, нет необходимости в таких перекрывающихся слушателях, поскольку вы уже определяете отдельные методы в классе B для обработки разных типов слушателей.
Чтобы проиллюстрировать мою точку зрения, рассмотрим этот пример, стилизованный под ваш код:
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)
Этот дизайн работает, но сбивает с толку, потому что ClickableMouseListener обрабатывает два типа событий, а ClickableMouseWidget обрабатывает два типа слушателей, как вы правильно заметили. Теперь рассмотрим следующую альтернативу, в которой вместо наследования используется композиция:
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
Не используйте наследование, это не то, что вы хотите, и это приведет к хрупкому и сложному изменению дизайна. Композиция - более гибкий и лучший подход к дизайну. Всегда старайтесь разрабатывать интерфейсы как можно более детально, потому что они не должны быть изменены событием. Это ваш контракт с остальной частью системы. Если необходимо добавить новые функции, первый вариант - добавить дополнительную информацию к событию. Если это не подходит, вам следует разработать новый интерфейс для доставки этого события. Это предотвращает необходимость изменения существующего кода, на который это не влияет.
Вот мой любимый шаблон для этого, я полагаю, его обычно называют наблюдателем.
Создайте новый интерфейс, определяющий методы для этого типа события (fooEvent () addFooEventListener () removeFooEventListener ()). Реализуйте этот интерфейс в конкретном классе, который генерирует эти события. (Я обычно называю это чем-то вроде SourcesFooEvent, FiresFooEvent, FooEventSource и т. д.)
Если вы хотите уменьшить дублирование кода, вы можете создать вспомогательный класс, который обрабатывает регистрацию слушателей, сохраняет их в коллекции и предоставляет метод огня для публикации событий.
Здесь могут помочь дженерики. Во-первых, общий интерфейс слушателя:
public interface Listener<T> {
void event(T event);
}
Затем соответствующий интерфейс EventSource:
public interface EventSource<T> {
void addListener(Listener<T> listener);
}
Наконец, абстрактный базовый класс для быстрого создания вспомогательного класса для обработки регистрации слушателей и отправки событий:
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);
}
}
}
Вы могли бы использовать абстрактный EventDispatcher через инкапсуляцию, позволяя любому другому классу легко реализовать EventSource, не требуя при этом расширения какого-либо конкретного класса.
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);
}
}
Надеюсь, это иллюстрирует хороший баланс между безопасностью типов (важно), гибкостью и повторным использованием кода.
Если вы собираетесь использовать функции Java 5, вам следует использовать CopyOnWriteArrayList вместо ArrayList для EventDispatcher <T>, чтобы избежать проблем с параллелизмом.
Марк, я думаю, вам может понадобиться новый CopyOnWriteArrayList <Listener <T>> (); вместо. В противном случае код не компилируется.
Не могли бы вы немного расширить свои мотивы для этого дизайна? Почему имеет значение, что B расширяет A, в каких ситуациях все это будет использоваться? В остальном подход Ужина, кажется, указывает правильное направление.