-
Cours :
- Notions programmation orientée objet et Java (pdf)
- Tests (pdf)
- Principes SOLID (pdf)
- Patrons de conception (pdf)
-
Planches de TD :
- TD 1 (pdf), Corrigé TD 1 (pdf)
- TD 2 (pdf), Corrigé TD 2 (pdf)
- TP 1 (pdf), Corrigé TP 1 (pdf)
Gestionnaire de processus
Questions
Question 1 : Il s’avère que d’autres types de processus peuvent exister (par exemple des processus avec différents niveaux de priorités). Selon cette constatation précise, quel principe SOLID a été violé et pourquoi ?
La classe ProcessManager
dépend de la classe concrète
Process
et donc si on souhaite changer de classe pour les
processus, on est obligé de changer la classe
ProcessManager
. Selon le principe DIP, il faut dépendre
d’abstractions, pas d’implémentations.
Question 2 : Par ailleurs, le gestionnaire de processus pourrait avoir une autre façon de sélectionner les prochains processus à exécuter et une autre façon d’exécuter un processus pendant un quantum de temps. Selon cette constatation précise, quel principe SOLID a été violé et pourquoi ?
Selon cette remarque, la classe ProcessManager
a deux
raisons de changer correspondant à l’implémentation des méthodes
selectNext
et exec
qui peuvent changer de
manière indépendante (pas de lien entre les changements). La classe
ProcessManager
a donc deux responsabilités et ne respecte
pas SRP.
Question 3 : Si on veut proposer de nouveaux états
pour les processus, il faut changer le contenu la classe
Process
. Selon cette constatation précise, quel
principe SOLID a été violé et pourquoi ?
Selon cette remarque, on est obligé de changer le code interne de la
classe Process
sans possibilité de passer par une
extension, car la gestion des états est complétement interne à la classe
Process
sans passer par d’autres classes. On viole donc
OCP.
Question 4 : On souhaite rajouter des processus qui
ne sont jamais censés s’arrêter (les processus “démons”). Or, la classe
Process
possède une méthode int getTimeLeft()
.
Selon cette constatation précise, quel principe SOLID a été violé et
pourquoi ?
Si on regarde l’utilisation de la méthode getTimeLeft
dans les classes autres que Process
, on s’aperçoit qu’elle
n’est utilisé dans ProcessManager
que pour savoir si le
processus est fini ou pas. Par conséquent, on aurait pu définir à la
place une méthode boolean isOver()
qui indique si le
processus est terminé. Dans ce cas, il n’y aura pas de problème pour
définir des processus “démons” qui implémenteraient cette nouvelle
méthode. On peut donc dire qu’on viole ISP dans le sens où la classe
client ProcessManager
dépend de getTimeLeft
alors qu’elle n’a pas vraiment besoin de cette fonctionnalité, mais
d’une fonctionnalité plus restreinte.
Question 5 : Pour rajouter les processus démons
de la question précédente, on vous demande de rajouter des classes
AbstractProcess
, Daemon
et
FiniteProcess
de telle sorte à ce que Daemon
et FiniteProcess
étendent
AbstractProcess
. L’objectif est que
FiniteProcess
remplace la classe Process
et
que Daemon
permette de créer des processus démons. Pour
ceci AbstractProcess
contiendra une méthode abstraite
boolean isOver()
qui indique si le processus est terminé.
La classe AbstractProcess
ne devra pas avoir de méthode
getTimeLeft
.
Les classes AbstractProcess
, Daemon
et
FiniteProcess
ont le code suivant :
public abstract class AbstractProcess {
enum State { READY, RUNNING, BLOCKED }
private final String name;
private State state = State.READY;
private int lastExecDate = 0;
public AbstractProcess(String name) {
this.name = name;
}
public void execQuantum(int date) {
System.out.println(date + ":" + name);
= date;
lastExecDate }
public void handleSignal(Signal signal) {
switch (signal) {
case BLOCK -> {
if (state == State.RUNNING) {
System.out.println(name + " blocked");
= State.BLOCKED;
state }
}
case UNBLOCK -> {
if (state == State.BLOCKED) {
System.out.println(name + " unblocked");
= State.READY;
state }
}
case SLEEP -> {
if (state == State.RUNNING)
= State.READY;
state }
case AWAKE -> {
if (state == State.READY)
= State.RUNNING;
state }
}
}
public boolean isReady() {
return state == State.READY;
}
public boolean isRunning() {
return state == State.RUNNING;
}
public int getLastExecDate() {
return lastExecDate;
}
public String getName() {
return name;
}
public abstract boolean isOver();
}
public class Daemon extends AbstractProcess {
public Daemon(String name) {
super(name);
}
@Override
public boolean isOver() {
return false;
}
}
public class FiniteProcess extends AbstractProcess {
private int timeLeft;
public FiniteProcess(String name, int duration) {
super(name);
this.timeLeft = duration;
}
@Override
public boolean isOver() {
return timeLeft <= 0;
}
@Override
public void execQuantum(int date){
super.execQuantum(date);
--;
timeLeft}
}
Question 6 : Réécrire la classe
ProcessManager
pour qu’elle délègue la sélection de
processus à une instance d’une classe de l’interface
ProcessSelector
contenant l’unique méthode
Process selectNext(List<Process> processes)
(cf. le
diagramme ci-dessous).
public class ProcessManager {
private List<AbstractProcess> processes = new ArrayList<>();
private int date = 0;
private AbstractProcess running = null;
private ProcessSelector selector;
public ProcessManager(ProcessSelector selector) {
this.selector = selector;
}
public void add(AbstractProcess process) {
.add(process);
processesSystem.out.println(process.getName() + " new");
}
public void exec(int nbQuantum) {
for(int i = 0; i < nbQuantum ; i++) {
= selector.select(processes);
AbstractProcess candidate switchRunningProcess(candidate);
execRunningProcessQuantum();
++;
date}
}
private void switchRunningProcess(AbstractProcess candidate) {
if (candidate == null) return;
if (running != null) running.handleSignal(Signal.SLEEP);
.handleSignal(Signal.AWAKE);
candidate= candidate;
running
}
private boolean canExecRunningProcess() {
return running != null && running.isRunning();
}
private void execRunningProcessQuantum () {
if (!canExecRunningProcess())
return;
.execQuantum(date);
runningif (running.isOver()) {
.remove(running);
processes= null;
running }
}
}
public interface ProcessSelector {
select(List<AbstractProcess> processes);
AbstractProcess }
public class RoundRobinSelector implements ProcessSelector{
@Override
public AbstractProcess select(List<AbstractProcess> processes) {
= null;
AbstractProcess candidate int lastDate = Integer.MAX_VALUE;
for(AbstractProcess process : processes) {
int lastExecDate = process.getLastExecDate();
if(process.isReady() && lastExecDate < lastDate) {
= process;
candidate = lastExecDate;
lastDate }
}
return candidate;
}
}
Question 7 : Pour le moment, les processus définis ne peuvent être que dans trois états : ready, running ou blocked. Comme un processus peut habituellement être dans plus d’états (il existe neuf états de processus sous Unix), il faut prévoir l’ajout (ou le retrait) d’autres états dans des versions ultérieures du programme. Vous allez utiliser le patron de conception State pour définir les états possibles d’un processus ainsi que ses transitions. Dessinez le diagramme de classe correspondant à cette modification.
Question 8 : Écrivez l’interface
ProcessState
ainsi que les classes qui l’implémentent et
décrivez les modifications à faire sur la classe
AbstractProcess
de manière que le comportement du programme
soit le même qu’auparavant.
L’interface ProcessState
est la suivante :
public interface ProcessState {
void handleSignal(Signal signal, AbstractProcess process);
boolean isRunning();
boolean isReady();
}
La classe AbstractProcess
doit être modifiée comme
suivant :
public abstract class AbstractProcess {
private ProcessState state;
public AbstractProcess(String name) {
this.name = name;
= new ReadyState();
state }
public void handleSignal(Signal signal) {
.handleSignal(signal, this);
state}
public boolean isReady() {
return state.isReady();
}
public boolean isRunning() {
return state.isRunning();
}
// code précédent
}
Les classes pour les états sont les suivantes :
public class BlockedState implements ProcessState{
@Override
public void handleSignal(Signal signal, AbstractProcess process) {
if (signal == Signal.UNBLOCK) {
System.out.println(process.getName() + " unblocked");
.setState(new ReadyState());
process}
}
@Override
public boolean isRunning() {
return false;
}
@Override
public boolean isReady() {
return false;
}
}
public class ReadyState implements ProcessState {
@Override
public void handleSignal(Signal signal, AbstractProcess process) {
if(signal == Signal.AWAKE)
.setState(new RunningState());
process}
@Override
public boolean isRunning() {
return false;
}
@Override
public boolean isReady() {
return true;
}
}
public class RunningState implements ProcessState{
@Override
public void handleSignal(Signal signal, AbstractProcess process) {
if(signal == Signal.BLOCK) {
System.out.println(process.getName() + " blocked");
.setState(new BlockedState());
process}
else if(signal == Signal.SLEEP)
.setState(new ReadyState());
process}
@Override
public boolean isRunning() {
return true;
}
@Override
public boolean isReady() {
return false;
}
}
Question 9 : On souhaite rajouter de nouveaux processus avec différents niveaux de priorités allant de 1 à 10 et d’utiliser les priorités pour la sélection du processus à exécuter (par exemple en ne sélectionnant le prochain processus à exécuter uniquement parmi ceux de priorité maximale qui peuvent s’exécuter). Décrivez à l’aide d’un diagramme de classe la manière de réorganiser le code pour prendre compte cette modification.
Pour faire ce changement, le plus simple est d’utiliser le patron de
conception stratégie pour la gestion de la méthode isOver
puis de créer une classe PriorityProcess
étendant
Process
.
Ensuite on peut paramétrer la classe ProcessManager
avec
le type des processus :
public class ProcessManager<P extends Process> {
private final List<P> processes = new ArrayList<>();
private int date = 0;
private P running = null;
<P> processSelector;
ProcessSelector
public ProcessManager(ProcessSelector<P> processSelector) {
this.processSelector = processSelector;
}
public void add(P process) {
.add(process);
processesSystem.out.println(process.getName() + " new");
}
private P selectNext() {
return processSelector.selectNext(processes);
}
public void execQuantum(int nbQuantum) {
for(int i = 0; i < nbQuantum ; i++) {
= selectNext();
P candidate switchRunningProcess(candidate);
execRunningProcessQuantum();
++;
date}
}
private void switchRunningProcess(P candidate) {
if (candidate == null) return;
if (running != null) running.handleSignal(Signal.SLEEP);
.handleSignal(Signal.AWAKE);
candidate= candidate;
running
}
private boolean canExecRunningProcess() {
return running != null && running.isRunning();
}
private void execRunningProcessQuantum () {
if (!canExecRunningProcess())
return;
.execQuantum(date);
runningif (running.isOver()) {
.remove(running);
processes= null;
running }
}
}
On paramètre l’interface ProcessSelector
avec un type
P
correspondant à un type qui est une extension de
Process
.
public interface ProcessSelector<P extends Process> {
selectNext(List<? extends P> processes);
P }
Ensuite, on peut créer des sélecteurs spécifiques qui fonctionne sur
les Process
ou les PriorityProcess
.
public class RoundRobinSelector implements ProcessSelector<Process> {
@Override
public Process selectNext(List<? extends Process> processes) {
Process candidate = null;
int lastDate = Integer.MAX_VALUE;
for(Process process : processes)
if(process.isReady() && process.getLastExecDate() < lastDate) {
= process;
candidate = process.getLastExecDate();
lastDate }
return candidate;
}
}
public class PrioritySelector implements ProcessSelector<PriorityProcess>{
public PriorityProcess selectNext(List<? extends PriorityProcess> processes) {
= null;
PriorityProcess candidate int maxPriority = -1;
for(PriorityProcess process : processes) {
int priority = process.getPriority();
if(process.isReady() && priority > maxPriority) {
= process;
candidate = priority;
maxPriority }
}
return candidate;
}
}
Finalement, il faut adapter le code de
TestProcessManager
.
public class TestProcessManager {
public static void main(String[] args) {
= new PriorityProcess("P1", 0, new DaemonTimeManager());
PriorityProcess p1 = new PriorityProcess("P2", 5, new DaemonTimeManager());
PriorityProcess p2 = new PriorityProcess("P3", 10, new DaemonTimeManager());
PriorityProcess p3 <PriorityProcess> processManager = new ProcessManager<>(new PrioritySelector());
ProcessManager.add(p1);
processManager.add(p2);
processManager.execQuantum(10);
processManager.add(p2);
processManager.execQuantum(2);
processManager.add(p3);
processManager.handleSignal(Signal.BLOCK);
p1.execQuantum(3);
processManager.handleSignal(Signal.UNBLOCK);
p1.execQuantum(10);
processManager}
}