سؤال

لدي فئة جافا التي تطلق أحداث جافا مخصصة.هيكل الكود هو كما يلي:

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) {
  ..
  }

}

هل هذا هو النهج الصحيح؟لقد كنت أبحث في الويب عن أمثلة، لكن لم أتمكن من العثور على أي منها.

هناك بعض الأشياء التي لا أحبها في هذا الحل:

  1. BListener له طريقتان يستخدم أحدهما AEvent الاستخدامات الأخرى BEvent كمعلمة.
  2. B الطبقة على حد سواء لديها addAListener و addBListener طُرق.هل يجب علي إخفاء addAListener بكلمة رئيسية خاصة؟ [تحديث:لا يمكن الإخفاء باستخدام كلمة رئيسية خاصة]
  3. مشكلة مماثلة مع fireAListenerEvent1 و fireBListenerEvent1 طُرق.

أنا أستخدم إصدار جافا 1.5.

هل كانت مفيدة؟

المحلول

لا أرى سببا لذلك BListener ينبغي أن تمتد AListener.

هل تريد حقًا إجبار جميع المهتمين بذلك؟ B الأحداث لتنفيذها أيضا event1()?

كما لا يمكنك الإضافة addAListener(), ، نظرًا لأن الفئة المشتقة لا يمكنها تقليل ظهور الطريقة الموجودة في الفئة الأصلية.أيضًا، لا يجب أن تحتاج إلى ذلك، وإلا فسوف تنتهك مبدأ استبدال ليسكوف (يجب أن يكون كل "ب" قادرًا على فعل كل ما يستطيع "أ" فعله).

وكملاحظة أخيرة، أود أن أقول fire*() طرق محمية.لا يوجد عادةً أي سبب على الإطلاق لإبقائها عامة، كما أن تقليل عدد الأعضاء العموميين يبقي واجهتك العامة نظيفة.

نصائح أخرى

لا تستخدم الميراث، فهذا ليس ما تريده وسيؤدي إلى تصميم هش وصعب التغيير.يعد التكوين أسلوبًا أكثر مرونة وأفضل للتصميم.حاول دائمًا تصميم واجهات تفصيلية قدر الإمكان لأنه لا ينبغي تغييرها.هم العقد الخاص بك مع بقية النظام.إذا كانت هناك حاجة إلى إضافة وظائف جديدة، فإن الخيار الأول هو إضافة المزيد من المعلومات إلى الحدث.إذا لم يكن ذلك مناسبًا، فيجب عليك تصميم واجهة جديدة لتقديم هذا الحدث.وهذا يمنع الاضطرار إلى تغيير أي رمز موجود لم يتأثر.

هذا هو النمط المفضل لدي لهذا، وأعتقد أنه يشار إليه عادة باسم المراقب.

قم بإنشاء واجهة جديدة تحدد طرقًا لنوع الحدث هذا (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);
  }
}

نأمل أن يوضح هذا التوازن الجيد بين أمان النوع (مهم) والمرونة وإعادة استخدام التعليمات البرمجية.

أفهم من تعليقك لـ 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 تلقائيًا.

يبدو لي أنه يمكنك إبقاء الأمور بسيطة للغاية.

فهمي

  • لديك فئة أساسية أ الذي ينفذ بعض عملية أساسية

  • قد يكون لديك فئة فرعية أكثر تحديدًا ب وهذا بالإضافة إلى ذلك قد يؤدي المزيد عمليات محددة

إذا كان الأمر كذلك، فأنت بحاجة إلى التعامل مع كلا الحدثين ( أساسي ل و أساسي + محدد ل ب )

حسنًا، لا تحتاج إلى التحميل الزائد على الأساليب للقيام بذلك، الشيء الوحيد الذي عليك القيام به هو إضافة معالجات (أو مستمعين) محددين لأحداث معينة.

قد يكون الحدث "أساسيًا"، فلا بأس بذلك.

ولكن عندما يكون الحدث محددًا، يتعين عليك التصرف وفقًا لذلك.لذا، ما سأفعله هو إضافة شيك في المستمع المحدد للتمييز محدد حدث مثل هذا:

        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"

وللمضي قدمًا في الأمر، يمكنك أيضًا التخلص من الواجهات لإبقاء الأمر أكثر بساطة

لو

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);
    }
  }
  ...
}

كما قلت في التعليق، لست متأكدًا مما تريد تحقيقه بالفعل؟من يتم الاتصال به من أين، وماذا يجب عليه فعله عندما يتم الاتصال به؟

بناءً على المعلومات القليلة التي لدينا حول العلاقة بينهما 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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top