Mediator class design pattern (two different ways)

R

raylopez99

Here are two different ways of achieving a mediator pattern: the
first, using circular references (for lack of a better way to describe
it), but not using delegates, with the second using delegates.

The first way is an adaptation from C++ for Dummies by Jeff Cogswell.
But obviously it uses references not pointers.

The second way (I'll post this later, as I haven't yet done it) will
be adapted from the book C#3.0 Design Patterns by Judith Bishop, and
it uses delegates.

Which way do I prefer? Both, but they're both "mind bending" to a
degree. Perhaps, once you get used to delegates, the Judith Bishop
delegate way is easier to write, but the 'classic' way by Cogswell is
also good, and it's the way I initially learned about the mediator
design pattern when coding in C++.

A quick note to what exactly a mediator class pattern is: it's like a
hub and spoke system--the mediator (the hub) communicates with a bunch
of classes (at the spokes) and is the 'switchboard' through which
these spokes communicate to one another and are controlled by the
hub. That is, for a class on one 'spoke' to communicate with another
class on another 'spoke', it has to go through the 'hub' mediator.
Why do this? Because otherwise you'll have that classic "N-star" of
connections problem, so that everytime you add a class you have to
connect it to talk to all the other preexisting classes, which quickly
becomes unmanageable.

Here is the code for the non-delegate version:

// October 6, 2008

//output - works fine, as expected.

/*
The gist is this: the mediator class has as member variables all the
classes on the 'spokes', here called 'OtherClass2' and '3', which
inherit from a base class called 'OtherClass', and using the method
"PartChanged" as well as the 'trick' as indicated below (keyword:
'trick'), it can change and/or communicate with any spoke class.
*/

//output:
o is OtherClass 2 type: MediatorC.OtherClass2 having value:2
o is OtherClass 3 type: MediatorC.OtherClass3 having value:1
o is OtherClass 3 type: MediatorC.OtherClass3 having value:1001
OtherClass3 > one thous: 1001
notice now myoc3.oci3 is 1001!: 1001
now decrement myoc2 by ten
o is OtherClass 2 type: MediatorC.OtherClass2 having value:-8
now square both values myoc2 and myoc3
squared values myoc2,3 are: 64, 1002001 //1001^2 = 1002001

the end

Press any key to continue . . .

// Mediator Class Adapted from C++ for Dummies by Jeff Cogswell

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MediatorC
{
class Mediator
{
public int m;
// public OtherClass oc;
public OtherClass2 myoc2;
public OtherClass3 myoc3;
public Mediator()
{
m = 1;
myoc2 = new OtherClass2(this);
myoc3 = new OtherClass3(this);

}

public void PartChanged(OtherClass o)
{

if (o is OtherClass2)
{
OtherClass2 oceye2 = (OtherClass2)o;
Console.WriteLine("o is OtherClass 2 type: {0} having
value:{1}", oceye2.GetType(), oceye2.oci2);

if (oceye2.oci2 > 10)
{
Console.WriteLine("OtherClass2 > ten: {0}",
oceye2.oci2);

}
}
if (o is OtherClass3)
{
OtherClass3 oceye3;
oceye3 = (OtherClass3)o;
Console.WriteLine("o is OtherClass 3 type: {0} having
value:{1}", oceye3.GetType(), oceye3.oci3);

if (oceye3.oci3 > 1000)
{
Console.WriteLine("OtherClass3 > one thous: {0}",
oceye3.oci3);

}
}

}

public void DoSomething()
{
myoc2.UniqueFunc_Cl2(1);
myoc3.UniqueFunc_C13(0);
myoc3.UniqueFunc_Cl3(1000); //notice how you can access
myoc3 public functions and, via the 'trick' below, the 'spoke' class
myoc3 can talk back to the mediator class
if (myoc3.oci3 >= 1000)
{
Console.WriteLine("notice now myoc3.oci3 is 1001!:
{0}", myoc3.oci3);
Console.WriteLine("now decrement myoc2 by ten");
myoc2.UniqueFunc_Cl2(-10);
}
Console.WriteLine("now square both values myoc2 and
myoc3");
OtherClass tempOtherClassbaseRef;
tempOtherClassbaseRef = myoc2;
int iSq =
tempOtherClassbaseRef.ReturnTheInt_oci_Squared();
tempOtherClassbaseRef = myoc3;
int jSq =
tempOtherClassbaseRef.ReturnTheInt_oci_Squared();
Console.WriteLine("squared values myoc2,3 are: {0}, {1}",
iSq, jSq);
Console.WriteLine("\n the end \n");
}

}

class OtherClass
{
private int _oci;
Mediator AMediator;

public int oci
{
get { return _oci; }
set { _oci = value; }
}

public OtherClass(Mediator m)
{
_oci = m.m;
AMediator = m; //trick! This exists in the base class of
the 'spoke' classes and is required
}

public void Changed()
{
AMediator.PartChanged(this); //trick! This exists in the
base class of the 'spoke' classes and is required }

public virtual int ReturnTheInt_oci_Squared()
{
return -1;
}

public virtual void incr_oci()
{ _oci++; }

}

class OtherClass2:OtherClass
{
private int _oci2;
public int oci2
{
get { return _oci2; }
set { _oci2 = value; }
}
public OtherClass2(Mediator M):base(M)
{
_oci2 = M.m;
}
public override int ReturnTheInt_oci_Squared()
{
return oci2*oci2;
}
public override void incr_oci()
{ _oci2++; }

public void UniqueFunc_Cl2(int idelta)
{
_oci2 = _oci2 + idelta;
base.Changed(); //this tells base, and ultimately
Mediator, that something has changed in derived class
//trick!

}

}

class OtherClass3 : OtherClass
{
private int _oci3;
public int oci3
{
get { return _oci3; }
set { _oci3 = value; }
}

public OtherClass3(Mediator m): base(m)
{
_oci3 = m.m;
}

public override int ReturnTheInt_oci_Squared()
{
return oci3*oci3;
}

public override void incr_oci()
{ _oci3++; }

public void UniqueFunc_Cl3(int idelta)
{
_oci3 = _oci3 + idelta;
base.Changed(); //this tells base, and ultimately
Mediator, that something has changed in derived class
//trick!
}

}

//of course, you can add as many "spoke" classes, OtherClass4,
OtherClass5, etc., as you want here, and the functions above
"UniqueFunc" etc are completely arbitrary. The important code is the
'trick' and similar infrastructure code

}
//////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MediatorC
{
class Program
{
static void Main(string[] args)
{
Mediator myMed = new Mediator();
myMed.DoSomething();



////////////////////////////////////


}
}
}
///////////////////
 
M

Michael S

raylopez99 said:
Which way do I prefer? Both, but they're both "mind bending" to a
degree.

I'd call them pretty obvious. Why are you posting your long essays on stuff
beginners learn with ease?

- Michael Starberg
 
R

raylopez99

I'd call them pretty obvious. Why are you posting your long essays on stuff
beginners learn with ease?

- Michael Starberg


It's not obvious. In fact, I think there's a typo in Bishops mediator
pattern--trying to figure it out now. If it's obvious, please post a
mediator that uses delegates.

RL
 
J

Jeff Louie

How about:

public partial class Form1 : Form
{
Controller controller = new Controller();
public Form1()
{
...
controller.NotifyDispatcher += new
Mortgage.NotifyHandler(Form1_OnINotifyEvent);
controller.NotifyDispatcher += total.GetNotifyHandler();
}
...}


public class Controller : Mortgage.IUpdate
{
Model model = new Model();

public event Mortgage.NotifyHandler NotifyDispatcher;
public bool Update(Mortgage.Parameters mp)
{
Mortgage.Calculation mc = model.Calculate(mp);
if (mc.IsValid())
{
// fire event to all registered listeners of change in
model state
NotifyDispatcher(this, new
Mortgage.MortgageEventArgs(mc));
return true;
}
else return false; // do nothing if input data is invalid
}


Regards,
Jeff
 
R

raylopez99

How about:
Regards,
Jeff

Thanks, but I don't see it. This looks more like a standard "events"
model, where one object can notify other objects that an event has
occurred (" // fire event to all registered listeners of change in
model state ")

In a mediator class, you need a mediator and at least two other
classes, with the mediator being able to pass messages from one class
to the other. In fact, what I'm debating now in my example that I'm
writing (should be done in a day or two, as I just code for fun) is
whether the two classes should "live" inside the mediator (as in my
original example) or, as is the case in Judith Bishop's example (where
the two classes don't talk to one another, as I propose), they should
be independently instantiated outside the mediator itself.

RL
 
R

raylopez99

Here are two different ways of achieving a mediator pattern:  the
first, using circular references (for lack of a better way to describe
it), but not using delegates, with the second using delegates.

The first way is an adaptation from C++ for Dummies by Jeff Cogswell.
But obviously it uses references not pointers.

The second way (I'll post this later, as I haven't yet done it) will
be adapted from the book C#3.0 Design Patterns by Judith Bishop, and
it uses delegates.

Here is a mediator class using the 'second way' I mentioned above,
modeled after a design pattern in Judith Bishop's book.

BTW, you can see a delegate is essentially a global "GOTO". Why do I
say this? Because check out what happens at the point ^&* below. And
the statement:" //^&* note once .Send1 fired, then rest of statement
is not executed, even AFTER the throw is done and you would think the
call would be ‘unwound’ to end up back here (i.e., as if it were
recursive)". If delegates were not a global GOTO then logically after
the call ended, the program would continue, like a recursive call,
where it left off. But it does not. In short, delegates (and events,
which are a form of delegate) are a kind of global GOTO.

This program does the following: two classes, which inherit from a
base class, receive information from a mediator class that is common
to both (as a composite member variable/class). By using if/then/else
statements the two classes are able to 'talk' to one another through
the mediator, which broadcasts messages (information, here a string,
int, and char) to all subscribers (a rather inelegant way, but short
of introducing another delegate, which would have been confusing, I
could not think of any better way...please let me know if there is
one).

One class Colleague 1, handles even numbers using the if statement "if
(i%2 == 0) ..."and the other, Colleague2 handles odd numbers, but both
always receive messages (information) broadcast by the Mediator
class. Upon a randomly generated 'magic number' being hit --I made
the magic number(s) 3,5,7,9--the one class broadcasts itself (using
mediator.Send1, see the comment "//this is key") to all other classes,
which then, using if/then logic, can do stuff with these magic
numbers.

Pretty slick eh?

Next time, I will include a bunch of delegates that will be in a hash
table and/or dictionary and you can look them up by a key.

RL

//////// OUTPUT (when a magic number, here 5, happened to be triggered
by the random number generator)

inside SendC1: j
Colleague1 name here: john, String: john, int:10, char: j random_i:5
Colleague2 name: lucy, string: maGiC X!, odd int: 5, char:x
Colleague1 name(2): john5, String: john, int:10, random int: 5 char:x
Press any key to continue . . .
////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MediatorC
{

class Mediator
{
public delegate int Callback1(string msg, int i, char c);
Callback1 respond1;
public void SignOn(Callback1 method1)
{
respond1 += new Callback1(method1);

}

public int Send1(string msg, int i, char c)
{
int j=-1;
if (respond1 != null)
{
if (c == 'x')
{// begin magic 'x' broadcast
j = respond1.Invoke(msg, i, c);
}
else
{

j = respond1.Invoke(msg, i, c);
}
}
return j;
}

public Mediator()
{
}

}

class Colleague
{
protected int iC1;
protected string strC1;
protected char cC1;
protected Mediator mediator;
public Colleague(Mediator m, string s, int i, char c)
{
this.mediator = m;
mediator.SignOn(Receive);
strC1 = s; cC1 = c;
}

public virtual int Receive(string s, int i, char c)
{
// virtual function
return 0;
}

public int SendC1(int iSC)
{

int i_temp;
Console.WriteLine("inside SendC1: {0}", cC1.ToString());
i_temp = mediator.Send1(strC1, iSC, cC1);
return i_temp;
}

}

class Colleague1:Colleague //even Colleague1 is even numbers
{

public Colleague1(Mediator m, string s, int i, char c)
: base(m, s, i, c)
{ }

public override int Receive(string s, int i, char c)
{
int temp_return_i = -1;
if (i % 2 == 0)
{
int j = RandomINT.RandomInt();

Console.WriteLine("Colleague1 name here: {0}, String: {1},
int:{2}, char: {3} random_i:{4}",strC1, s, i.ToString(), c.ToString(),
j);

temp_return_i = i;
iC1 = j;
strC1 = strC1 + j.ToString();
if (j == 3 || j == 5 || j ==7 || j ==9)
{
cC1 = 'x'; //magic 'x' routine
mediator.Send1("maGiC X!", j, 'x'); //magic 'x'
broadcast (this is key)
//^&* note once .Send1 fired, then rest of statement below, namely,
Console.WriteLine("Colleague1 name(2)...", is not executed, even AFTER
the throw is done and you would think the call would be ‘unwound’ to
end up back here (i.e., as if it were recursive)
}
Console.WriteLine("Colleague1 name(2): {0}, String: {1},
int:{2}, random int: {3} char:{4}", strC1, s, i.ToString(), j,
cC1.ToString());

}

return temp_return_i;
}

}

class Colleague2:Colleague //odd numbers
{
public Colleague2(Mediator m, string s, int i, char c)
: base(m, s, i, c)
{ }

public override int Receive(string s, int i, char c)
{
int temp_return_i = -1;

if (c == 'x')
{
//magic x! routine
Console.WriteLine("Colleague2 name: {0}, string: {1},
odd int: {2}, char:{3}", strC1, s, i, c);
}
else
{
if ((i % 2) != 0)
{
Console.WriteLine("Colleague2 name here: {0},
String: {1}, int:{2}, char: {3}", strC1, s, i, c.ToString());
temp_return_i = i;
//return temp_return_i;
}
}

return temp_return_i;
}

}

public static class RandomINT
{
public static int RandomInt()
{
Random myRandomInt = new Random();
return myRandomInt.Next(1, 10);
//static class random number generator
}
}

}
//////////////
// main here
namespace MediatorC
{
class Program
{
static void Main(string[] args)
{

Mediator m = new Mediator();
Colleague2 head2 = new Colleague2(m, "lucy", 11, 'l');
Colleague1 head1 = new Colleague1(m, "john", 10, 'j');

head1.SendC1(10); //colleague1 is even numbers

}
}
}
//////////////////
 
R

raylopez99

BTW, you can see a delegate is essentially a global "GOTO".  Why do I
say this?  Because check out what happens at the point ^&* below. And
the statement:" //^&* note once .Send1 fired, then rest of statement
is not executed, even AFTER the throw is done and you would think the
call would be ‘unwound’ to end up back here (i.e., as if it were
recursive)".  If delegates were not a global GOTO then logically after
the call ended, the program would continue, like a recursive call,
where it left off.  But it does not.  In short, delegates (and events,
which are a form of delegate) are a kind of global GOTO.

OK I take that back. Upon closer inspection it looks like in fact the
program is 'unwound' like in a recursive manner, or picking up where
it left off. So delegates are not entirely like a GOTO.

BTW, here is the output without a magic number:

/*
inside SendC1: j
Colleague1 name here: john, String: john, int:10, char: j random_i:6
Colleague1 name(2): john6, String: john, int:10, random int: 6 char:j
Press any key to continue . . .
*/

And if you put this line(s) in main:
head1.SendC1(10); //colleague1 is even numbers
head2.SendC1(11); //colleague2 is odd numbers

you get this output, when there is no 'magic number' generated:

/*
inside SendC1: j
Colleague1 name here: john, String: john, int:10, char: j random_i:4
Colleague1 name(2): john4, String: john, int:10, random int: 4 char:j
inside SendC1: l
Colleague2 name here: lucy, String: lucy, int:11, char: l
Press any key to continue . . .
*/

RL
 
J

Jon Skeet [C# MVP]

raylopez99 said:
OK I take that back. Upon closer inspection it looks like in fact the
program is 'unwound' like in a recursive manner, or picking up where
it left off. So delegates are not entirely like a GOTO.

Indeed, they're nothing like GOTO - as you've been informed several
times in the past.
 
R

raylopez99

Indeed, they're nothing like GOTO - as you've been informed several
times in the past.

Hey Jon--up to p. 85 (Chapter 3, Advanced Generics) in your book. Gets
bogged down a bit here...but good stuff. I went through your example
on p. 84 and will post a modification of it in another thread.

Ray
 
J

Jon Skeet [C# MVP]

raylopez99 said:
Hey Jon--up to p. 85 (Chapter 3, Advanced Generics) in your book. Gets
bogged down a bit here...but good stuff. I went through your example
on p. 84 and will post a modification of it in another thread.

Hopefully when you get to later on in the book you'll have more
appreciation for lambda expressions :)

I tried my hardest to stop the generics chapter from getting *too*
heavy, but some topics just can't be explained easily :( There's
another section (third part of chapter 9, from memory) which talks
about the changes to type inference for generics - that's possibly the
"boggiest" bit of the book, but it's more for later reference than
anything else.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top