Как обойти это превышение или неправильное использование дженериков Java - PullRequest
3 голосов
/ 19 сентября 2011

Я пытаюсь создать универсальный boradcaster, на который клиентский код может подписаться. Затем клиентский код будет обновлен (через ReportListener), когда любые изменения будут в режиме для конкретных подклассов Reporter_Abstract.

Я знаю, что я перестал использовать дженерики здесь (например,

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

чувствует себя грязным), но я не уверен, как применить эти требования по-другому. Как я могу гарантировать, что слушатель, добавленный в код, обрабатывает изменения I, где I является подтипом 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 );
}   

И класс, который хочет слушать изменения

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()
        }
  }
}

Если это правильный путь (или правильный путь), то не должна ли эта строка

listener.recognizeChange( (I) this );

позвольте мне использовать аргумент 'this' без приведения и знать, что это определение I?

Я далеко от базы?

Ответы [ 3 ]

3 голосов
/ 19 сентября 2011

@ StriplingWarrior и я обсуждал похожую схему в этом посте: Есть ли способ сослаться на текущий тип с помощью переменной типа?

Я бы сказал, что вы не сошли с пути в плане реализации этого паттерна, однако важно признать контракт, который Reporter_Abstract должен выложить на его подклассы, которые отвечают за его правильную реализацию. Подклассы должны быть final, если они используют параметр типа I со своим собственным типом, чтобы дополнительные подклассы не могли сделать I неправильным.

Enum использует аналогичный шаблон - еще одна причина, по которой enum не может быть расширен, поскольку его родительский класс Enum ожидает, что его параметр типа E будет соответствовать типу расширяющего enum.

Реальный вопрос в том, хотите ли вы этот шаблон, в отличие от того, что @ Энди предлагает . То, что вы пытаетесь сделать, может быть сделано безопасно только в том случае, если вы имеете исключительный контроль над чем-либо, подклассами Reporter_Abstract.

3 голосов
/ 19 сентября 2011

Если это правильный путь (или правильный путь), то эта строка

listener.recognizeChange( (I) this );

не должна позволять мне использовать аргумент 'this' без приведения и знать,что это определение I?

Поскольку кто-то может заявить:

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

Что касается решения проблемы, разве вы не можете просто сделать:

interface ChangeListener {
    void stateChanged();
}

По моему опыту, слушатели в любом случае часто знают, что они слушают, поэтому в этом параметре метода нет необходимости, и, следовательно, нет необходимости бороться с генериками: -)

2 голосов
/ 19 сентября 2011

Как я могу убедиться, что слушатель, добавленный в код, обрабатывает изменения в I ...

Один шаблон, который работал для меня, в нескольких кодовых базах, это определить, что слушатель не вс точки зрения отправителя, но вместо этого с точки зрения события , отправленного отправителем.Это позволяет отправлять данные вместе с уведомлением, включая ссылку на отправителя.

Этот шаблон работает:

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

Слушатель может пропустить себя, если он фактически является отправителем.Обратите внимание, что некоторые слушатели могут быть внутренними классами отправителя.

Обратите внимание, что реализация производителя включается в отправители по составу, а не по наследству, что позволяет использовать его даже в классах, которые уже имеют суперкласс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...