Table des matières
Gestionnaire d'événements
Prenons un exemple : je réalise une application chargée de manipuler une base de données. Il s'agit d'une base de données sur le cinéma.
Une partie de l'application concerne les acteurs. Je veux pouvoir les afficher, en ajouter, en modifier…
Une autre partie concerne les films. Je veux aussi pouvoir les afficher, les modifier…
On peut voir ces deux parties comme des sous-applications et on a intérêt à les rendre aussi indépendantes que possibles. Ainsi, on pourra commencer le développement de l'application « acteurs » et plus tard s'occuper de l'application « films ». Plus tard on pourrait ajouter une 3e application « salles ». Si les applications ne sont pas assez indépendantes, ajouter des fonctionnalités sera très difficile.
Des contrôleurs séparés
On définit donc un module acteurs.py et un module films.py qui contiennent chacun toutes les fonctions nécessaires.
acteurs.py contient ainsi les fonctions :
- pour afficher les boutons de menu de l'interface concernant les acteurs,
- toutes les fonctions permettant d'agir sur les acteurs dans la base de données
Pour se fixer les idées, disons que dans acteurs.py on ait les besoin suivant :
- afficher une liste d'acteurs. On définit une fonctions
show_list_actors(bdd, window)qui se trouve dansacteurs.py. Cette fonction a besoin d'un lien vers la base de données et d'un lien vers la fenêtre. - afficher la liste des films liées à un acteur. C'est la fonction
show_films_with_actor(id_actor, bdd, window). Mais cette fonction est dansfilms.pypuisqu'elle concerne des films.
C'est ennuyeux car acteurs.py a besoin de connaître une fonction de films.py. De même, films.py aurait besoin de connaître des fonctions de acteurs.py. Ces références croisées seront difficiles à gérer. On voudrait trouver une solution pour que acteurs.py ne fasse aucune référence à films.py et réciproquement.
Gestionnaire d'événements
On crée plutôt un gestionnaire d'événements dont le rôle est de centraliser les événements et les liens entre modules.
Le gestionnaire utilise une file d'attente.
# events.py
from files import File
class Events:
def __init__(self):
self.__file = File()
def push_event(self, trigger:str, data):
event = {"name":name, "data":data}
self.__file.enfiler(event)
def pop_event(self):
if self.__file.est_vide():
return None
return self.__file.defiler()
Maintenant, si acteurs.py a besoin de lancer la commande show_films_with_actor(id_actor, bdd, window), il se contente de faire :
events.push_event("films:list:actor", id_actor)
acteurs.py a notifié le gestionnaire d'événements. Il n'a pas à se préoccuper de se qui se passera ensuite, ce n'est pas son rôle.
Ainsi, acteurs.py a juste à connaître l'existence de l'objet créé par events.py. De la même façon, films.py sera dépendant de events.py. Les références sont décroisées.
Le routeur
On ajoute un objet routeur dont le rôle est de lire les événements et d'exécuter les demandes correspondantes.
Le routeur se charge de lire le prochain événement. L'événement est associé à une chaîne de caractères trigger qui indique ce qu'il faut faire. Le routeur aura ensuite une liste de routes, c'est à dire une liste de d'actions possibles. Le trigger devra correspondre à une action possible.
# routeur.py
class Routeur:
def __init__(self, events):
'''
events: gestionnaire d'événements de type Events
'''
self.__events = events
self.__routes = []
def add_route(self, trigger, action):
self.__routes.append({"trigger":trigger, "action":action})
def exec(self):
'''
exécute le prochain événement
'''
event = self.__events.pop_event()
if event == None:
return
trigger = event['trigger']
data = event['data']
for route in self.__routes:
if route["trigger"] == trigger:
# route trouvée
fct = route["action"]
fct(data)
return
Création des routes
Par exemple dans acteurs.py on définirait :
# acteurs.py
def show_list_acteurs(bdd, window):
# le code de la fonction...
def show_acteur(id_acteur, events, bdd, window):
# le code...
# il y aurait a un endroit un bouton pour afficher les films de cet acteur
events.push_event("films:list:actor", id_actor)
def initialisation_routes(events, bdd, window, routeur):
# ci-dessous exemple de la route pour afficher un acteur
routeur.add_route("show:actor", lambda data:show_acteur(data, events, bdd, window)
# toutes les routes utiles
Et dans le programme principal,
# main.py
# import du module
from tkinter import *
from routeur import Routeur
from events import Events
from bdd import BDD
import acteurs
# Construction de la fenêtre principale «root»
window = Tk()
window.title('Simple exemple') # titre de la fenêtre
# création des objets
bdd = BDD()
events = Events()
routeur = Routeur(events)
acteurs.initialisation_routes(events, bdd, window, routeur)
# ajout d'une fonction chargée de lire les événements
def refresh():
routeur.exec()
after(100, refresh) # rappelera refresh dans 100ms
after(100, refresh)
# fin du contenu
root.mainloop()
La boucle est lancée. Toutes les 100ms, refresh s'exécutera. Il s'agira d'exécuter le prochain événement dans la file.
