Наследовать универсальный тип в интерфейсе реализации вложенного перечисления - PullRequest
1 голос
/ 07 июля 2019

Я пытаюсь написать общий абстрактный класс для моего конечного автомата, который будет использовать enum, реализующий некоторый интерфейс, определенный внутри этого абстрактного класса.У меня есть абстрактный класс, который содержит поле, реализующее универсальный интерфейс, и некоторые функции-оболочки для логики переключения состояний.Я пытаюсь расширить этот абстрактный класс и создать вложенный тип enum, реализующий этот универсальный интерфейс, но есть некоторые места, где я должен четко определить, какие типы я использую для универсальных шаблонов.Приведенный ниже код демонстрирует эту проблему

public abstract class FiniteStateMachine<C, I> { // <- generic types declared here
   private State<C, I> currentState;
   protected FiniteStateMachine(State<C, I> initial){ currentState = initial; }
   // some other methods for FSM, that I don't want to include in State<>
   // ...

   public synchronized void process(C context, I input) {
      State<C, I> nextState = currentState.process(context, input)
      if(currentState != nextState){
         currentState.onExit(nextState, context);
         State<C, I> previousState = currentState;
         currentState = nextState;
         nextState.onEnter(previousState, context);
      }
   }

   public interface State<C, I> { //<- this interface should use the same types as FiniteStateMachine
      State<C, I> process(C context, I input);
      default void onEnter(State<C, I> s, C context) {}
      default void onExit(State<C, I> s, C context) {}
   }
}
class FSM extends FiniteStateMachine<Data, String> { // <- here I define types used for FSM
   public FSM() { super(FSMStage.START); }

   enum FSMState implements State<Data, String> { // <- and here I have to repeat them
      START{
         @Override
         public FSMState process(Data p, String s) {
            // ...
            return NEXT;
         },
         @Override
         public void onExit(State s, Data d) { /* ... */ }
      },
      NEXT{
         // ...
      }

   }
}

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

FiniteStateMachine является абстрактным, а не интерфейсом, потому что мне нужны некоторые флаги и поля начального состояния (и я не могу создать «абстрактное поле» иначе, как с помощью взлома защищенного конструктора).FiniteStateMachine.State - это интерфейс, потому что он используется в перечислениях, которые не могут быть расширены.Я также хочу сохранить FiniteStateMachine и FiniteStateMachineState в одном файле, потому что отдельные поля создают много раздувного контента в проекте.Также внутри расширения FSM метод onExit имеет тип State вместо FSMStage.

Я пробовал что-то вроде FiniteStateMachine<C, I, State<C, I>>, но в ошибках говорилось, что «State не доступно в контексте».

Есть ли способ объявить типы в одном месте в расширяющем классе вместо FSM и FSMState, как сейчас?Или, может быть, есть способ объявить типы только для FSMState и заставить FSM повторно использовать эти типы?Или, может быть, этот дизайн полностью испорчен?

Ответы [ 2 ]

1 голос
/ 07 июля 2019

Дело в том, что внутренние интерфейсы (и перечисления тоже между прочим) неявно статичны, поэтому они не могут использовать общие параметры внешнего класса, потому что у них нет доступа к экземплярам внешнего класса.

Так что в основном этоИнтерфейс находится внутри абстрактного класса только для удобства кода, вы также можете извлечь его в отдельный файл.В этом дизайне я не думаю, что есть способ пропустить несколько объявлений универсального типа.

Я думаю, что изменение дизайна просто для того, чтобы избежать повторного объявления, не стоит - вы могли бы сделать State абстрактнымКласс и удалить универсальные типы из объявления, чтобы он получал универсальные типы из внешнего класса, но тогда идея с перечислениями не работает.

0 голосов
/ 07 июля 2019

C в FiniteStateMachine - это не то же самое C в State классе. Если вы хотите сделать их зависимыми, вы должны определить третий тип в вашем FiniteStateMachine, который использует C и I с State. Вы были почти правы с FiniteStateMachine<C, I, State<C, I>>, но вот как вы можете это сделать:

public abstract class FiniteStateMachine<C, I, T extends FiniteStateMachine.State<C, I>> {
    private T currentState;
    protected FiniteStateMachine(T initial){ currentState = initial; }

    public synchronized void process(C context, I input) {
        FiniteStateMachine.State<C, I> nextState = currentState.process(context, input);
        if(currentState != nextState){
            currentState.onExit(nextState, context);
            State<C, I> previousState = currentState;
            currentState = (T) nextState;
            nextState.onEnter(previousState, context);
        }
    }

    public interface State<CTX, INPT> {
        State<CTX, INPT> process(CTX context, INPT input);
        default void onEnter(State<CTX, INPT> s, CTX context) {}
        default void onExit(State<CTX, INPT> s, CTX context) {}
    }
}

Теперь вы заставляете типы State быть такими же, как те, которые определены в FiniteStateMachine.

Теперь вы можете использовать его как:

public class FSM extends FiniteStateMachine<Date, String, FSM.FSMState> { 
    public FSM() { super(FSMState.START); }

    public enum FSMState implements FiniteStateMachine.State<Date, String> {
        START{
            @Override
            public FSMState process(Date p, String s) {
                // ...
                return NEXT;
            }

            @Override
            public void onExit(FiniteStateMachine.State s, Date d) { /* ... */ }
        },
        NEXT{
            @Override
            public FiniteStateMachine.State<Date, String> process(Date context, String input) {
                return null;
            }
            // ...
        }
    }
}

В противном случае, как насчет повторения универсальных типов!? Из того, что я узнал до сих пор, нет никакого способа упростить написание ваших классов. Я думаю, что источником вашего вопроса было, когда два класса объявлены в одном файле. Что если они были разделены на два разных файла? Вы продолжаете так думать?

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