Combinando pattern compositi, decoratori e visitatori

3

Attualmente sto lavorando a un progetto in cui ho bisogno di manipolare una struttura dati ad albero, che ho implementato con un modello composito. Voglio essere in grado di fare diverse azioni su questa struttura dati, quindi ho implementato un modello di visitatore.

Stavo iniziando a vedere alcuni duplicati di codice tra i miei visitatori, quindi ho deciso di implementare un modello di decoratore sui miei visitatori. I decoratori implementano l'interfaccia Visitor e il loro costruttore accetta un visitatore come parametro.

L'idea è che il decoratore esegua del codice prima, esegue il suo visitatore di base (quello passato come parametro) ed esegue un codice dopo, un po 'così:

class ConcreteDecorator {
    decoratedVisitor: Visitor

    visitLeaf(leaf) {
        // Do some stuff before
        result := decoratedVisitor.visitLeaf(leaf)
        // Do some stuff after
        return result;
    }

    visitComposite(composite) {
        // Do some stuff before
        result := decoratedVisitor.visitComposite(leaf)
        // Do some stuff after
        return result;
    }
}

Il decoratore / materiale per i visitatori può essere configurato in questo modo:

visitor := new ConcreteVisitorA()
visitor := new ConcreteDecoratorA(visitor)
Visitor := new ConcreteDecoratorB(visitor)

Il problema è che, una volta che chiamo visitComposite su un decoratore, ad esempio, il decoratore fa la sua roba, quindi chiama la sua visita visitatoreComposite. Poiché visitComposite è un metodo ricorsivo, quando viene chiamato su ConcreteVisitor e il metodo fa le sue chiamate ricorsive, perdiamo i decoratori. I decoratori vengono applicati solo al primo nodo visitato.

Ho pensato a soluzioni diverse, come l'utilizzo dell'ereditarietà invece dei decoratori, ma voglio poter riutilizzare i decoratori su visitatori diversi, quindi non è un'opzione praticabile.

Ho anche pensato di usare un qualche tipo di schema di strategia. Le strategie avrebbero un metodo beforeVisitLeaf / afterVisitLeaf e beforeVisitComposite / afterVisitComposite. Il problema è che non posso implementare un decoratore di filtri, che interrompa la visita di determinati nodi.

Sono riuscito a fare una hack questa soluzione e farlo funzionare mantenendo un aggancio sui metodi dei visitatori secondari e sostituendo manualmente i metodi dell'istanza del visitatore secondario con i metodi decorator (posso farlo perché ... JavaScript).

Questa soluzione è super hacky, quindi mi chiedevo se avessi qualche idea di progettazione su come risolvere il problema.

    
posta Antoine Boisier-Michaud 06.12.2018 - 16:01
fonte

2 risposte

2

La correzione è rimuovere il composito.

Voglio dire, puoi certamente mantenerlo per la struttura, ma l'intero punto del composito è che le cose possono operare contro un'interfaccia composita ignorante, e se accade per essere un composto allora funziona contro più cose.

Poiché i visitatori conoscono le implementazioni di quell'interfaccia, non sono realmente compositi. Perderai un sacco di benefici e ti imbatterai in problemi come quello che hai qui.

Invece non lo tratterei come un composito. O avere una singola classe con i bambini a volte essere vuota (significa che non hai nemmeno bisogno di un visitatore), o trattare leaf e composito node come semplici vecchi dati. I visitatori vengono applicati a un nodo ma non si prevede che si applichino ai propri figli (ma forse è necessario esaminarli per prendere una decisione).

Effettivamente, adottare un approccio un po 'più funzionale in cui i dati e il comportamento sono distinti.

    
risposta data 07.12.2018 - 05:21
fonte
2

Dalla tua descrizione, ho la sensazione che il tuo codice sia simile a questo

class Composite 
{
    accept<T>(Visitor<T> visitor) {
        visitor.visitComposite(this)
    }
}

class ConcreteVisitor // inherits Visitor<T>
{
    visitComposite(composite) {
        // do some work
        for child in composite.children {
            child.accept(this)
        }
        // do some more work
    }
}

Se questo è il caso, la radice del tuo problema sta nel fatto che hai spostato una responsabilità della classe Composite (dal pattern Composite) nella funzione visitComposite del tuo visitatore.
Quando si chiama un metodo di una classe Composite del pattern Composite, è responsabilità della classe che inoltrare la chiamata ai nodi figli secondo necessità.

Se non vuoi legare il pattern Visitor a un particolare ordine trasversale di materiali compositi e figli del pattern Composite, puoi estendere le classi visitatore per avere due funzioni che dovrebbero essere chiamate dalla classe Composite, come questa

class Composite 
{
    accept<T>(Visitor<T> visitor) {
        visitor.visitCompositePreChildren(this)
        for child in children {
            child.accept(visitor)
        }
        visitor.visitCompositePostChildren(this)
    }
}

class ConcreteVisitor // inherits Visitor<T>
{
    visitCompositePreChildren(composite) {
        // do some work before the child nodes
    }
    visitCompositePostChildren(composite) {
        // do some work after the child nodes
    }
}

Questo rende anche il tuo modello decoratore sui visitatori possibile.

    
risposta data 07.12.2018 - 13:58
fonte

Leggi altre domande sui tag