An alternate approach would be to introduce "an additional level of indirection" - take the mapping of users to groups and groups to users, and make it an abstraction unto itself. This offers several advantages: Users and Groups are decoupled from one another, many mappings can easily be maintained and manipulated simultaneously, and the mapping abstraction can be extended in the future by defining derived classes.
The Mediator pattern promotes a "many-to-many relationship network" to "full object status". Modelling the inter-relationships with an object enhances encapsulation, and allows the behavior of those inter-relationships to be modified or extended through subclassing.
An example where Mediator is useful is the design of a user and group capability in an operating system. A group can have zero or more users, and, a user can be a member of zero or more groups. The Mediator pattern provides a flexible and non-invasive way to associate and manage users and groups.
Colleagues (or peers) are not coupled to one another. Each talks to the Mediator, which in turn knows and conducts the orchestration of the others. The "many to many" mapping between colleagues that would otherwise exist, has been "promoted to full object status". This new abstraction provides a locus of indirection where additional leverage can be hosted.
Airlines have stream-lined operations by limiting the number of direct point-to-point flights, and relying on “hub” cities more. Each hub serves as a "concentrator". Origins and destinations are decoupled by introducing the intermediary of a hub.
Before | After | |
---|---|---|
// Node objects interact directly with each other, // recursion is required, removing a Node is hard, // and it is not possible to remove the first node. class Node { public: Node( int v ) { m_val = v; m_next = 0; } void add_node( Node* n ) { if (m_next) m_next->add_node( n ); else m_next = n; } void traverse() { cout << m_val << " "; if (m_next) m_next->traverse(); else cout << '\n'; } void remove_node( int v ) { if (m_next) if (m_next->m_val == v) m_next = m_next->m_next; else m_next->remove_node( v ); } private: int m_val; Node* m_next; }; int main( void ) { Node lst( 11 ); Node two( 22 ), thr( 33 ), fou( 44 ); lst.add_node( &two ); lst.add_node( &thr ); lst.add_node( &fou ); lst.traverse(); lst.remove_node( 44 ); lst.traverse(); lst.remove_node( 22 ); lst.traverse(); lst.remove_node( 11 ); lst.traverse(); } // 11 22 33 44 // 11 22 33 // 11 33 // 11 33 | // A "mediating" List class focuses and simplifies // all the administrative responsibilities, and the // recursion (which does not scale up well) has been // eliminated. class Node { public: Node( int v ) { m_val = v; } int get_val() { return m_val; } private: int m_val; }; class List { public: void add_node( Node* n ) { m_arr.push_back( n ); } void traverse() { for (int i=0; i < m_arr.size(); ++i) cout << m_arr[i]->get_val() << " "; cout << '\n'; } void remove_node( int v ) { for (vector |
Mediator and Observer are competing patterns. The difference between them is that Observer distributes communication by introducing "observer" and "subject" objects, whereas a Mediator object encapsulates the communication between other objects. We've found it easier to make reusable Observers and Subjects than to make reusable Mediators. [GoF, p346]
On the other hand, Mediator can leverage Observer for dynamically registering colleagues and communicating with them. [GoF, p282]
Mediator is similar to Facade in that it abstracts functionality of existing classes. Mediator abstracts/centralizes arbitrary communication between colleague objects, it routinely "adds value", and it is known/referenced by the colleague objects (i.e. it defines a multidirectional protocol). In contrast, Facade defines a simpler interface to a subsystem, it doesn't add new functionality, and it is not known by the subsystem classes (i.e. it defines a unidirectional protocol where it makes requests of the subsystem classes but not vice versa). [GoF, p193]