Newsgroup: it.comp.java
Titolo: Problemino polimorfismo - soluzione + elegante?

Vorrei trovare la soluzione più elegante per questo problema: Moracinese
. Supponiamo di avere una interfaccia Moracinese che contiene il solo
metodo "boolean batte(Moracinese m)".
Questo metodo serve a stabilire se la classe che implementa
l'interfaccia è "vince" con quella che viene passata come parametro.
A questo punto l'interfaccia Moracinese può essere implementata da una o
più classi. Ad esempio la classe Sasso e la classe Forbici, che devono
implementare il metodo " boolean batte(Moracinese m)" descritto in
precedenza.
Quando si realizza la classe Sasso è noto che questa batterà la classe
Forbici, quindi il metodo per le 2 classi andrà implementato di conseguenza.
Quale è la soluzione più elegante per risolvere il problema?
Supponiamo inoltre che possano venir aggiunte in seguito nuove
implementazioni dell'interfaccia. In pratica sarà noto solo quando viene
implementata una nuova classe, quale delle altri classi sarà migliore di
questa e viceversa. 
Come poter risolvere in modo efficiente/elegante il problema?
Grazie
Inviato da 
www.mynewsgate.net

Qui si potrebbe implementare il Pattern Double Dispatch
http://c2.com/cgi/wiki?DoubleDispatchExample
(Un esempio di Bruno Bossola:
http://www.jugtorino.it/newwiki/jsp/Wiki?EsperimentoRefactoringEstateZeroQuattro
)
che e' un altro modo di vedere il Pattern Visitor.
Io farei cosi': innanzitutto un bel Test JUnit (ho imparato bene Bruno? ;-)
__________
import junit.framework.TestCase;
public class MoraCineseTest extends TestCase {
 Sasso sasso = new Sasso();
 Carta carta = new Carta();
 Forbici forbici = new Forbici();
  public MoraCineseTest(String arg0) {
    super(arg0);
  }
 public void testSasso() {
  assertTrue(sasso.batte(forbici));
  assertFalse(sasso.batte(sasso));
  assertFalse(sasso.batte(carta));
 }
 public void testForbici() {
  assertFalse(forbici.batte(forbici));
  assertFalse(forbici.batte(sasso));
  assertTrue(forbici.batte(carta));
 }
 public void testCarta() {
  assertFalse(carta.batte(forbici));
  assertTrue(carta.batte(sasso));
  assertFalse(carta.batte(carta));
 }
}
________
Poi aggiungerei un metodo all'interfaccia MoraCinese
public interface MoraCinese {
 public boolean batte(MoraCinese avversario);
 public MoraCineseComparator getComparator();
}
____________
Quindi creerei l'interfaccia MoraCineseComparator.
public interface MoraCineseComparator {
 public boolean battutoDa(Sasso avversario);
 public boolean battutoDa(Carta avversario);
 public boolean battutoDa(Forbici avversario);
}
______
A questo punto le implementazioni delle classi
Sasso, Carta, Forbici:
public class Sasso implements MoraCinese {
  public boolean batte(MoraCinese avversario) {
    return avversario.getComparator().battutoDa(this);
  }
  public MoraCineseComparator getComparator() {
    return new MoraCineseComparator() {
      public boolean battutoDa(Sasso avversario) {
        return false;
      }
      public boolean battutoDa(Carta avversario) {
        return true;
      }
      public boolean battutoDa(Forbici avversario) {
        return false;
      }
    };
  }
}
public class Forbici implements MoraCinese {
  public boolean batte(MoraCinese avversario) {
    return avversario.getComparator().battutoDa(this);
  }
  public MoraCineseComparator getComparator() {
    return new MoraCineseComparator() {
      public boolean battutoDa(Sasso avversario) {
        return true;
      }
      public boolean battutoDa(Carta avversario) {
        return false;
      }
      public boolean battutoDa(Forbici avversario) {
        return false;
      }
    };
  }
}
public class Carta implements MoraCinese {
  public boolean batte(MoraCinese avversario) {
    return avversario.getComparator().battutoDa(this);
  }
  public MoraCineseComparator getComparator() {
    return new MoraCineseComparator(){
      public boolean battutoDa(Sasso avversario) {
        return false;
      }
      public boolean battutoDa(Carta avversario) {
        return false;
      }
      public boolean battutoDa(Forbici avversario) {
        return true;
      }
    };
  }
}
_____
Se dovessi creare una nuova classe che implementa MoraCinese,
il compilatore ti mostrerebbe i punti da modificare.
Spero di non aver scritto troppe stupidate.
Per favore fammi sapere cosa ne pensi.
Ciao
Franco

> Io farei cosi': ...
Intanto grazie per la risposta molto interessante.
L'unico dubbio che mi è venuto guardando il tuo codice è che le classi
sono dipendenti da ogni modifica o aggiunta di classi.
Cioè se voglio aggiungere una nuova classe Albero che implementa
l'interfaccia Moracinese e che ad esempio vince con Sasso e Carta, sono
cmq costretto a modificare anche l'interfaccia MoraCineseComparator,
aggiungendo un nuovo metodo che dovrà poi essere implementato anche dalle
altre classi Sasso, Carta, Forbici create in precedenza...le classi sono
accoppiate tra loro e non dovrebbe essere cosi...o sbaglio?
E' ovvio che da qualche parte deve esistere un punto in cui si possa
leggere chi batte qualcun altro...pero' questo rende poco "riutilizzabile"
la cosa...
Non che voglia emttere in discussione la tua soluzione, veramente
interessante ;)
grazie ancora
Inviato da 
www.mynewsgate.net

 accoppiate tra loro e non dovrebbe essere cosi...o sbaglio?
E' vero, ma mi sembra che sia la natura del problema a richiederlo.
Ciao
Franco

 Franco
sono d'accordo, ti faccio cmq un esempio banale.
Se invece dell'interfaccia MoraCineseComparator si utilizzasse una classe
MoraCineseClass con all'interno una serie di metodi statici simili a quelli
dell'interfaccia MoraCineseComparator.
Ad esempio:
public static class MoraCineseComparator{
 
  public static boolean battutoDa(Sasso io, Forbici avversario){
    return true;
  }
  public static boolean battutoDa(Sasso io, Carta avversario){
    return false;
  }
 
  public static boolean battutoDa(Forbici io, Sasso avversario){
    return false;
  }
 
  public static boolean battutoDa(Forbici io, Carta avversario){
    return true;
  }
  ...
  ...
}
questi metodi possono essere aggiornati/aggiunti facilmente senza toccare le
classi Sasso, Forbici e Carta già esistenti.
Quindi se devo inserire una nuova classe Albero che vince con tutti, basta
inserire i nuovi metodi
  public static boolean battutoDa(Forbici io, Albero avversario){
    return false;
  }
  public static boolean battutoDa(Sasso io, Albero avversario){
    return false;
  }
  public static boolean battutoDa(Albero io, Sasso avversario){
    return true;
  }
  ...
  ...
il metodo batte(...) da implementare in ogni classe (Sasso, Carta, Forbici,
Albero) sarà
  public boolean batte(MoraCinese avversario) {
    return MoraCineseComparator.battutoDa(this, avversario);
  }
non andrebbe bene?
diciamo che mi sono accorto adesso che ho finito di scrivere che cosi facendo
sarebbe meglio usare una classe da estendere, in quanto il metodo scritto qui
sopra è uguale per tutte le sottoclassi...
cmq grazie ;)
Inviato da 
www.mynewsgate.net

   }
Il problema e' che questa implementazione, a causa del binding statico dei
metodi di Java, non viene compilata: prova e vedrai. E' per questo problema
che viene utilizzato il double dispatch pattern.
Per aggirare il problema potresti:
1) dichiarare un metodo in MoraCineseComparator
  public static boolean battutoDa(Forbici io, MoraCinese avversario){
   if (avversario instanceOf Sasso)
       ...
   else if (avversario instanceOf Carta)
       ....
  }
2) Implementare il metodo batte nelle classi Sasso, Carta ecc. come
 public boolean batte(MoraCinese avversario) {
  if (avversario instanceOf Sasso)
    return MoraCineseComparatorStatic.battutoDa(this, (Sasso)avversario);
  if (avversario instanceOf Carta)
    return MoraCineseComparatorStatic.battutoDa(this, (Carta)avversario);
 }
Come vedi in entrambe le soluzioni, crei delle lunghe liste di clausole
  if (avversario instanceOf Sasso)
Ciao
Franco