[GO] Développons quelque chose de proche de celui intégré avec TDD ~ Design pattern ~

En utilisant googletest / googlemock, je développe un logiciel qui fonctionne sur Linux embarqué avec TDD. Puisque la procédure réellement développée avec TDD est écrite telle quelle en temps réel, il y a une partie aléatoire. J'espère que vous apprécierez également le processus. Si vous avez des questions ou des erreurs, veuillez commenter. Ce sera encourageant.

Si vous souhaitez en savoir plus sur l'histoire jusqu'à présent, veuillez consulter les articles précédents. Essayez de développer quelque chose de proche de intégré avec TDD ~ Préparation ~ Essayez de développer quelque chose de proche de celui intégré avec TDD ~ Résolution de problèmes ~ Essayez de développer quelque chose de proche de celui intégré avec TDD ~ file open ~ Essayez de développer quelque chose de proche de celui intégré avec TDD ~ traitement d'initialisation / de terminaison de libevdev ~ Développer quelque chose de proche de celui intégré avec la détection d'entrée TDD-Key- Essayez de développer quelque chose de proche de celui intégré avec TDD-Intermediate review- Développer quelque chose de proche de intégré avec TDD ~ héritage d'interface ~

C'est enfin fini. La dernière fois, j'ai implémenté les spécifications suivantes.

La détection d'entrée de clé et la détection de minuterie peuvent désormais être utilisées sur la même interface en utilisant l'interface EventDetector comme classe de base commune. Cette fois, différentes opérations sur la LED peuvent être utilisées sur la même interface, améliorant encore la capacité d'extension du programme.

Que voulez-vous faire avec le fonctionnement des LED

Les pilotes de LED d'aujourd'hui ont trois fonctions pour manipuler les LED.

led_driver.h


void TurnOnLed(LedDriver self);
void TurnOffLed(LedDriver self);
void ToggleLed(LedDriver self);

Je veux utiliser ces trois dans la même interface et rendre la fonction principale indubitable.

main.c


  for(int i = 0; detectors[i] != NULL; i++) {
    StartEventDetector(detectors[i]);
    //Attendre la détection d'événement
    while(CheckEvent(detectors[i]) != DETECTOR_EVENT_DETECTED) {}

    ToggleLed(caps_led);  //Appelez l'interface commune ici et activez-la/Je veux éteindre correctement
    //Afin de répondre aux spécifications, il doit être écrit comme suit.
    // if (i == 0) TurnOnLed(caps_led);
    // else if (i == 1) TurnOffLed(caps_led);
  }

Modèle de commande

Par conséquent, le fonctionnement des LED est implémenté sous la forme d'un modèle de commande. Le modèle de commande est un modèle de conception qui peut exprimer des «commandes» sous forme d'instances.

Le modèle de commande est assez simple pour vous faire rire (bien que ce soit un désordre en C).

command.h


typedef struct CommandInterfaceStruct *CommandInterface;
typedef struct CommandStruct *Command;

typedef struct CommandStruct {
  CommandInterface vtable;
} CommandStruct;

//L'interface est exécutée()Seulement
typedef struct CommandInterfaceStruct {
  void (*Execute)(Command);
} CommandInterfaceStruct;

void CommandExecute(Command cmd);

La classe qui hérite de l'interface Command n'a besoin que d'implémenter Execute (). Cependant, cette implémentation simple a une flexibilité incroyable.

Mise en œuvre de la commande d'opération LED

Comme toujours, commençons par le test.

//Test pour générer et exécuter la commande pour allumer la LED
TEST_F(LedOperatorTest, LedTrunOnOperationCallsIoWriteWithOne) {
  // driver_Est une instance de LedDriver
  Command command = LedOperatorFactory(driver_, OP_LED_TURN_ON);
  EXPECT_CALL(*mock_io, IO_WRITE(kFd, StrEq("1\n"), 2)).Times(1);
  CommandExecute(command);
}

Nous savons que nous avons besoin d'au moins trois commandes contrôlées par LED, marche / arrêt / bascule. Par conséquent, j'ai préparé un modèle FactoryMethod et j'ai décidé de changer l'instance générée avec un argument.

Premièrement, la classe dérivée de fonctionnement des LED est la suivante. À l'exception de l'héritage de l'interface de commande, il n'y a qu'une instance de LedDriver.

led_operator_factory.c


typedef struct LedOperatorStruct {
  CommandStruct base;
  LedDriver driver;
} LedOperatorStruct;

typedef struct LedOperatorStruct *LedOperator;

L'implémentation d'Execute () qui allume la LED est la suivante.

led_operator_factory.c


static void LedTurnOn(Command super) {
  LedOperator self = (LedOperator)super;
  TurnOnLed(self->driver);
}

static CommandInterfaceStruct interface = {
  .Execute = LedTurnOn
};

Il ne vous reste plus qu'à créer une instance. À ce stade, nous n'utilisons pas l'argument pour changer le fonctionnement de la LED, mais nous l'implémenterons peu de temps après.

led_operator_factory.c


Command LedOperatorFactory(LedDriver driver, int32_t op_id) {
  if (driver == NULL) return NULL;
  LedOperator command = calloc(1, sizeof(LedOperatorStruct));
  command->base.vtable = &interface;
  command->driver = driver;

  return (Command)command;
}

Si vous le faites jusqu'à présent, le test pour allumer la LED passera.

Bien que l'explication soit un peu compliquée, nous avons préparé les tests suivants un par un et avons procédé à l'implémentation.

//Tester pour créer et exécuter la commande pour désactiver
TEST_F(LedOperatorTest, LedTrunOffOperationCallsIoWriteWithZero) {
  Command command = LedOperatorFactory(driver_, OP_LED_TURN_OFF);
  EXPECT_CALL(*mock_io, IO_WRITE(kFd, StrEq("0\n"), 2)).Times(1);
  CommandExecute(command);
}

//Tester pour générer et exécuter la commande pour basculer
TEST_F(LedOperatorTest, LedToggleOperationTogglesIoWrite) {
  Command command = LedOperatorFactory(driver_, OP_LED_TOGGLE);

  InSequence s;
  EXPECT_CALL(*mock_io, IO_WRITE(kFd, StrEq("1\n"), 2)).Times(1);
  EXPECT_CALL(*mock_io, IO_WRITE(kFd, StrEq("0\n"), 2)).Times(1);
  EXPECT_CALL(*mock_io, IO_WRITE(kFd, StrEq("1\n"), 2)).Times(1);

  CommandExecute(command);
  CommandExecute(command);
  CommandExecute(command);
}

//Vérifiez que l'usine ne parvient pas à générer la commande
TEST_F(LedOperatorTest, FatoryReturnsNullIfFailed) {
  Command command = LedOperatorFactory(nullptr, OP_LED_TURN_ON);
  EXPECT_EQ(nullptr, command);

  command = LedOperatorFactory(driver_, -1);
  EXPECT_EQ(nullptr, command);

  command = LedOperatorFactory(driver_, OP_MAX_FACTORY_ID);
  EXPECT_EQ(nullptr, command);
}

Le code produit final ressemble à ceci:

led_operator_factory.c


static void LedTurnOn(Command super) {
  LedOperator self = (LedOperator)super;
  TurnOnLed(self->driver);
}

static void LedTurnOff(Command super) {
  LedOperator self = (LedOperator)super;
  TurnOffLed(self->driver);
}

static void LedToggle(Command super) {
  LedOperator self = (LedOperator)super;
  ToggleLed(self->driver);
}

static CommandInterfaceStruct interface[OP_MAX_FACTORY_ID] = {
  { .Execute = LedTurnOn },
  { .Execute = LedTurnOff },
  { .Execute = LedToggle }
};

Command LedOperatorFactory(LedDriver driver, int32_t op_id) {
  if (driver == NULL) return NULL;
  if (op_id <= -1 || OP_MAX_FACTORY_ID <= op_id) return NULL;

  LedOperator command = calloc(1, sizeof(LedOperatorStruct));
  command->base.vtable = &interface[op_id];
  command->driver = driver;

  return (Command)command;
}

Maintenant, vous pouvez tout faire à partir de l'interface de commande, que ce soit la LED allumée, éteinte ou à bascule.

Code produit

Le code produit final est le suivant.

main.c


int main(void) {
  struct timeval kTime = {};
  const struct input_event kPressA = {kTime, EV_KEY, KEY_A, INPUT_KEY_PRESSED};

  EventDetector detectors[NUM_OPERATION_ON_DETECTION];
  detectors[0] = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressA);
  detectors[1] = CreateTimeOutDetector(5000, TIMER_ONE_SHOT);
  
  LedDriver caps_led = CreateLedDriver();
  if (InitLedDriver(caps_led, LED_DEVICE) != LED_DRIVER_SUCCESS) {
    DEBUG_LOG("Fail to init led device\n");
    exit(1);
  }

  Command operators[NUM_OPERATION_ON_DETECTION];
  operators[0] = LedOperatorFactory(caps_led, OP_LED_TURN_ON);
  operators[1] = LedOperatorFactory(caps_led, OP_LED_TURN_OFF);

  //La spécification est réalisée uniquement par l'interface EventDetector et Command.
  for(int i = 0; i < NUM_OPERATION_ON_DETECTION; i++) {
    StartEventDetector(detectors[i]);
    while(CheckEvent(detectors[i]) != DETECTOR_EVENT_DETECTED) {}

    CommandExecute(operators[i]);
  }
//Le processus de fin est omis
}

Si possible, les fonctions de détection de divers types d'événements et d'exécution de certaines opérations peuvent toutes être implémentées avec la même interface.

Détecte que le bouton a été enfoncé 3 fois en 2 secondes

Mettez en œuvre le problème suivant (mais ignorez un peu le règlement). (Problème) Implémentez une fonction qui retourne vrai / faux selon que le bouton a été pressé trois fois en 2 secondes.

Dans le problème ci-dessus, la pression du bouton est détectée lors de la mesure du passage de 2 secondes avec une minuterie. Les éléments individuels ne sont pas un gros problème, mais vous devez faire deux choses en même temps.

Cette fois, je vais essayer de résoudre ce problème en utilisant le modèle ActiveObject, qui est un modèle de conception de traitement parallèle simple.

J'omettrai la description du test car l'histoire sera mélangée, mais je continuerai à tout développer avec TDD après cela. Cliquez ici pour des tests sur ActiveObject. active_object_test.c

Modèle ActiveObject

Je pense que ce modèle est plus facile à comprendre si vous regardez l'API et l'implémentation principale. En gros, ajoutez simplement plusieurs commandes que vous souhaitez exécuter et exécutez-les toutes en même temps avec Run ().

active_object_engine.h


//Ajouter une commande au moteur
void FuelEngine(ActiveObjectEngine engine, Command cmd);

//Exécuter la commande installée dans le moteur
void EngineRuns(ActiveObjectEngine engine);

active_object_engine.c


typedef struct ActiveObjectEngineStruct {
  //Empruntez une liste de glib. Contient la liste de commandes ajoutée par FuelEngine.
  GSList *commands;
} ActiveObjectEngineStruct;

void FuelEngine(ActiveObjectEngine engine, Command cmd) {
  if ((engine == NULL) || (cmd == NULL)) return;
  //Ajouter une commande au moteur
  engine->commands = g_slist_append(engine->commands, (gpointer)cmd);
}

void EngineRuns(ActiveObjectEngine engine) {
  while(g_slist_length(engine->commands) > 0) {
    //Continue d'exécuter la commande tant que la commande existe dans le moteur.
    Command cmd = (Command)g_slist_nth_data(engine->commands, 0);
    CommandExecute(cmd);
    //Exclure les commandes une fois exécutées du moteur
    engine->commands = g_slist_remove(engine->commands, (gpointer)cmd);
  }
}

Pour autant que je sache, ça ne ressemble à rien. La véritable essence d'ActiveObject est ** après que Command a commencé à s'ajouter au moteur **.

detect_chain.c


//Cette DetectChain sera utilisée plus tard pour détecter la commande Konami.
//Commande qui exécute la commande de réveil lorsqu'un événement est détecté
Command CreateDetectChain(EventDetector detector,
                          //Prend le moteur qui s'exécute comme argument
                          ActiveObjectEngine engine,
                          //Commandes à ajouter au moteur lorsqu'un événement est détecté
                          Command wakeup) {
  DetectChain self = calloc(1, sizeof(DetectChainStruct));
  self->base.vtable = &interface;
  self->detector = detector;
  self->wakeup = wakeup;
}

static void DetectChainExecute(Command super) {
  DetectChain self = (DetectChain)super;

  if (CheckEvent(self->detector) == DETECTOR_EVENT_DETECTED) {
    //Ajouter une commande de réveil au moteur lorsqu'un événement est détecté
    FuelEngine(self->engine, self->wakeup);
    self->detector_started = false;
    CleanupEventDetector(self->detector);
  } else {
    //Se remettre au moteur jusqu'à ce qu'un événement soit détecté
    FuelEngine(self->engine, (Command)self);
  }
}

static CommandInterfaceStruct interface = {
  .Execute = DetectChainExecute
};

L'avez-vous vu d'une manière ou d'une autre?

En d'autres termes, si vous ajoutez une commande qui arrête le moteur après 2 secondes de détection écoulées et une commande qui compte la détection de pression sur le bouton du moteur, les deux commandes continueront à se déplacer en même temps. Lorsque le moteur est arrêté, retirez la valeur du compteur de pression sur le bouton et vous avez terminé.

ActiveObject est un modèle de traitement parallèle dans lequel un seul objet se déplace à la fois. Personnellement, c'est un modèle assez préféré, mais je ne peux pas l'utiliser quand j'ai vraiment besoin de multithreading. Il a une empreinte légère et devrait bien fonctionner dans des environnements pauvres.

Implémentation qui renvoie vrai / faux selon que le bouton a été enfoncé 3 fois en 2 secondes

Le nombre de fois que le bouton «A» a été enfoncé a été émis dans les 2 secondes immédiatement après l'exécution du programme.

Résultat d'exécution


sudo led_controller/three_times_in_two_sec
Press A key 3 times in two seconds

Ci-dessous, seules les parties importantes sont extraites.

three_times_in_two_sec.c


int main(void) {
  struct timeval kTime = {};
  const struct input_event kPressA = {kTime, EV_KEY, KEY_A, INPUT_KEY_PRESSED};

  ActiveObjectEngine engine = CreateActiveObjectEngine();

  // Create components.
  EventDetector press_a = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressA);
  EventDetector two_sec = CreateTimeOutDetector(2000, TIMER_ONE_SHOT);
  Command total = CreateCountTotal();
  Command halt = CreateHaltEngine(engine);

  TriggerActionPair one = CreateTriggerActionPair(press_a, total);
  TriggerActionPair two = CreateTriggerActionPair(two_sec, halt);

  Command cmd_one = CreateActionOnTriggerChain(one, engine, LOOP_CHAIN);
  Command cmd_two = CreateActionOnTriggerChain(two, engine, ONE_SHOT_CHAIN);
  FuelEngine(engine, cmd_one);
  FuelEngine(engine, cmd_two);

  EngineRuns(engine);

  //Par rapport à 3, la vérité est retournée
  printf("Press A key %d times in two seconds\n.", TotalIs(total));

  //Terminer le traitement
  return 0;
}

action_on_trigger.c


//Commande qui exécute la commande spécifiée lorsqu'un événement est détecté
typedef struct TriggerActionPairStruct {
  EventDetector detector;
  Command command;
} TriggerActionPairStruct;

typedef struct ActionOnTriggerChainStruct {
  CommandStruct base;
  TriggerActionPair chain;
  ActiveObjectEngine engine;
  int32_t loop_flag;
  bool started;
} ActionOnTriggerChainStruct;
typedef struct ActionOnTriggerChainStruct *ActionOnTriggerChain;

static void ExecuteActionOnTrigger(Command super) {
  ActionOnTriggerChain self = (ActionOnTriggerChain)super;

  if (!self->started) {
    StartEventDetector(self->chain->detector);
    self->started = true;
  }

  //Lorsqu'un événement est détecté
  if (CheckEvent(self->chain->detector) == DETECTOR_EVENT_DETECTED) {
    FuelEngine(self->engine, self->chain->command);
    CleanupEventDetector(self->chain->detector);
    self->started = false;
    if (self->loop_flag == ONE_SHOT_CHAIN)
      return;  // Finish this chain by avoiding FuelEngine().
  }

  FuelEngine(self->engine, (Command)self);
}

static CommandInterfaceStruct interface = {
  .Execute = ExecuteActionOnTrigger
};

TriggerActionPair CreateTriggerActionPair(EventDetector detector, Command command) {
  TriggerActionPair pair = calloc(1, sizeof(TriggerActionPairStruct));
  pair->detector = detector;
  pair->command = command;
  return pair;
}

// Should give ActiveObjectEngine.
Command CreateActionOnTriggerChain(TriggerActionPair chain,
                                   ActiveObjectEngine engine,
                                   int32_t loop_flag) {
  ActionOnTriggerChain self = calloc(1, sizeof(ActionOnTriggerChainStruct));
  self->base.vtable = &interface;
  self->chain = chain;
  self->engine = engine;
  self->loop_flag = loop_flag;
  self->started = false;

  return (Command)self;
}

Voir GitHub pour le reste des détails. Code source complet Code source où ** three_times_in_two_sec.c ** contient main.

Détecter la commande Konami

Utilisez DetectChain, que j'ai présenté plus tôt, pour détecter les commandes Konami. Lorsqu'une commande est détectée, le voyant de verrouillage des majuscules s'allume et le processus se termine. Si vous faites une erreur en cours de route, le programme se terminera sans rien faire. Puisqu'il y a de nombreux événements à détecter, la préparation est un peu longue. Si vous créez une commande DetectChain comme Matryoshka, tout ce que vous avez à faire est d'exécuter ActiveObjectEngine.

konami_command.c


int main(void) {
  struct timeval kTime = {};
  const struct input_event kPressUp = {kTime, EV_KEY, KEY_UP, INPUT_KEY_PRESSED};
  const struct input_event kPressDown = {kTime, EV_KEY, KEY_DOWN, INPUT_KEY_PRESSED};
  const struct input_event kPressLeft = {kTime, EV_KEY, KEY_LEFT, INPUT_KEY_PRESSED};
  const struct input_event kPressRight = {kTime, EV_KEY, KEY_RIGHT, INPUT_KEY_PRESSED};
  const struct input_event kPressA = {kTime, EV_KEY, KEY_A, INPUT_KEY_PRESSED};
  const struct input_event kPressB = {kTime, EV_KEY, KEY_B, INPUT_KEY_PRESSED};

  LedDriver caps_led = CreateLedDriver();
  if (InitLedDriver(caps_led, LED_DEVICE) != LED_DRIVER_SUCCESS) {
    DEBUG_LOG("Fail to init led device\n");
    exit(1);
  }

  // Create components.
  EventDetector press_up = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressUp);
  EventDetector press_down = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressDown);
  EventDetector press_left = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressLeft);
  EventDetector press_right = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressRight);
  EventDetector press_a = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressA);
  EventDetector press_b = CreateKeyInputDetector(KEYBOARD_DEVICE, &kPressB);
  Command caps_on = LedOperatorFactory(caps_led, OP_LED_TURN_ON);

  ActiveObjectEngine engine = CreateActiveObjectEngine();
  Command acceptted = CreateDetectChain(press_a, engine, caps_on);
  Command uuddlrlrb = CreateDetectChain(press_b, engine, acceptted);
  Command uuddlrlr = CreateDetectChain(press_right, engine, uuddlrlrb);
  Command uuddlrl = CreateDetectChain(press_left, engine, uuddlrlr);
  Command uuddlr = CreateDetectChain(press_right, engine, uuddlrl);
  Command uuddl = CreateDetectChain(press_left, engine, uuddlr);
  Command uudd = CreateDetectChain(press_down, engine, uuddl);
  Command uud = CreateDetectChain(press_down, engine, uudd);
  Command uu = CreateDetectChain(press_up, engine, uud);
  Command start = CreateDetectChain(press_up, engine, uu);

  FuelEngine(engine, start);

  EngineRuns(engine);

  return 0;
}

Qu'il s'agisse de détection de pression de bouton 3 fois en 2 secondes ou de détection de commande Konami, aucune modification n'a été apportée aux modules de détection d'entrée de touche, de minuterie et de fonctionnement LED qui ont été créés jusqu'à présent. C'est un gros point.

finalement

@ mt08, merci d'avoir fourni (en temps opportun) un numéro intéressant. J'ignore beaucoup le règlement, mais est-ce une mise en œuvre cool?

C'est la fin d'essayer de développer quelque chose de proche de l'intégration avec TDD. Lorsque la motivation sera rétablie, je la transplanterai autour de la tarte aux râpes et la déplacerai. Vers la fin, je ne pouvais pas expliquer tous les détails, alors j'ai cassé diverses choses ...

Après cela, je vous serais reconnaissant si vous pouviez poser des questions ou Masakari dans les commentaires.

Recommended Posts

Développons quelque chose de proche de celui intégré avec TDD ~ Design pattern ~
Développons quelque chose de proche de intégré avec TDD ~ Revue intermédiaire ~
Essayez de développer quelque chose de proche de l'intégration avec TDD ~ Problème ~
Développons quelque chose de proche de celui intégré avec TDD ~ édition ouverte de fichier ~
Développons quelque chose de proche de celui intégré avec TDD ~ Version de détection d'entrée de clé ~
Développons quelque chose de proche de l'implémentation avec TDD ~ traitement d'initialisation / de terminaison de libevdev ~
Utilisons le modèle de conception comme le langage C avec OSS design_pattern_for_c!
Modèles de conception à utiliser avec les bibliothèques Java fréquemment utilisées - Modèle de façade
Étapes pour développer Django avec VSCode
Modèles de conception à utiliser avec les bibliothèques Java fréquemment utilisées - Modèle abstrait Factory
[Introduction à WordCloud] Jouez avec le scraping ♬
[Introduction à Python] Utilisons foreach avec Python
Apprenez le modèle de conception "Singleton" avec Python
Développons un algorithme d'investissement avec Python 1
Apprenez le modèle de conception "Façade" avec Python