The main goal is to reduce the connection between the code. This is a somewhat event-based way of thinking, but "events" are not tied to a specific object.
I will write a great example below in some pseudo code that is a bit like JavaScript.
Say we have a Radio class and a Relay class:
class Relay { function RelaySignal(signal) {
Whenever a radio receiver receives a signal, we want several relays to somehow transmit a message. The number and types of relays may vary. We could do it as follows:
class Radio { var relayList = []; function AddRelay(relay) { relayList.add(relay); } function ReceiveSignal(signal) { for(relay in relayList) { relay.Relay(signal); } } }
It works great. But now imagine that we want another component to also take part in the signals received by the Radio class, namely: Speakers:
(sorry if the analogies are not the top label ...)
class Speakers { function PlaySignal(signal) {
We can repeat the pattern again:
class Radio { var relayList = []; var speakerList = []; function AddRelay(relay) { relayList.add(relay); } function AddSpeaker(speaker) { speakerList.add(speaker) } function ReceiveSignal(signal) { for(relay in relayList) { relay.Relay(signal); } for(speaker in speakerList) { speaker.PlaySignal(signal); } } }
We could do it even better by creating an interface, for example, "SignalListener", so that we only need one list in the Radio class, and it can always call the same function on any object that we have that wants to listen to the signal But this still creates a connection between any interface / base class / etc that we accept and the Radio class. Basically, when you change one of the Radio, Signal, or Relay classes, you need to think about how this can affect the other two classes.
Now try something else. Let me create a fourth class called RadioMast:
class RadioMast { var receivers = []; //this is the "subscribe" function RegisterReceivers(signaltype, receiverMethod) { //if no list for this type of signal exits, create it if(receivers[signaltype] == null) { receivers[signaltype] = []; } //add a subscriber to this signal type receivers[signaltype].add(receiverMethod); } //this is the "publish" function Broadcast(signaltype, signal) { //loop through all receivers for this type of signal //and call them with the signal for(receiverMethod in receivers[signaltype]) { receiverMethod(signal); } } }
Now we have a template that we know about, and we can use it for any number and types of classes, if they:
- know about RadioMast (a class that processes all messages)
- know about the signature of the method for sending / receiving messages
So, we are changing the Radio class to its final, simple form:
class Radio { function ReceiveSignal(signal) { RadioMast.Broadcast("specialradiosignal", signal); } }
And add speakers and relays to the list of RadioMast receivers for this type of signal:
RadioMast.RegisterReceivers("specialradiosignal", speakers.PlaySignal); RadioMast.RegisterReceivers("specialradiosignal", relay.RelaySignal);
Now the Speakers and Relay class has zero knowledge of anything, except that they have a method that can receive a signal, and the Radio class, which is the publisher, knows RadioMast that it publishes the signals. This is the point of use for a messaging system such as publish / subscribe.