The main goal is to reduce coupling between the code. It's a somewhat event-based way of thinking, but the "events" aren't tied to a specific object.
I'll write out a big example below in some pseudo code that looks a bit like JavaScript.
Let's say we have a class Radio and a class Relay:
class Relay {
function RelaySignal(signal) {
//do something we don't care about right now
}
}
class Radio {
function ReceiveSignal(signal) {
//how do I send this signal to other relays?
}
}
Whenever radio receives a signal, we want a number of relays to relay the message in some way. The number and types of relays can differ. We could do it like this:
class Radio {
var relayList = [];
function AddRelay(relay) {
relayList.add(relay);
}
function ReceiveSignal(signal) {
for(relay in relayList) {
relay.Relay(signal);
}
}
}
This works fine. But now imagine we want a different component to also take part of the signals that the Radio class receives, namely Speakers:
(sorry if the analogies aren't top notch...)
class Speakers {
function PlaySignal(signal) {
//do something with the signal to create sounds
}
}
We could 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 make this even better by creating an interface, like "SignalListener", so that we only need one list in the Radio class, and always can call the same function on whatever object we have that wants to listen to the signal. But that still creates a coupling between whatever interface/base class/etc we decide on and the Radio class. Basically whenever you change one of the Radio, Signal or Relay class you have to think about how it could possibly affect the other two classes.
Now let's try something different. Let's create a fourth class named 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 pattern that we are aware of and we can use it for any number and types of classes as long as they:
- are aware of the RadioMast (the class handling all the message passing)
- are aware of the method signature for sending/receiving messages
So we change the Radio class to its final, simple form:
class Radio {
function ReceiveSignal(signal) {
RadioMast.Broadcast("specialradiosignal", signal);
}
}
And we add the speakers and the relay to the RadioMast's receiver list 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, being the publisher, is aware of the RadioMast that it publishes signals to. This is the point of using a message-passing system like publish/subscribe.