rediVider
rediVider

Reputation: 1307

How to get around this java generics overuse or misuse

I'm trying to create a generic boradcaster to which client code may subscribe. Client code will then be updated (via the ReportListener) when any changes are mode to concrete Reporter_Abstract sub classes.

I know I'm over using generics here (for example

public abstract class Reporter_Abstract<I extends Reporter_Abstract<I>>

feels dirty), but i'm not sure how to enforce these requirements in another way. How can i ensure that a listener added in code handles changes to I, where I is the subtype of Reporter_Abstract

public abstract class Reporter_Abstract<I extends Reporter_Abstract<I>>
{
  private final List<ReportListener<I>> _listeners;


  /**
   * Broadcast change to any attached listeners, skipping the sender
   */
  protected final void reportChange( ReportListener<I> sender )
  {
    List<ReportListener<I>> listeners = collectListeners();
    for( ReportListener<I> listener : listeners )
    {
        try
        {
            if( sender == null || sender != listener )
            {
                listener.recognizeChange( (I) this );
            }
        }
        catch( Exception e )
        {
            _log.error( "Uncaught exception from listener: " + listener, e );
        }
    }
  }
}


public class SomeStateReporter extends Reporter_Abstract<SomeStateReporter>
{
  private boolean _isSomeStateActive;

  public void setSomeStateActive( boolean isSomeStateActive )
  {
     if( isSomeStateActive ^ _isSomeStateActive )
     {
        _isASomeStateActive = isSomeStateActive;
        super.reportChange();
     }
  }
}

public interface ReportListener<T extends Reporter_Abstract<?>>
{
    public void recognizeChange( T report );
}   

And the class that wants to listen to changes

public class ChangeHandlingClass()
{ 
  public void attachSomeStateListener( SomeStateReporter someStateReporter )
  {
    sometateReporter.addListener( new SomeStateUpdateListener() );
  }


  private class SomeStateUpdateListener implements ReportListener<SomeStateReporter>
  {
        @Override
        public void recognizeChange( SomeStateReporter report )
        {
            handleStateChange( report.isSomeStateActive()
        }
  }
}

If this is the right way (or A right way), then shouldn't this line

listener.recognizeChange( (I) this );

allow me to use an argument of 'this' without the cast, and know that this is the definition of I?

Am I way off base?

Upvotes: 3

Views: 1171

Answers (3)

Paul Bellora
Paul Bellora

Reputation: 55213

@StriplingWarrior and I discussed a similar pattern on this post: Is there a way to refer to the current type with a type variable?

I would say that you're not off track in terms of implementing this pattern, however it's important to recognize the contract that Reporter_Abstract must lay out to its subclasses, which are responsible for implementing it correctly. Subclasses must be final as long as they consume the type parameter I with their own type, to prevent additional subclasses from making I incorrect.

Enum employs a similar pattern - an additional reason why an enum cannot be extended, because its parent class Enum expects its type parameter E to match the type of the extending enum.

The real question is whether you want this pattern as opposed to something like @Andy suggests. What you're trying to do can only be done safely if you have exclusive control over anything subclassing Reporter_Abstract.

Upvotes: 3

meriton
meriton

Reputation: 70564

If this is the right way (or A right way), then shouldn't this line

listener.recognizeChange( (I) this );

allow me to use an argument of 'this' without the cast, and know that this is the definition of I?

Because somebody might declare:

class MyEvilReporter extends Reporter_Abstract<SomeStateReporter> {
    ...
}

As for solving the problem, can't you just do:

interface ChangeListener {
    void stateChanged();
}

In my experience, listeners often know what they are listening on anyway, so there is no need for this method parameter, and hence no need to fight with generics :-)

Upvotes: 3

Andy Thomas
Andy Thomas

Reputation: 86381

How can i ensure that a listener added in code handles changes to I ...

One pattern that's worked for me, in multiple codebases, is to define the listener not in terms of the sender, but instead in terms of an event sent by the sender. This allows sending data along with the notification, including a reference to the sender.

This pattern works:

// Listener type
public interface IEventListener<EVENT> {
    public void receiveEvent( @NonNull EVENT event );
}

// Event producer interface
public interface IEventProducer<LISTENER extends IEventListener<EVENT>, EVENT> {
    public void addEventListener( @NonNull LISTENER listener );
    public void removeEventListener( @NonNull LISTENER listener );
}

// Concrete implementation
public final class EventProducer<LISTENER extends IEventListener<EVENT>,EVENT> 
  implements IEventProducer<LISTENER, EVENT> { ... }

A listener can skip itself if it's effectively the sender. Note that some listeners could be inner classes of the sender.

Note that the producer implementation is included in senders by composition rather than inheritance, which allows its use even in classes that already have a superclass.

Upvotes: 2

Related Questions