Question

I am new to design patterns, here is a classic example of basic mediator pattern that has 3 problems with it, first of all take a look at the application image, diagram, code and description:

Font Chooser Dialog

Class Diagram of the example

We use a DialogDirector to implement the font dialog box shown in the Motivation. The abstract class DialogDirector defines the interface for directors.

class DialogDirector {
public:
    virtual ~DialogDirector();

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

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

Widget is the abstract base class for widgets. A widget knows its director.

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

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

Changed calls the director's WidgetChanged operation. Widgets call WidgetChanged on their director to inform it of a significant event.

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

Subclasses of DialogDirector override WidgetChanged to affect the appropriate widgets. The widget passes a reference to itself as an argument to WidgetChanged to let the director identify the widget that changed. DialogDirector subclasses redefine the CreateWidgets pure virtual to construct the widgets in the dialog.

The ListBox, EntryField, and Button are subclasses of Widget for specialized user interface elements. ListBox provides a GetSelection operation to get the current selection, and EntryField's SetText operation puts new text into the field.

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 is a simple widget that calls Changed whenever it's pressed. This gets done in its implementation of 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();
}

The FontDialogDirector class mediates between widgets in the dialog box. FontDialogDirector is a subclass of 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 keeps track of the widgets it displays. It redefines CreateWidgets to create the widgets and initialize its references to them:

 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 ensures that the widgets work together properly:

 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
    }
}

When I want to add one more widget, I have to deal with 3 problems:

  1. I need to add MyNewWidget class. (it's not a problem)
  2. I need to change the FontDialogDirector class implementation and add MyNewWidget* _myNewWidget;.
  3. Add _myNewWidget= new MyNewWidget(this);
  4. Change the WidgetChanged method implementation, and add else if (theChangedWidget == _myNewWidget) //... to it.

Is there any solution that I don't have to hard code, when a new widget arrives?

Was it helpful?

Solution

I think I understand what you are after, but first note class FontDialogDirector is essentially a container for a specific list of Widgets, it is actually the coded definition of which Widgets your dialog contains, and it is the place for defining the related "Mediator action". So adding a new Widget always requires a change to FontDialogDirector, especially because you need to put the mediator code somewhere. There is no sensible way to avoid this part of "hard coding" completely, at least not by a solution which does not change the whole architecture of your example radically.

However, what you can (and should) avoid is a method like WidgetChanged getting longer and longer with each Widget you add to the dialog. There should be one and only one place where new Widgets are defined - in your case the constructor of `FontDialogDirector. According to the Open Closed principle there should be no need to change an existing event handler method for other widgets if you need to add a new event handler for a new widget.

The general idea is indeed the use of events: Widget.changed() should not call a specific method of the DialogDirector, but emit a changed event to which a "user" of the Widget can subscribe. That way, there will be no need to make the Widget contain a member of type DialogDirector, just a list of subscribers.

In C++ (which is what you used above), you may need to implement a publisher-subscriber mechanism for this. The classic "Observer" pattern is one possible way, or you just pass a member function pointer to the Widget, use a command object for this purpose, or you google for "c++ publisher subscriber", which will you present some more alternatives. Note that C++ GUI frameworks typically provide a standard mechanism for this, like Qt's signal/slot mechanism, which is what you should use in a real-world program.

That way, your FontDialogDirector can provide individual methods like

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

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

and pass these methods to the Widget. If this happens as a constructor argument or by specific "AddEventHandler" methods is not important, just use the pub-sub mechanism you picked above. If we assume your list of "change event subscribers" is stored in a member variable _subscribers, your Widget::changed method then will look roughly like this:

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

and notifyChange will directly be mapped to the related method of FontDialogDirector.

OTHER TIPS

The "correct" answer, as Robert Harvey's (always) excellent advice implies, will be specific to the problem you are trying to solve. Patterns are not building blocks, but some patterns have become embedded in programming languages and tools. Most patterns are work-arounds for language-specific limitations.

The Observer pattern is one. If you were using C#, I would say subscribe to events, and have a dedicated method for each widget event. It is possible to create a "generic dialog" that does not use any dialog specific code at all, only dynamically populated instances driven purely by data, such as a list of widgets to use, and rules to use when events are fired. Do you want to create such a thing? Probably not. Lets go through the points where you wish to reduce code:

  1. MyNewWidget class: usually a set of UI components are already provided and can be reused across all dialogs.
  2. Additional field in the "director": all component references/pointers could be stored in a collection that expands dynamically.
  3. Instantiation of the widget: this could be driven by data, where the data specifies which widgets to instantiate, and additional properties per instance.
  4. Code executed when an event is fired: firstly you need something like an "event". In C# this is built in. In other languages you might use a function pointer. In any case, the underlying implementation will add dynamically add a pointer to a function when you subscribe to an event.

Now how do you avoid writing widget-specfic code when the event is raised? This is where you should probably just keep writing code. It is possible to create a "language" for widget behavior, and get rid of all code. You have given us no reason to suggest this.

So you have widget specific code. How do you "address" a widget instance in this code? The easiest way is with a field per widget. Otherwise you have to use an identifier (string, integer key, etc) to find it in the dynamic collection.

In the end the easiest way to do these tasks is generally the standard technique recommended by your tools and environment.

Licensed under: CC-BY-SA with attribution
scroll top