How does a sender obtain a reference to a recipient (i.e. how connections are made)?

There are several ways a sender can obtain a reference to a recipient, each of them being useful in certain circumstances. These ways are:

  1. Receive as a constructor parameter
  2. Receive inside a message (i.e. as a method parameter)
  3. Receive in response to a message (i.e. as a method return value)
  4. Receive as a registered observer

Let’s take a closer look at what each of them is about and which one to choose in what circumstances.

Receive as a constructor parameter

Two objects can be composed by passing one into a constructor of another:

1 sender = new Sender(recipient);

A sender that receives the recipient then saves a reference to it in a private field for later, like this:

1 private Recipient _recipient;
2 
3 public Sender(Recipient recipient)
4 {
5   _recipient = recipient;
6 }

Starting from this point, the Sender may send messages to Recipient at will:

1 public void DoSomething()
2 {
3   //... other code
4 
5   _recipient.DoSomethingElse();
6 
7   //... other code
8 }

Advantage: “what you hide, you can change”

Composing using constructors has one significant advantage. Let’s look again at how Sender is created:

1 sender = new Sender(recipient);

and at how it’s used:

1 sender.DoSomething();

Note that only the code that creates a Sender needs to be aware of it having access to a Recipient. When it comes to invoking a method, this private reference is invisible from outside. Now, remember when I described the principle of separating object use from its construction? If we follow this principle here, we end up with the code that creates a Sender being in a totally different place than the code that uses it. Thus, every code that uses a Sender will not be aware of it sending messages to a Recipient at all. There is a maxim that says: “what you hide, you can change”3 – in this particular case, if we decide that the Sender does not need a Recipient to do its job, all we have to change is the composition code to remove the Recipient:

1 //no need to pass a reference to Recipient anymore
2 new Sender();

and the code that uses Sender doesn’t need to change at all – it still looks the same as before, since it never knew Recipient:

1 sender.DoSomething();

Communication of intent: required recipient

Another advantage of the constructor approach is that it allows stating explicitly what the required recipients are for a particular sender. For example, a Sender accepts a Recipient in its constructor:

1 public Sender(Recipient recipient)
2 {
3  //...
4 }

The signature of the constructor makes it explicit that a reference to Recipient is required for a Sender to work correctly – the compiler will not allow creating a Sender without passing something as a Recipient4.

Where to apply

Passing into a constructor is a great solution in cases we want to compose a sender with a recipient permanently (i.e. for the lifetime of a Sender). To be able to do this, a Recipient must, of course, exist before a Sender does. Another less obvious requirement for this composition is that a Recipient must be usable at least as long as a Sender is usable. A simple example of violating this requirement is this code:

1 sender = new Sender(recipient);
2 
3 recipient.Dispose(); //but sender is unaware of it
4                      //and may still use recipient later:
5 sender.DoSomething();

In this case, when we tell sender to DoSomething(), it uses a recipient that is already disposed of, which may lead to some nasty bugs.

Receive inside a message (i.e. as a method parameter)

Another common way of composing objects together is passing one object as a parameter of another object’s method call:

1 sender.DoSomethingWithHelpOf(recipient);

In such a case, the objects are most often composed temporarily, just for the time of execution of this single method:

1 public void DoSomethingWithHelpOf(Recipient recipient)
2 {
3   //... perform some logic
4 
5   recipient.HelpMe();
6 
7   //... perform some logic
8 }

Where to apply

Contrary to the constructor approach, where a Sender could hide from its user the fact that it needs a Recipient, in this case, the user of Sender is explicitly responsible for supplying a Recipient. In other words, there needs to be some kind of coupling between the code using Sender and a Recipient. It may look like this coupling is a disadvantage, but I know of some scenarios where it’s required for code using Sender to be able to provide its own Recipient – it allows us to use the same sender with different recipients at different times (most often from different parts of the code):

1 //in one place
2 sender.DoSomethingWithHelpOf(recipient);
3 
4 //in another place:
5 sender.DoSomethingWithHelpOf(anotherRecipient);
6 
7 //in yet another place:
8 sender.DoSomethingWithHelpOf(yetAnotherRecipient);

If this ability is not required, I strongly prefer the constructor approach as it removes the (then) unnecessary coupling between code using Sender and a Recipient, giving me more flexibility.

Receive in response to a message (i.e. as a method return value)

This method of composing objects relies on an intermediary object – often an implementation of a factory pattern – to supply recipients on request. To simplify things, I will use factories in examples presented in this section, although what I tell you is true for some other creational patterns as well (also, later in this chapter, I’ll cover some aspects of the factory pattern in depth).

To be able to ask a factory for recipients, the sender needs to obtain a reference to it first. Typically, a factory is composed with a sender through its constructor (an approach I already described). For example:

1 var sender = new Sender(recipientFactory);

The factory can then be used by the Sender at will to get a hold of new recipients:

 1 public class Sender
 2 {
 3   //...
 4 
 5   public void DoSomething()
 6   {
 7     //ask the factory for a recipient:
 8     var recipient = _recipientFactory.CreateRecipient();
 9 
10     //use the recipient:
11     recipient.DoSomethingElse();
12   }
13 }

Where to apply

I find this kind of composition useful when a new recipient is needed each time DoSomething() is called. In this sense, it may look much like in case of the previously discussed approach of receiving a recipient inside a message. There is one difference, however. Contrary to passing a recipient inside a message, where the code using the Sender passed a Recipient “from outside” of the Sender, in this approach, we rely on a separate object that is used by a Sender “from the inside”.

To be more clear, let’s compare the two approaches. Passing recipient inside a message looks like this:

1 //Sender gets a Recipient from the "outside":
2 public void DoSomething(Recipient recipient)
3 {
4   recipient.DoSomethingElse();
5 }

and obtaining it from a factory:

1 //a factory is used "inside" Sender
2 //to obtain a recipient
3 public void DoSomething()
4 {
5   var recipient = _factory.CreateRecipient();
6   recipient.DoSomethingElse();
7 }

So in the first example, the decision on which Recipient is used is made by whoever calls DoSomething(). In the factory example, whoever calls DoSomething() does not know at all about the Recipient and cannot directly influence which Recipient is used. The factory makes this decision.

Factories with parameters

So far, all of the factories we considered had creation methods with empty parameter lists, but this is not a requirement of any sort - I just wanted to make the examples simple, so I left out everything that didn’t help make my point. As the factory remains the decision-maker on which Recipient is used, it can rely on some external parameters passed to the creation method to help it make the decision.

Not only factories

Throughout this section, we have used a factory as our role model, but the approach of obtaining a recipient in response to a message is wider than that. Other types of objects that fall into this category include, among others: repositories, caches, builders, collections5. While they are all important concepts (which you can look up on the web if you like), they are not required to progress through this chapter so I won’t go through them now.

Receive as a registered observer

This means passing a recipient to an already created sender (contrary to passing as constructor parameter where the recipient was passed during creation) as a parameter of a method that stores the reference for later use. Usually, I meet two kinds of registrations:

  1. a “setter” method, where someone registers an observer by calling something like sender.SetRecipient(recipient)method. Honestly, even though it’s a setter, I don’t like naming it according to the convention “setWhatever()” – after Kent Beck6 I find this convention too much implementation-focused instead of purpose-focused. Thus, I pick different names based on what domain concept is modeled by the registration method or what is its purpose. Anyway, this approach allows only one observer and setting another overwrites the previous one.
  2. an “addition” method - where someone registers an observer by calling something like sender.addRecipient(recipient) - in this approach, a collection of observers needs to be maintained somewhere and the recipient registered as an observer is merely added to the collection.

Note that there is one similarity to the “passing inside a message” approach – in both, a recipient is passed inside a message. The difference is that this time, contrary to “pass inside a message” approach, the passed recipient is not used immediately (and then forgotten), but rather it’s remembered (registered) for later use.

I hope I can clear up the confusion with a quick example.

Example

Suppose we have a temperature sensor that can report its current and historically mean value to whoever subscribes to it. If no one subscribes, the sensor still does its job, because it still has to collect the data for calculating a history-based mean value in case anyone subscribes later.

We may model this behavior by using an observer pattern and allow observers to register in the sensor implementation. If no observer is registered, the values are not reported (in other words, a registered observer is not required for the object to function, but if there is one, it can take advantage of the reports). For this purpose, let’s make our sensor depend on an interface called TemperatureObserver that could be implemented by various concrete observer classes. The interface declaration looks like this:

1 public interface TemperatureObserver
2 {
3   void NotifyOn(
4     Temperature currentValue,
5     Temperature meanValue);
6 }

Now we’re ready to look at the implementation of the temperature sensor itself and how it uses this TemperatureObserver interface. Let’s say that the class representing the sensor is called TemperatureSensor. Part of its definition could look like this:

 1 public class TemperatureSensor
 2 {
 3   private TemperatureObserver _observer
 4     = new NullObserver(); //ignores reported values
 5 
 6   private Temperature _meanValue
 7     = Temperature.Celsius(0);
 8 
 9   // + maybe more fields related to storing historical data
10 
11   public void Run()
12   {
13     while(/* needs to run */)
14     {
15       var currentValue = /* get current value somehow */;
16       _meanValue = /* update mean value somehow */;
17 
18       _observer.NotifyOn(currentValue, _meanValue);
19 
20       WaitUntilTheNextMeasurementTime();
21     }
22   }
23 }

As you can see, by default, the sensor reports its values to nowhere (NullObserver), which is a safe default value (using a null for a default value instead would cause exceptions or force us to put a null check inside the Run() method). We have already seen such “null objects”7 a few times before (e.g. in the previous chapter, when we introduced the NoAlarm class) – NullObserver is just another incarnation of this pattern.

Registering observers

Still, we want to be able to supply our own observer one day, when we start caring about the measured and calculated values (the fact that we “started caring” may be indicated to our application e.g. by a network packet or an event from the user interface). This means we need to have a method inside the TemperatureSensor class to overwrite this default “do-nothing” observer with a custom one after the TemperatureSensor instance is created. As I said, I don’t like the “SetXYZ()” convention, so I will name the registration method FromNowOnReportTo() and make the observer an argument. Here are the relevant parts of the TemperatureSensor class:

 1 public class TemperatureSensor
 2 {
 3   private TemperatureObserver _observer
 4     = new NullObserver(); //ignores reported values
 5 
 6   //... ... ...
 7 
 8   public void FromNowOnReportTo(TemperatureObserver observer)
 9   {
10     _observer = observer;
11   }
12 
13   //... ... ...
14 }

This allows us to overwrite the current observer with a new one should we ever need to do it. Note that, as I mentioned, this is the place where the registration approach differs from the “pass inside a message” approach, where we also received a recipient in a message, but for immediate use. Here, we don’t use the recipient (i.e. the observer) when we get it, but instead, we save it for later use.

Communication of intent: optional dependency

Allowing registering recipients after a sender is created is a way of saying: “the recipient is optional – if you provide one, fine, if not, I will do my work without it”. Please, don’t use this kind of mechanism for required recipients – these should all be passed through a constructor, making it harder to create invalid objects that are only partially ready to work.

Let’s examine an example of a class that:

  • accepts a recipient in its constructor,
  • allows registering a recipient as an observer,
  • accepts a recipient for a single method invocation

This example is annotated with comments that sum up what these three approaches say:

 1 public class Sender
 2 {
 3   //"I will not work without a Recipient1"
 4   public Sender(Recipient1 recipient1) {...}
 5 
 6   //"I will do fine without Recipient2 but you
 7   //can overwrite the default here if you are
 8   //interested in being notified about something
 9   //or want to customize my default behavior"
10   public void Register(Recipient2 recipient2) {...}
11 
12   //"I need a recipient3 only here and you get to choose
13   //what object to give me each time you invoke 
14   //this method on me"
15   public void DoSomethingWith(Recipient3 recipient3) {...}
16 }

More than one observer

Now, the observer API we just skimmed over gives us the possibility to have a single observer at any given time. When we register a new observer, the reference to the old one is overwritten. This is not really useful in our context, is it? With real sensors, we often want them to report their measurements to multiple places (e.g. we want the measurements printed on the screen, saved to a database, and used as part of more complex calculations). This can be achieved in two ways.

The first way would be to just hold a collection of observers in our sensor, and add to this collection whenever a new observer is registered:

1 private IList<TemperatureObserver> _observers
2   = new List<TemperatureObserver>();
3 
4 public void FromNowOnReportTo(TemperatureObserver observer)
5 {
6   _observers.Add(observer);
7 }

In such case, reporting would mean iterating over the list of observers:

1 ...
2 foreach(var observer in _observers)
3 {
4   observer.NotifyOn(currentValue, meanValue);
5 }
6 ...

The approach shown above places the policy for notifying observers inside the sensor. Many times this could be sufficient. Still, the sensor is coupled to the answers to at least the following questions:

  • In what order do we notify the observers? In the example above, we notify them in order of registration.
  • How do we handle errors (e.g. one of the observers throws an exception) - do we stop notifying further observers, or log an error and continue, or maybe do something else? In the example above, we stop on the first observer that throws an exception and rethrow the exception. Maybe it’s not the best approach for our case?
  • Is our notification model synchronous or asynchronous? In the example above, we are using a synchronous for loop.

We can gain a bit more flexibility by extracting the notification logic into a separate observer that would receive a notification and pass it to other observers. We can call it “a broadcasting observer”. The implementation of such an observer could look like this:

 1 public class BroadcastingObserver
 2   : TemperatureObserver,
 3     TemperatureObservable //I'll explain it in a second
 4 {
 5   private IList<TemperatureObserver> _observers
 6     = new List<TemperatureObserver>();
 7 
 8   public void FromNowOnReportTo(TemperatureObserver observer)
 9   {
10     _observers.Add(observer);
11   }
12 
13   public void NotifyOn(
14     Temperature currentValue,
15     Temperature meanValue)
16   {
17     foreach(var observer in _observers)
18     {
19       observer.NotifyOn(currentValue, meanValue);
20     }
21   }
22 }

This BroadcastingObserver could be instantiated and registered like this:

 1 //instantiation:
 2 var broadcastingObserver
 3   = new BroadcastingObserver();
 4 
 5 ...
 6 //somewhere else in the code...:
 7 sensor.FromNowOnReportTo(broadcastingObserver);
 8 
 9 ...
10 //somewhere else in the code...:
11 broadcastingObserver.FromNowOnReportTo(
12       new DisplayingObserver())
13 ...
14 //somewhere else in the code...:
15 broadcastingObserver.FromNowOnReportTo(
16       new StoringObserver());
17 ...
18 //somewhere else in the code...:
19 broadcastingObserver.FromNowOnReportTo(
20       new CalculatingObserver());

With this design, the other observers register with the broadcasting observer. However, they don’t really need to know who they are registering with - to hide it, I introduced a special interface called TemperatureObservable, which has the FromNowOnReportTo() method:

1 public interface TemperatureObservable
2 {
3   public void FromNowOnReportTo(TemperatureObserver observer);
4 }

This way, the code that registers an observer does not need to know what the concrete observable object is.

The additional benefit of modeling broadcasting as an observer is that it would allow us to change the broadcasting policy without touching either the sensor code or the other observers. For example, we might replace our for loop-based observer with something like ParallelBroadcastingObserver that would notify each of its observers asynchronously instead of sequentially. The only thing we would need to change is the observer object that’s registered with a sensor. So instead of:

1 //instantiation:
2 var broadcastingObserver
3   = new BroadcastingObserver();
4 
5 ...
6 //somewhere else in the code...:
7 sensor.FromNowOnReportTo(broadcastingObserver);

We would have

1 //instantiation:
2 var broadcastingObserver
3   = new ParallelBroadcastingObserver();
4 
5 ...
6 //somewhere else in the code...:
7 sensor.FromNowOnReportTo(broadcastingObserver);

and the rest of the code would remain unchanged. This is because the sensor implements:

  • TemperatureObserver interface, which the sensor depends on,
  • TemperatureObservable interface which the code that registers the observers depends on.

Anyway, as I said, use registering instances very wisely and only if you specifically need it. Also, if you do use it, evaluate how allowing changing observers at runtime is affecting your multithreading scenarios. This is because a collection of observers might potentially be modified by two threads at the same time.