Stabilità dei visitatori rispetto alla flessibilità dell'istanza

8

Sto lavorando su un'applicazione GUI che genera un file di configurazione. Ho una gerarchia di classi per il modello di configurazione e utilizzo un albero di oggetti di quella gerarchia in diversi contesti diversi. Attualmente, utilizzo il pattern Visitor per evitare di inquinare le mie classi del modello con codice specifico del contesto.

interface IConfigurationElement {
    void acceptVisitor(IConfigurationElementVisitor visitor);
}

In una versione precedente ho usato catene di condizioni instanceof invece di Visitor. Confrontando i due approcci vedo i seguenti scambi.

Visitor

  • È più facile e più sicuro aggiungere nuovo IConfigurationElement . Basta aggiungere una nuova dichiarazione a IConfigurationElementVisitor e il compilatore genera errori per tutte le implementazioni dei visitatori. Con instanceof chains devi ricordare tutti i posti che devi estendere con il nuovo elemento di configurazione. Fondamentalmente instanceof viola il principio DRY in quanto duplica la logica in diversi punti.
  • Il modello di visitatore è più efficiente di una catena di condizioni instanceof

instanceof

  • Il grande vantaggio di instanceof è la sua flessibilità. Ad esempio instanceof mi consente di definire soluzioni speciali per diversi sottoinsiemi di IConfigurationElement implementazioni che devono essere gestite in modo simile in alcuni casi. Al contrario, Visitor mi impone di implementare un metodo per ogni classe di implementazione ogni volta.

Esiste una soluzione comune per questo tipo di problema? Posso adattare il visitatore in qualche modo, quindi posso fornire una soluzione comune per alcuni casi?

    
posta Johannes Luong 30.01.2013 - 09:02
fonte

3 risposte

1

Potresti usare visitor con instanceOf

interfacce:

interface Visitable {
  void accept(Object visitor);
}
interface AVisitor {
  void visitA(A a);
}
interface BVisitor {
  void visitB(B b);
}
interface CVisitor {
  void visitB(C c);
}

visitabili:

class C implements Visitable {
  public void accept(Object visitor) {
    if (visitor instanceof CVisitor) {
      ((BVisitor)vistor).visitC(this);
    }
  }
}

class B implements Visitable {
  public void accept(Object visitor) {
    if (visitor instanceof BVisitor) {
      ((BVisitor)vistor).visitB(this);
    }
  }
}

class A extends B implements Visitable {
  public void accept(Object visitor) {
    super.accept(visitor);
    if (visitor instanceof AVisitor) {
      ((AVisitor)vistor).visitA(this);
    }
  }
}

Visitatori:

class PrintBs implements BVisitor {
  public void visitB(B b) {
    system.out.println(b);
  }
}

class PrintAs implements AVisitor {
  public void visitA(A a) {
    system.out.println(a);
  }
}

class PrintCs implements CVisitor {
  public void visitC(C c) {
    system.out.println(c);
  }
}
class PrintAsAndCs implements CVisitor, AVisitor{
  public void visitA(A a) {
    system.out.println(a);
  }
  public void visitC(C c) {
    system.out.println(c);
  }
}

ogni classe conosce solo le sue interfacce correlate, quindi aggiungere nuovi visitatori o visitabili richiede di cambiare tutto in quella categoria (visitatore / visitabile) (per visitatore, non richiede modifiche, per visitabile richiede la creazione di una nuova interfaccia visitatore, ma di nuovo, nessuna modifica di oggetti esistenti).

In questo modo, non esiste una catena di istanze di test e il visitatore di sottoinsieme non ha bisogno di sapere nemmeno di tipi al di fuori di questo sottoinsieme.

La domanda è cosa fare con la situazione in cui A estende B (e B è Visitabile anche), in tal caso, puoi semplicemente aggiungere super.accept (visitatore) in accept (quindi sarebbe una catena corta di instanceof-s, ma onlu finché la gerarchia è profonda, e non dovrebbe essere troppo profonda per importare, e non è necessario scriverla interamente manualmente).

    
risposta data 30.01.2013 - 09:14
fonte
0

Beh, sì, potresti. Cattura le caratteristiche comuni dei molti elementi di configurazione assegnando loro dei ruoli. Si può finire con instanceof, ma non in un modo che viola il principio di DRY, ma piuttosto come un modo per aggirare la tipizzazione statica di Java.

class ConfigurationElementA implements IConfigurationElement, ICommittable {
}

class ConfigurationElementB implements IConfigurationElement, IVerifiable {
}

class Visitor {
    void accept(IConfigurationElement element) {
        if (element instanceof ICommittable) {
            // ...
        }

        // Note: not a chain of instanceofs.

        if (element instanceof IVerifiable) {
            // ...
        }
    }
}

In altre parole, si consente al visitatore di accettare genericamente elementi di configurazione e di agire su gruppi di implementazioni tramite ruoli. Tieni presente che puoi andare specifico di cui hai bisogno modellando i tuoi elementi di configurazione di conseguenza.

Puoi riconoscere qui il pattern RoleInterface di Martin Fowler.

    
risposta data 04.02.2013 - 02:51
fonte
0

Posso pensare ad alcune soluzioni potenziali:

  1. crea un metodo privato nell'implementazione Visitor e disponi di più metodi visit nella chiamata di implementazione Visitor del metodo privato.

  2. se quanto sopra viene ripetuto in molti punti, puoi considerare la creazione di una classe astratta che implementa Visitor e reindirizza un sottoinsieme di implementazioni visit a un comune metodo protected abstract .

  3. crea più interfacce Visitor :

    interface AOrB extends IConfigurationElementVisitor {
        <Result> Result accept(AOrBVisitor<Result> visitor);
    
        interface AOrBVisitor<Result> {
            Result visit(A a);
            Result visit(B b);
        }
    }
    
    interface ThisCouldBeOfTypeB extends IConfigurationElementVisitor {
        <Result> Result accept(ThisCouldBeOfTypeBVisitor<Result> visitor);
    
        interface ThisCouldBeOfTypeBVisitor<Result> {
            Result visit(B b);
            Result visit(ThisCouldBeOfTypeB visitable);
        }
    }
    
    class A implements AOrB, ThisCouldBeOfTypeB {...}
    
    class B implements AOrB, ThisCouldBeOfTypeB {...}
    
    class C implements ThisCouldBeOfTypeB {...}
    

Penso che 3 sia quello che stai cercando. è un polimorfismo statico al 100% e genera avvisi del compilatore se un tipo non viene gestito. Ma l'unico svantaggio che posso pensare a 3 è che il mantenimento delle implementazioni Visitable* potrebbe diventare complesso se ci sono molte diverse interfacce Visitable* .

    
risposta data 20.03.2018 - 00:46
fonte

Leggi altre domande sui tag