Очень простая машина с конечным состоянием

У меня есть начальное знание C ++ и использование этого и некоторого собственного фиктивного дизайна. Я придумал вышеупомянутую реализацию. FiniteStateMachine здесь сначала запрашивает состояния, которые должны быть добавлены, затем добавляются переходы. Переход является собственностью государств (от государства). После того, как машина подготовлена, acceptInput вызывается для приема /отклонения ввода.

#include<iostream>
#include<vector>
using namespace std;

class Input {
   private:
      string data;
   public:
      Input(string data): data(data){}
      Input(const Input &ip) {
         data = ip.getData();
      }
      string getData() const {
         return this->data;
      }
};
class State;
class Transition {
   private:
      Input ip;
      State *to;
   public:
      Transition(Input ip,State *to): ip(ip),to(to){}
      Input getInput() {
         return this->ip;
      }
      const State* getTransitionState() {
         return this->to;
      }
};

class State {
   private:
     string label;
     bool isStart;
     bool isEnd;
     vector<Transition> transitions;
   public:
     State(string label="",bool isStart=false,bool isEnd=false): 
       label(label),isStart(isStart),isEnd(isEnd) {}
     State(const State &s) {
        this->label = s.getLabel();
     }
     string transit(Input ip) {
        for(vector<Transition>::iterator it = transitions.begin(); it<transitions.end(); ++it) {
           if((*it).getInput().getData()==ip.getData())
              return (*it).getTransitionState()->getLabel();
        }
        return string("");
     }
     string getLabel() const {
        return this->label;
     }
     void addTransition(Transition t) {
        this->transitions.push_back(t);
     }
};

class StateMachine {
   private:
      vector<State> states;
      int currentStateIndex;
      Input *currentInput;
      void consumeInput();
   public:
      void addState();
      void acceptInput();
      void addTransition();
};

void StateMachine::addTransition() {
   string start,end;
   string inputLabel;
   cout << "Enter the start state of the transition \n";
   cin >> start;
   cout << "Enter the endstate of the transition \n";
   cin >> end;
   cout << "Enter the input label of the transition\n";
   cin >> inputLabel;
   vector<State>::iterator sit=states.end(), eit=states.end();
   for(vector<State>::iterator it = states.begin(); it<states.end(); ++it) {
      if((*it).getLabel()==start) {
         sit = it;
      }
      if((*it).getLabel()==end) {
         eit = it;
      }
      if(sit!=states.end()&&eit!=states.end()) {
         break;
     }
   }
   if(sit==states.end()||eit==states.end()) {
      cout << "Both the states not present\n";
      return;
   }
   Transition t(Input(inputLabel),&(*eit));
   (*sit).addTransition(t);
}

void StateMachine::addState() {
   string stateLabel;
   cin >> stateLabel;
   for(vector<State>::iterator it = states.begin(); it<states.end(); ++it) {
      if((*it).getLabel()==stateLabel) {
         cout << "a state with label "+stateLabel+" already present" << std::endl;
         return;
      }
   }
   char isStart,isEnd;
   cout << "Is a start state ? y/n" << std::endl;
   cin >> isStart;
   cout << "Is a end state ? y/n" << std::endl;
   cin >> isEnd;
   State s(stateLabel,isStart=='y',isEnd=='y');
   this->states.push_back(s);
   if(isStart=='y') {
      this->currentStateIndex = states.size()-1;
   }
}
void StateMachine::acceptInput() {
   string inputLabel;
   cin >> inputLabel;
   string nextStateLabel = this->states[this->currentStateIndex].transit(Input(inputLabel));
   vector<State>::iterator it = states.begin();
   for(; it<states.end(); ++it) {
      if((*it).getLabel()==nextStateLabel) {
         this->currentStateIndex = it-states.begin();
         cout << "current state is "+nextStateLabel<<std::endl;
         break;
      }
   }
   if(it==states.end())
      cout << "no transition for the input form currentState" << std::endl;
}


int main() {
   StateMachine sm;
   sm.addState();
   sm.addState();
   sm.addTransition();
   sm.addTransition();
   sm.addTransition();
   sm.addTransition();
   while(1) {
      sm.acceptInput();
   }
   return 0;
}
11 голосов | спросил Abhinav Agarwal 6 +03002017-10-06T14:03:01+03:00312017bEurope/MoscowFri, 06 Oct 2017 14:03:01 +0300 2017, 14:03:01

2 ответа


12

using namespace std

Не используйте using namespace std. Это считается плохой практикой.

Согласованность

Используйте последовательный стиль кодирования. Вы используете std::endl и cout и ---- +: = 4 =: + ---- и states.

this->states сначала, public last

Это вопрос личных предпочтений, но обычно вас больше интересует, как вы можете получить доступ к своему классу. Поэтому раздел private обычно помещается первым, а public обычно помещается последним.

Минимизировать IO в ваших функциях

Ваш private нужен пользовательский ввод для настройки. Но это трудно проверить. Вместо этого попробуйте предоставить удобный интерфейс:

StateMachine

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

bool StateMachine::addState(std::string label, bool startState, bool endState) {
    for(auto & state : states) {
        if(state.getLabel() == label) {
            return false;
        }
    }
    states->emplace_back(label, startState, endState);
    // ...
}

Мы можем еще использовать пользовательский ввод:

StateMachine sm;

sm.addState("example1", true, false);
sm.addState("example2", false, false);
sm.addState("example3", false, false);
sm.addState("example4", false, true);

Таким образом, мы вообще не потеряли никакой функциональности.

Предпочитает диапазон std::string label; char isStart, isEnd; std::cin >> label >> isStart >> isEnd; sm.addState(label, isStart, isEnd); -loops

См. выше пример. С ними гораздо удобнее работать, если вам не нужен индекс или итератор определенного элемента. Вам нужен компилятор, который понимает C ++ 11 или выше.

Документация и комментарии

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

for

к

void StateMachine::acceptInput() {
   string inputLabel;
   cin >> inputLabel;
   string nextStateLabel = this->states[this->currentStateIndex].transit(Input(inputLabel));
   vector<State>::iterator it = states.begin();
   for(; it<states.end(); ++it) {
      if((*it).getLabel()==nextStateLabel) {
         this->currentStateIndex = it-states.begin();
         cout << "current state is "+nextStateLabel<<std::endl;
         break;
      }
   }
   if(it==states.end())
      cout << "no transition for the input form currentState" << std::endl;
}

Немного легче читать, не так ли? Обратите внимание, что вы можете заменить void StateMachine::acceptInput(std::string inputLabel) { std::string nextStateLabel = states[currentStateIndex].transit(Input(inputLabel)); for(auto it = states.begin(); it != states.end(); ++it) { if(it->getLabel() == nextStateLabel) { // This is our new state. currentStateIndex = it - states.begin(); std::cout << "current state is " << nextStateLabel << std::endl; return; } } std::cout << "no transition for the input form currentState" << std::endl; } на (*it).foo , Кроме того, всегда всегда it->foo всякий раз, когда функция выполняет свою работу. Просто имейте в виду, что вы должны использовать RAII.

Скрыть сведения об осуществлении

Ни в коем случае пользователь не должен знать, что существует return или Transition. Вы можете скрыть все эти детали реализации. Все, что им нужно, это чистый интерфейс в State:

StateMachine
ответил Zeta 6 +03002017-10-06T14:32:39+03:00312017bEurope/MoscowFri, 06 Oct 2017 14:32:39 +0300 2017, 14:32:39
3

Ответ Зеты превосходный. Я хотел бы указать на несколько дополнительных вещей.

Во-первых, помните, что вы можете использовать = default для реализации конструктора шаблона, такого как конструктор копирования для Input. По мере изменения кода в будущем это уменьшит вероятность того, что вы забудете скопировать недавно добавленное поле.

Во-вторых, Input, вероятно, является ненужным классом. Вот мои рассуждения:

  1. Инкапсуляция позволяет вам использовать ограничения. Например, если вам нужно убедиться, что длина входной строки всегда была четной, тогда было бы целесообразно инкапсулировать строку общего назначения в класс с помощью методов для поддержания этого ограничения. Класс Input не имеет таких ограничений - любая строка является допустимым.

  2. Инкапсуляция позволяет скрыть представление объекта за интерфейсом. Например, класс Rectangle в программе рисования может использовать две координаты или одну координату плюс высоту и ширину. Пользователю класса не должно волновать, что такое базовое представление, и поставщик класса должен иметь возможность изменить представление, не затрагивая вызывающего абонента .
    В случае Input представление неинтересно и (эффективно) отображается через API. Да, теоретически внутреннее представление может быть чем-то иным, чем std::string, а конструктор и аксессор могут преобразовывать назад и вперед, но это не и это кажется маловероятным.

  3. Инкапсуляция позволяет предоставить ограниченный API. A std::string имеет расширенный API, и можно утверждать, что это больше, чем пользователь потребностей StateMachine. Но Input не ограничивает этот API - он просто передает оригинальный std::string назад к пользователю.

  4. Инкапсуляция позволяет вам дать что-то содержательное имя. Это одно из преимуществ, которое я вижу в существующем классе Input. Но, учитывая, что вам не нужен класс для других преимуществ, более простой способ сделать это будет typedef std::string Input;.

Наконец, я думаю, что стоит подчеркнуть и обобщить один из комментариев Zeta: Отделить пользовательский интерфейс от «бизнес-логики». Класс StateMachine должен дать вам внутреннюю работу конечного автомата и ничего Больше. Включение интерфейса внутри класса нарушает принцип единой ответственности . Следуя принципу единой ответственности, код легче тестировать (посредством автоматизации) и делает его более пригодным для повторного использования. Если в StateMachine не было встроенного в консоль ввода-вывода, тогда было бы легко повторно использовать его графическую программу пользовательского интерфейса или на сетевом сервере, который отвечает на запросы клиентов.

ответил Adrian McCarthy 6 +03002017-10-06T21:21:58+03:00312017bEurope/MoscowFri, 06 Oct 2017 21:21:58 +0300 2017, 21:21:58

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132