270
« le: juillet 06, 2015, 05:03:03 pm »
Bonjour
Une dernière couche avant les vacances (toujours d'objet) pour les signaux. Les signaux sont très "polymorphes", c'est à dire qu'ils ont pas mal d'aspects et de comportements différents (lumineux, mécaniques, sémaphores, carrés, avertissements, …). Même façon d'écrire que pour les classes précédentes, une classe de base qui contient tout ce qui est commun, des classes dérivées pour les particularités. Sauf qu'ici on va regrouper des particularités, conduisant à de l'héritage d'héritage (pas de panique c'est tout naturel).
La première classe est la classe Signal :
typedef unsigned int typeFeux;
enum Feu {E=0,Vl=1,A=2,S=4,C=8,R=16,RR=32,M=64,Cv=128,D=256};
class Signal {
protected:
typeFeux feu=E; // 16 cas possibles (16 bits)
public:
virtual boolean ouvrir() { /* commande du signal */ return true; }
virtual boolean fermer() { /* commande du signal */ return true; }
boolean aubiner() { return fermer(); }
boolean ferme() { return feu&(S|C|Cv); }
boolean v30() { return feu&RR; } // vitesse 30
boolean r30() { return feu&A|R; } // ralentissement a 30
};
virtual typeFeux feux() { return E; }
virtual void maj() {}
virtual Signal* suivant() { return NULL; }
virtual Signal* precedent() { return NULL; }
virtual boolean occupe() { return false; } // occupation du canton
// methodes utilitaires
Signal* selon(Aiguille &a,Signal &s1,Signal &s2) { return a.directe()?&s1:&s2; }
boolean occupee(Zone &z) { return z.occupee(); }
boolean occupees(Zone &z1,Zone &z2) { return z1.occupee() || z2.occupee(); }
boolean occupees(Zone &z1,Zone &z2,Zone &z3) { return z1.occupee() || z2.occupee() || z3.occupee(); }
};
La classe Signal comporte une variable mémorisant l'état des feux, les valeurs des feux sont des puissances de 2 pour pouvoir faire des opérations ensemblistes (unions, intersections) facilement. Les fonctions ouvrir() et fermer() seront définies dans les classes dérivées. La fonction aubiner() ferme le signal (on pourrait éventuellement la redéfinir en cas de besoin). Les fonctions ferme(), v30() ou r30() sont des fonctions utilitaires ainsi que les fonctions occupee(s)() et la fonction selon(). Les fonctions fonctions feux() et maj() seront définies dans les classes dérivées, la fonction feux() "calcule" les feux à l'ouverture du signal, la fonction maj() est appelée par le signal suivant lors d'un changement de feux. La fonction utilitaire selon() retourne le signal suivant en fonction de la position de l'aiguille (facilité d'écriture). Les fonctions utilitaires occupee(s)() testent l'occupation du canton (utiles pour les cantons multi-zones notamment dans les gares).
Quelques classes correspondant à des signaux particuliers :
La classe Carré violet est la plus simple, pas d'interactions avec d'autre signaux. On peut aubiner le signal lors de l'occupation de la zone qui suit le signal. Cette classe peut être utilisée directement (sans faire de nouvelles classes). Pour ce type de carré on sait définir les méthodes ouvrir(), fermer() (feux() et maj() ne servent pas) :
class CarreViolet:public Signal {
boolean ouvrir() { feu=M; /* commande signal */ return true; }
boolean fermer() { feu=Cv; /* commande signal */ return true; }
};
Exemple de carré violet :
CarreViolet cv5;
La classe Carré (sans sémaphore) est plus intéressante car elle dépend d'autres signaux. On peut aubiner le signal lors de l'occupation de la zone qui suit le signal. Pour ce type de signal on sait définir les méthodes ouvrir(), fermer(), feux() et maj(), par contre les fonctions suivant() et precedent() dépendent d'un carré particulier (topologie du réseau), il faudra donc hériter de Carre pour un carré particulier. La classe Carre :
class Carre:public Signal {
boolean ouvrir() { feu=feux(); precedent()->maj(); /* commande signal */ return true; }
boolean fermer() { feu=C; precedent()->maj(); /* commande signal */ return true; }
typeFeux feux() { // feux a l'ouverture
if (suivant()->ferme()) return A; else return Vl;
}
void maj() { typeFeux f; // appele par le signal suivant
if (ferme()) return; // pas de propagation si pas de changement
f=feux(); if (f!=feu) { feu=f; precedent()->maj(); }
}
};
Exemple de carré particulier :
class C2:public Carre {
virtual Signal* suivant() { return selon(a2,s1,s3); } // selon a2
virtual Signal* precedent() { return &s1; }
};
le signal suivant dépends d'une aiguille.
La classe Sémaphore interagit avec d'autres signaux et avec des zones. Le sémaphore est fermé (aubiné) par l'occupation de la première zone du canton, lors de la libération de la dernière zone du canton ouvrir() est appelée pour mettre à jour les feux. La fonction occupe() teste l'occupation du canton (pour les cantons multi-zones). Pour ce type de signal on sait définir les méthodes ouvrir(), fermer(), feux() et maj(), par contre les fonctions suivant() et precedent() dépendent d'un sémaphore particulier, il faudra donc hériter de Semaphore pour un sémaphore particulier. La classe Semaphore :
class Semaphore:public Signal {
boolean ouvrir() { feu=feux(); precedent()->maj(); /* commande signal */ return true; }
boolean fermer() { feu=S; precedent()->maj(); /* commande signal */ return true; }
typeFeux feux() { // feux a l'ouverture
if (occupe()) return S; else // canton occupe ( precaution )
if (suivant()->ferme()) return A; else return Vl;
}
void maj() { typeFeux f; // appele par le signal suivant
if (ferme()) return; // pas de propagation si pas de changement
f=feux(); if (f!=feu) { feu=f; precedent()->maj(); }
}
};
Exemple de sémaphore particulier :
class S2:public Semaphore {
virtual Signal* suivant() { return &s2; }
virtual Signal* precedent() { return &s1; }
virtual boolean occupe() { return occupees(za,zb,zc); } // occupation du canton (3 zones)
};
Voila avec cette façon de programmer on peut prendre en compte toutes les configurations de signal possible, j'ai sur mon réseau des carrés avec manoeuvre ou rappel de ralentissement et en plus sémamaphore (pour les itinéraires permanents).
Je n'ai pas de classe Canton, mais cela serait possible.
Comme pour des zones et les itinéraires il manque les constructeurs (les destructeurs ne sont pas utiles ici), ce que je voudrai montrer ici c'est essentiellement l'aspect conceptuel de l'approche objet. Les classes ne sont pas exemptes d'erreurs, ni de mauvaises écritures (notamment Zone qui n'utilise pas les pointeurs où il faudrait). Bien qu'ayant enseigné, il y longtemps, le C++, j'ai beaucoup oublié (mais cela revient) car maintenant je fais du Java (je transcode ici du Java en C++), de plus je ne peux pas faire de tests, je peux juste compiler pour vérifier.
On pourrait imaginer de construire une bibliothèque de classes, mais cette bibliothèque ne pourrait pas être utilisée directement comme d'autres bibliothèques, il faudrait écrire des classes héritant des classes de la bibliothèque pour beaucoup de composants du réseau (zones, aiguilles, signaux, …), car ce sont ces classes qui définissent la topologie du réseau.
Bonnes vacances.
Pierre