Disclaimer: before explaining the problem here I am aware that the answer to the question might be a subject of an opinion. The reason I am asking this question is because I still believe that there is a "right-ish" way.
Even though I was told that adding logging (to the function) doesn't break the SRP, I disagree with it. All functions still do what they say they do when they don't have logging. So when somebody thinks "Well, it would be useful to have it logged as well" adding logging into that function is in fact an addition of the reason to change.
I think a decorator is a perfect structure for logging:
- It separates the concerns; therefore the code is consistent throughout the project (I mean there is no ambiguity when in some files logging exist, in others it doesn't)
- It allows using the original class without the logging if desired (like unit testing or other places of the code when it is not required to log)
However my question here is not about logging. My question is about dispatching a custom event. Just as we want to log something in some random places of the code; we as well might want to dispatch an event. The thing is that adding it means adding another reason for the function to change, therefore also breaks the SRP. We can have a function that does something, and by adding the event dispatcher functionality nothing changes in terms of core functionality (just like with logging). Therefore it seems adding a decorator to dispatch an event seems like a great idea! However...
Dispatching an event means that we can hook something important to it. Sure, it can be something trivial, but there is nothing wrong with building an important piece of code that is executed because of the subscription to that event. But the word "decorator" correlates with the word "non-breaking". If we use a decorator pattern to create a pizza, we still can have pizza without any decorators at all. Just like we can have a function without the logging (if we remove a decorator that does logging). But if we have a decorator that dispatches an event, it is no longer "non-breaking" since, as I said earlier, something important might be subscribed to it. Which means that adding the dispatcher to the core function is a better option. But in that case it wouldn't be consistent: why some of the functions dispatch events and the rest do not? Using decorators is an amazing approach to just plug in desired functionality on the fly.
So my question is: is there any pattern that is modular enough but more tightly coupled to an original function than the decorator is? In addition to that I can say that decorators have their own drawbacks: first of all they don't say what they do; since they repeat the original function name, by looking at a function name, we can't know what it does; we either should look at the namespace (hopefully it is created meaningfully) or by looking at the implementation. Another disadvantage is that not all classes have interfaces and chaining decorators without having an interface won't work; for instance one can't decorate a factory class (there are frameworks that have factory classes that do not implement an interface). And the final disadvantage is that decorators really struggle to work when an original class has multiple public methods that call each other (not the best approach, but there are reasonable scenarios out there); in this case in the decorator you can't really call an original method (that is also decorated) so that the decorator's function would be triggered.
Inheritance could be an option, but you can't chain them and you add all kinds of unnecessary dependencies to the constructor when choosing inheritance too.
I am not sure what are other options and looking for a piece of advice. Thanks in advance.