Reputation: 1307
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
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
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
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