Maitrise d'Informatique
Module POD
Programmation Objet et Distribution
Université Pierre et Marie Curie
Année 2000--2001



Examen du 1er février 2001
Notes manuscrites et polycopiés autorisés - Durée 3 heures

Exercice 1 : Visiteurs

Le but de cet exercice est d'implanter le motif de conception (design pattern) ``visiteur''. Le <<Visiteur>> fait la représentation d'une opération applicable aux éléments d'une structure d'objet. Il permet de définir une nouvelle opération, sans qu'il soit nécessaire de modifier la classe des éléments sur lesquels elle agit.
On définit les classes JAVA suivantes pour les expressions arithmétiques et leurs traitements :
Eléments Visiteurs
abstract class  Element {
    abstract void accepteVisiteur(Visiteur v);
}

class Constante extends Element {
    int cte = 0;
    Constante(int n) {cte=n;}
    int getval() {return cte;}

    void  accepteVisiteur(Visiteur v) {
      v.visiteConstante(this);
    }  
}

class Binop extends Element {
    Element fg, fd;
    char oper;
    Binop (Element e1, char op, Element e2) 
      {fg=e1; oper=op; fd=e2;}
    char getop(){return oper;}
    Element fg(){return fg;}
    Element fd(){return fd;}

    void  accepteVisiteur(Visiteur v) {
      v.visiteBinop(this);
    }  
}
abstract class  Visiteur {
    abstract void visiteConstante(Constante c);
    abstract void visiteBinop(Binop bop);
}

class VisiteurAffiche extends Visiteur {
    void visiteConstante(Constante c) {
      System.out.print(c.getval());
    }
    void visiteBinop(Binop bop) {
       ...
    }
}

class VisiteurCalcule extends Visiteur {
    int result = 0;

    void visiteConstante(Constante c) {
      result=c.getval();
    }

   void visiteBinop(Binop bop) {
     ...
    }
    int getres() {return result;}
}

  1. Ecrire l'arbre des relations d'hétitage et d'agrégation de ces classes.

  2. Ecrire un programme principal qui construit l'expression <<(10*10) + (10*10)>>, l'affiche et calcule sa valeur en utilisant les visiteurs.
    Solution 1.2
    public class Exo1 {
        public static void main(String[]a) {
    
     Element e1 = new Constante (10);
            Element e2 = new Binop(e1,'*',e1);
            Element e3 = new Binop(e2,'+',e2);
    
            VisiteurAffiche v1 = new VisiteurAffiche();
            VisiteurCalcule v2 = new VisiteurCalcule();
    
            e3.accepteVisiteur(v1);
            System.out.print ("=");
            e3.accepteVisiteur(v2);
            System.out.println(v2.getres());
    
        }
    }
    


  3. Compléter les méthodes visiteBinop de la classe VisiteurAffiche et de la classe VisiteurCalcule.
    Solution 1.3
    class VisiteurAffiche extends Visiteur {
        void visiteConstante(Constante c) {
          System.out.print(c.getval());
        }
        void visiteBinop(Binop bop) {
            System.out.print("(");
            bop.fg().accepteVisiteur(this);
            System.out.print(bop.getop());
            bop.fd().accepteVisiteur(this);
            System.out.print(")");
        }
    }
    
    class VisiteurCalcule extends Visiteur {
        int result = 0;
    
        void visiteConstante(Constante c) {
     result=c.getval();
        }
    
       void visiteBinop(Binop bop) {
           bop.fg().accepteVisiteur(this);
           int rl = result;
           bop.fd().accepteVisiteur(this);
           int rd = result;
           char symb = bop.getop();
           switch (symb) {
           case ('*') : result = rl * rd; break;
           case ('+') : result = rl + rd; break;
           default : throw (new RuntimeException());
           }
        }
        int getres() {return result;}
    }
    


  4. Qu'affiche alors votre programme principal?
    Solution 1.4
    ((10*10)+(10*10))=200
    


  5. Ajouter à la hiérarchie des visiteurs un nouveau traitement qui calcule la profondeur d'un arbre d'expressions.
    Solution 1.5
    class VisiteurLongueur extends Visiteur {
        int result = 0;
    
        void visiteConstante(Constante c) {
          result = 0;
        }
        void visiteBinop(Binop bop) {
          bop.fg().accepteVisiteur(this);
          int rl = result;
          bop.fg().accepteVisiteur(this);
          int rd = result;
          if (rl > rd) { result = rl + 1;}
          else {result = rd + 1;}
        }
        int getres() {return result;}
    }
    
    public class Exo2 {
        public static void main(String[]a) {
    
            Element e1 = new Constante (10);
            Element e2 = new Binop(e1,'*',e1);
            Element e3 = new Binop(e2,'+',e2);
    
            VisiteurAffiche v1 = new VisiteurAffiche();
            VisiteurCalcule v2 = new VisiteurCalcule();
            VisiteurLongueur v3 = new VisiteurLongueur();
    
            e3.accepteVisiteur(v1);
            System.out.print ("=");
            e3.accepteVisiteur(v2);
            System.out.println(v2.getres());
            e3.accepteVisiteur(v3);
            System.out.println("profondeur max : "+v3.getres());        
    
        }
    }
    


  6. Indiquer les modifications à apporter aux visiteurs pour une nouvelle classe Variable sous-classe d'Element.
    Solution 1.6
    1. Ecrire la classe Variable
    2. Ajouter à la classe abstraite Visiteur une méthode abstaite void visiteVariableVariable v
    3. Ajouter à chaque sous-classe de Visiteur une méthode concrète void visiteVariableVariable v effectuant le travail correspondant.


  7. Quels sont les avantages et les inconvénients du motif Visiteur? Dans quels cas l'employerez-vous?
    Solution 1.7 C'est un motif de conception qui permet de différencier données et traitements. Il est à employer pour facilement étendre les traitements sur des données qui évolueront peu. Dans le cas contraire il peut devenir peu pratique à utiliser.

Exercice 2 : Synchronisation en RMI

Le but de cet exercice est d'implanter une synchronisation sur des objets distants. On l'illustrera cette synchronisation par un tableau d'objets.

interface Valeur extends Serializable {
  String toString();
  boolean inf(Valeur v);

}
class Bad_access extends Exception {};
class Bad_value extends Exception {};

interface Vecteur {
  public Valeur get(int i) throws Bad_access, Bad_value;
  public void set(int i, Valeur val) throws Bad_access;
  public int length();
}
  1. Définir l'interface VecteurDistant qui étend les interfaces Remote et Vecteur.
    Solution 2.1
    import java.io.*;
    import java.rmi.*;
    
    
    interface Valeur extends Serializable {
      public String toString();
      public boolean inf(Valeur v);
    
    }
    class Bad_access extends Exception {};
    class Bad_value extends Exception {};
    
    public interface VecteurDistant extends Remote {
    
      public Valeur get(int i) throws Bad_access, Bad_value, RemoteException;
    
      public void set(int i, Valeur val) throws Bad_access, RemoteException;
    
      public int length() throws RemoteException;
    }
    


  2. Ecrire une classe VecteurD implantant l'interface VecteurDistant.
    Solution 2.2
    
      import java.rmi.*;
      import java.rmi.server.UnicastRemoteObject;
    
    public class VecteurD extends UnicastRemoteObject
                        implements VecteurDistant {
        Valeur [] v = null;
        VecteurD(int n) throws RemoteException {v=new Valeur[n];}
        
        public Valeur get(int i) throws Bad_access, Bad_value, RemoteException {
          if ((i<0)|| (i > v.length)) {throw (new Bad_access());}
            else { if (v[i] == null) {throw (new Bad_value());}
            else return (v[i]);} 
        }
    
        public void set(int i, Valeur val) throws Bad_access, RemoteException {
          if ((i<0)|| (i > v.length)) {throw (new Bad_access());}
              else {v[i]=val;}
        }
    
        public int length() {
            return v.length; 
        }    
         
    }
    


  3. Ecrire un serveur RMI qui enregistre sous le nom monvect une instance de cette classe de taille 40 et où chaque élément du vecteur contient la valeur null.
    Solution 2.3
      import java.rmi.*;
      import java.rmi.server.UnicastRemoteObject;
    
      public class ServeurVecteur {
    
        public static void main (String args[]) {
          System.setSecurityManager(new RMISecurityManager());
          try {
            VecteurD v = new VecteurD(40);
            Naming.rebind("//ippc56.infop6.jussieu.fr/monvect",v);
          }
          catch (Exception e) { e.printStackTrace();
          }
        }
      }
    


  4. Ecrire un client qui affiche le contenu du vecteur distant monvect.
    Solution 2.4
    import java.rmi.*;
    public class ClientAffiche {
      public static void main( String argv[]) {
        String url0="rmi://ippc56.infop6.jussieu.fr/monvect";
         try {
           VecteurDistant v = (VecteurDistant)Naming.lookup(url0);
    
           int i = v.length();
           for (int j=0;j<i;j++) {
             System.out.print(v.get(j)+" ");
           }
         }
         catch (Exception e) {
           System.err.println("exception : " +
                             e.getMessage());
           e.printStackTrace();
         }
      }
    }
    


  5. Ecrire un client qui trie le vecteur distant monvect.
    Solution 2.5
    import java.rmi.*;
    public class ClientTrie {
      public static void main( String argv[]) {
        String url0="rmi://ippc56.infop6.jussieu.fr/monvect";
         try {
           VecteurDistant v = (VecteurDistant)Naming.lookup(url0);
    
           int i = v.length();
           for (int j=0;j<i;j++) {
               for (int k=0; k<j; k++) {
                   if (v.get(k).inf(v.get(j))) {
                       Valeur tmp = v.get(k);
                       v.set(k,v.get(j));
                       v.set(j,tmp);
                   }
               }
           }
         }
         catch (Exception e) {
           System.err.println("exception : " +
                             e.getMessage());
           e.printStackTrace();
         }
      }
    }
    


  6. Que risque-t-il de se passer si les deux clients effectuent leur travail silmutanément?

  7. Que faut-il ajouter à ce programme pour gérer l'accès concurrent à ce vecteur distant?

  8. Implanter votre proposition.

Exercice 3 : Gestion d'une liste de clients

Le but de cet exercice est d'implanter la gestion d'une liste de clients dans un serveur O'Caml.

Pour simplifier les entrées/sorties, on suppose connues les fonctions d'écriture d'une ligne et de lecture d'une ligne sur un descripteur de fichier de types suivants :
val write_line : Unix.file_descr -> string -> unit
val read_line : Unix.file_descr -> string
On se donne le squelette du serveur en O'Caml (la fonction List.nth donne le ième élément d'une liste) :
class  connexion n = 
  object (self : 'a ) 
    val nmax = n
    val mutable jeton = 0;
    val mutable fini = false;
    val mutable liste_soc = ([] : (Unix.file_descr * Unix.sockaddr) list)

    method ajoute (sd,sa) =  
      liste_soc <- liste_soc @ [sd,sa];
      write_line sd "ATTENDRE"
    method get_sd i = fst(List.nth  liste_soc i)
    method broadcast msg = 
      for i = 0 to nmax-1 do 
        write_line (self#get_sd i) msg
      done
    method start() =
      self#broadcast("DEBUT");
      while not fini do
        write_line (self#get_sd jeton) "TONTOUR";
        let s = read_line (self#get_sd jeton) in 
          self#run(s);
          jeton <- (jeton + 1) mod nmax
      done;
      self#close()
    method run s = self#broadcast s
    method close() = ()  
  end;;
(***********************************************************************)
class  serv_socket p  nmax = 
  object (self)
    val port = p 
    val mutable sock = ThreadUnix.socket Unix.PF_INET Unix.SOCK_STREAM 0
    initializer 
      let mon_adresse = 
        (Unix.gethostbyname(Unix.gethostname())).Unix.h_addr_list.(0) 
      in Unix.bind sock (Unix.ADDR_INET(mon_adresse,port)) ;
         Unix.listen sock nmax
    method private client_addr = function 
        Unix.ADDR_INET(host,_) -> Unix.string_of_inet_addr host
      | _ -> "Unexpected client"
    method run () =
     while true do  
       let connexion = new connexion nmax in 
       for i=0 to nmax-1 do 
         let (sd,sa) = ThreadUnix.accept sock in 
           connexion#ajoute(sd,sa)
       done; 
       connexion#start()
     done 
  end ;;
partie O'Caml

  1. Que fait le programme suivant :
    let s = new serv_socket 3124 5;;
    s#run();;
    
  2. Ecrire le corps de la méthode close pour terminer les connexions avec les clients.
  3. Sur ce modèle on veut implanter un serveur de jeu de C+/C- où les clients cherchent la valeur d'un nombre caché sur le serveur (celui-ci répond "C+", "C-" ou "C=" selon le nombre proposé). Implanter ce jeu en modifiant le squelette de la classe connexion.

  4. Indiquer comment faire en O'Caml pour ne pas avoir à écrire la classe connexion avant la classe serv_socket.
partie Java
Ce document a été traduit de LATEX par HEVEA.