Come modificare questa implementazione per coprire gli inconvenienti di Mediator Design Pattern qui

2

Sono nuovo per progettare modelli, ecco un classico esempio di modello di mediatore di base che ha 3 problemi con esso, prima di tutto guarda l'immagine dell'applicazione, il diagramma, il codice e la descrizione:

Utilizziamo DialogDirector per implementare la finestra di dialogo dei caratteri mostrata nella motivazione. La classe astratta DialogDirector definisce l'interfaccia per i director.

class DialogDirector {
public:
    virtual ~DialogDirector();

    virtual void ShowDialog();
    virtual void WidgetChanged(Widget*) = 0;

protected:
    DialogDirector();
    virtual void CreateWidgets() = 0;
};

Widget è la classe base astratta per i widget. Un widget conosce il suo direttore.

class Widget {
public:
    Widget(DialogDirector*);
    virtual void Changed();

    virtual void HandleMouse(MouseEvent& event);
    // ...
private:
    DialogDirector* _director;
};

Changed chiama l'operazione WidgetChanged del regista. I widget chiamano WidgetChanged sul loro direttore per informarlo di un evento significativo.

void Widget::Changed () {
    _director->WidgetChanged(this);
}

Sottoclassi di DialogDirector override WidgetChanged per influenzare i widget appropriati. Il widget passa un riferimento a se stesso come argomento a WidgetChanged per consentire al director di identificare il widget modificato. DialogDirector sottoclassi ridefiniscono il CreateWidgets puro virtuale per costruire i widget nella finestra di dialogo.

ListBox , EntryField e Button sono sottoclassi di Widget per elementi di interfaccia utente specializzati. ListBox fornisce un'operazione GetSelection per ottenere la selezione corrente e l'operazione EntryField SetText inserisce un nuovo testo nel campo.

class ListBox : public Widget {
public:
    ListBox(DialogDirector*);

    virtual const char* GetSelection();
    virtual void SetList(List<char*>* listItems);
    virtual void HandleMouse(MouseEvent& event);
    // ...
};

class EntryField : public Widget {
public:
    EntryField(DialogDirector*);

    virtual void SetText(const char* text);
    virtual const char* GetText();
    virtual void HandleMouse(MouseEvent& event);
    // ...
};

Button è un semplice widget che chiama Changed ogni volta che viene premuto. Questo viene fatto nella sua implementazione di HandleMouse :

class Button : public Widget {
public:
    Button(DialogDirector*);

    virtual void SetText(const char* text);
    virtual void HandleMouse(MouseEvent& event);
    // ...
};

void Button::HandleMouse (MouseEvent& event) {
    // ...
    Changed();
}

La classe FontDialogDirector media tra i widget nella finestra di dialogo. FontDialogDirector è una sottoclasse di DialogDirector :

class FontDialogDirector : public DialogDirector {
public:
    FontDialogDirector();
    virtual ~FontDialogDirector();
    virtual void WidgetChanged(Widget*);

protected:
    virtual void CreateWidgets();

private:
    Button* _ok;
    Button* _cancel;
    ListBox* _fontList;
    EntryField* _fontName;
};

FontDialogDirector tiene traccia dei widget visualizzati. Ridefinisce CreateWidgets per creare i widget e inizializzarne i riferimenti:

 void FontDialogDirector::CreateWidgets () {
    _ok = new Button(this);
    _cancel = new Button(this);
    _fontList = new ListBox(this);
    _fontName = new EntryField(this);

    // fill the listBox with the available font names

    // assemble the widgets in the dialog
}

WidgetChanged garantisce che i widget funzionino correttamente:

 void FontDialogDirector::WidgetChanged (
    Widget* theChangedWidget
) {
    if (theChangedWidget == _fontList) {
        _fontName->SetText(_fontList->GetSelection());

    } else if (theChangedWidget == _ok) {
        // apply font change and dismiss dialog
        // ...

    } else if (theChangedWidget == _cancel) {
        // dismiss dialog
    }
}

Quando voglio aggiungere un altro widget, devo affrontare 3 problemi:

  1. Devo aggiungere MyNewWidget class. (non è un problema)
  2. Devo modificare l'implementazione della classe FontDialogDirector e aggiungere MyNewWidget* _myNewWidget; .
  3. Aggiungi _myNewWidget= new MyNewWidget(this);
  4. Modifica l'implementazione del metodo WidgetChanged e aggiungi else if (theChangedWidget == _myNewWidget) //... ad essa.

C'è qualche soluzione che non ho nel codice, quando arriva un nuovo widget?

    
posta vaheeds 23.12.2016 - 18:09
fonte

2 risposte

1

Penso di capire cosa stai cercando, ma prima nota class FontDialogDirector è essenzialmente un contenitore per un elenco specifico di widget, in realtà è la definizione codificata di cui Widget contiene la tua finestra di dialogo, e è il luogo per definire la relativa "azione dei mediatori". Quindi, aggiungendo un nuovo Widget sempre richiede una modifica a FontDialogDirector , specialmente perché devi inserire il codice del mediatore da qualche parte. Non esiste un modo sensato per evitare completamente questa parte di "hard coding", almeno non con una soluzione che non modifichi radicalmente l'intera architettura del tuo esempio.

Tuttavia, ciò che puoi (e dovresti) evitare è un metodo come WidgetChanged che diventa sempre più lungo con ogni widget che aggiungi alla finestra di dialogo. Ci dovrebbe essere un solo luogo dove sono definiti i nuovi Widget - nel tuo caso il costruttore di 'FontDialogDirector. Secondo il principio di Open Closed, non dovrebbe essere necessario modificare un metodo del gestore di eventi esistente per altri widget se è necessario aggiungere un nuovo gestore di eventi per un nuovo widget.

L'idea generale è in effetti l'uso di eventi: Widget.changed() non dovrebbe chiamare un metodo specifico di DialogDirector , ma emettere un evento changed a cui un "utente" del Widget può iscriversi. In questo modo, non sarà necessario che il widget contenga un membro di tipo DialogDirector , solo un elenco di sottoscrittori.

In C ++ (che è quello che hai usato sopra), potresti dover implementare un meccanismo di sottoscrizione dell'editore per questo. Il classico pattern "Observer" è un modo possibile, oppure basta passare un puntatore alla funzione membro al Widget, utilizzare un oggetto comando per questo scopo oppure google per "c ++ publisher subscriber", che presenterà altre alternative. Nota che i framework C ++ GUI forniscono in genere un meccanismo standard per questo, come il meccanismo segnale / slot di Qt, che è quello che dovresti usare in un programma reale.

In questo modo, FontDialogDirector può fornire singoli metodi come

void FontDialogDirector::FontListChanged () {
    _fontName->SetText(_fontList->GetSelection());
}

void FontDialogDirector::OkButtonPressed () {
     // apply font change and dismiss dialog
}

e passa questi metodi al Widget. Se ciò accade come un argomento costruttore o da specifici metodi "AddEventHandler" non è importante, basta usare il meccanismo pub-sub che hai scelto sopra. Se supponiamo che il tuo elenco di "modifica dei sottoscrittori di eventi" sia memorizzato in una variabile membro _subscribers , il tuo metodo Widget::changed avrà quindi un aspetto approssimativo come questo:

void Widget::Changed () {
     for(auto p = _subscribers.begin(); p != _subscribers.end(); p++)
         (*p)->notifyChange();    
}

e notifyChange sarà direttamente essere mappato al metodo correlato di FontDialogDirector .

    
risposta data 27.12.2016 - 10:25
fonte
0

La risposta "corretta", come implica (sempre) l'eccellente consiglio di Robert Harvey, sarà specifica del problema che stai cercando di risolvere. I pattern non sono elementi costitutivi, ma alcuni pattern sono stati incorporati nei linguaggi e negli strumenti di programmazione. La maggior parte dei modelli funziona in modo approssimativo per limitazioni specifiche della lingua.

Il pattern Observer è uno. Se stavi usando C #, direi iscriviti agli eventi e disponi di un metodo dedicato per ogni evento del widget. È possibile creare una "finestra di dialogo generica" che non utilizza affatto un codice specifico per la finestra di dialogo, solo istanze popolate in modo dinamico guidate esclusivamente dai dati, come un elenco di widget da utilizzare e regole da utilizzare quando vengono generati eventi. Vuoi creare una cosa del genere? Probabilmente no. Passiamo attraverso i punti in cui desideri ridurre il codice:

  1. Classe MyNewWidget: in genere un set di componenti dell'interfaccia utente è già disponibile e può essere riutilizzato in tutte le finestre di dialogo.
  2. Campo aggiuntivo nel "director": tutti i riferimenti / i puntatori ai componenti possono essere memorizzati in una raccolta che si espande in modo dinamico.
  3. Istanziazione del widget: questo potrebbe essere guidato dai dati, in cui i dati specificano quali widget creare un'istanza e ulteriori proprietà per istanza.
  4. Codice eseguito quando viene generato un evento: in primo luogo è necessario qualcosa come un "evento". In C # è integrato. In altre lingue è possibile utilizzare un puntatore a funzione. In ogni caso, l'implementazione sottostante aggiungerà dinamicamente un puntatore a una funzione quando ti iscrivi ad un evento.

Ora come si evita di scrivere il codice widget-specfic quando viene sollevato l'evento? Questo è dove probabilmente dovresti continuare a scrivere codice. È possibile creare una "lingua" per il comportamento dei widget e sbarazzarsi di tutto il codice. Non ci hai fornito alcun motivo per suggerire questo.

Quindi hai un codice specifico per il widget. Come si "indirizza" un'istanza di widget in questo codice? Il modo più semplice è con un campo per widget. Altrimenti devi utilizzare un identificatore (stringa, chiave intera, ecc.) Per trovarlo nella raccolta dinamica.

Alla fine il modo più semplice per eseguire queste attività è generalmente la tecnica standard consigliata dai tuoi strumenti e dall'ambiente.

    
risposta data 25.12.2016 - 20:25
fonte