Object orientation true to its inventors original intent uses messaging for communication between objects. That means, data is flowing one-way during each “communication act”.

It´s easy to depict – but what actually does it mean? Does it mean, each functional unit (a, b, c) calls the next like this?
int doA() { var x = ...; doB(x); }
int doB(int x) { var y = ...x...; doC(y); }
Could be, at least technically. Formally this would be messaging (with no data, but just a signal flowing between processing steps).
But I understand messaging differently. To me we need to decouple communicating parties even more. Not only should we get rid of the coupling between request and response, but also between the responsibilities of sender and receiver.
This might be subtle, but think about what doA() does: it processes some data (…) and then requests a particular service by calling doB(). doA() thus not only knows how to do its own A-stuff, but also what comes next: some B-stuff. The same for doB().
The link between the processing steps is part of the processing steps. That, I think, is wrong. It´s too much coupling. It´s too many responsibilities for a functional unit.
Interestingly this coupling is asymmetric: Each functional unit knows about the purpose of the next one – but is oblivious to who called it. Strange, isn´t it?
That´s different with functions. Another translation of the above flow could be this:
var x = doA(); var y = doB(x); doC(y);
The functions neither know, who called them, nor what´s going to happen next. They don´t know each other. They are completely independent. I call that “mutual oblivion” (MO). And I think, that´s how messaging flows should be implemented. Their arrows mean, senders don´t know about receivers. And receivers don´t know about senders.
When using functions like shown the implementation naturally follows the Principle of Mutual Oblivion (PoMO). But what if functions are not the right tool for the job? That can be the case if functional units have more than one input or several outputs. Take this flow for example:

A function can return only one result. But here the processing is supposed to flow in one of two directions after validation. Of course, the obvious solution would be:
void validate(T data) { if (...) save(data); else report_validation_error("..."); }
But that´s violating the PoMO. validate() would be hard wired to the next processing steps.
And that wouldn´t change, even if those steps were injected as continuations:
void validate(T data, Action save, Action report_validation_error) { if (...) save(data); else report_validation_error("..."); }
No, to follow the PoMO requires a change of attitude. It´s not about technical detail, but semantics and responsibility. validate() still knows too much. It´s not context independent like a function. (That´s what FP programmers like so much about functions. If used right, they are easily composable. A function does not know about its usage context.)
To truly decouple functional units for messaging, they need to focus on their own responsibilities. That means, processing input and producing output. For validate() that could look like this:
void validate(T data, Action onValid, Action onInvalid) { if (...) onValid(data); else onInvalid("..."); }
You might think changing the continuation parameter names is a small thing. And technically it is. But it goes a long way to decouple validate() from any other functional units in whatever flow.
Now the procedure focusses on validation. It knows nothing about happens before and after. onValid makes no assumptions about what the next processing step might be if the data is valid. Instead of calling services events are fired. Here the difference lies in the parameter naming. But when switching to classes the language feature changes.
Here´s a traditional implementation of a validation service class:
class Validator { IRepository _repo; ILogger _log;
public Validator(IRepository repo, ILogger _log) { _repo = repo; _log = log; }
public void Validate(T data) { if (...) _repo.Save(data); else _log.Report_error("..."); } }
That´s SOLID, isn´t it? But it´s not object oriented in the original sense, I argue. Because it violates the PoMO, which – to me – is at the heart of messaging like one-way communication. Maybe I should extend my definition of messaging to underline this:
Messaging is one-way communication by transporting data (message) from a sender to a receiver – and both are oblivious of each other.
Fortunately it´s easy to change the class so that it conforms to the PoMO:
class Validator { public void Validate(T data) { if (...) OnValid(data); else OnInvalid("..."); }
public event Action OnValid; public event Action OnInvalid; }
Not only is this a reduction in LOC, it´s also more decoupled than the IoC version. Technically functionality still gets injected. But semantically it´s different. And that´s the point [1].
Events defined in that way are a feature of C#. But you can do the same in Ruby or JavaScript and other languages. Think function pointer or observer pattern.
Bottom line: If you want to try out messaging, then be sure to keep the functional units decoupled. Sending messages alone does not do the job. Senders and receivers should not even know about one another.
Endnotes
[1] Interestingly the switch from service injection to events also leads to a technical decoupling. As long as services are injected using interfaces there is a topological dependency between client and service. The client not only is dependent on some functionality (e.g. Save()), but also on where this functionality is located in the environment (e.g. IRepository). That means, should services get shuffled around because someone applies the ISP, dependent services need to change. That to me looks like a violation of the SRP, where each functional unit should only have one reason to change.
When using events, injection works on single functions instead of set of functions. That means the dependent functional unit does not know about its environment´s topology. It does not care on which interfaces these functions are implemented. It´s not affected, if they move around.
