jeudi 20 avril 2017

Should I use Observer Pattern if the subscribers are known in advance?

I am having this question when I'm using Unity with C#. But I think this question may arise in general for any OOP languages to people who are starting to learn to design OOP structures and when to use the observer pattern, or the publisher-subscriber pattern. The event I am specifically talking about is the event and delegates in C#.

I am looking for advices about using events. I am feeling event, or the Observer Pattern is so useful that I was getting afraid to be overusing it. I searched this question and found the community suggests that observer patterns is good to use to receive tweeter feeds, and that I should use the Observer Pattern if and only if using the pattern will reduce coupling. There was a discussion on when design patterns should be avoided, which suggested to prioritise to follow the SOLID principle.

The discussions seem to suggest that it is generally good to use the observer pattern when the list of subscribers for an event is expected to change at run time. For instance, RSS feeds will have different subscribers at any given time and there is no way for the programmer to know who is receiving it. So yes it seems a sweet spot to use the Observer Pattern here.

However, I still do not seem to be convinced whether I should favour of using this pattern, if the subscriber list is known to the developer at the compile time, and I want to perform Unit Testing.

Let's say in my game my character is entering a new area. Upon entering a new area, I want the game to:

Effect List

  1. Show an GUI saying The Swamp of the Code Smell in the middle of the screen
  2. Update the Quest Board to show quests specific to this area
  3. Start to decrease my character's HP 5 per second because the area has a bad smell and increase MP by 10 because it feels good to be there

To achieve this, I am not convinced which way is a better design pattern:

Approach 1: Feed all info into Constructor for Unit Testing

MapEnter class has UI, GlobalDamage, Character, ... and all required classes in its constructor. It can then just call GlobalDamage.ApplyDamagePerSecond(myCharacter), UI.ShowText(), ...

I thought of this way because a talk about Unit Test suggsted classes must be isolated and that means classes must not new any other objects, and that can be achieved only by being given the list of interactable classes through its constructor.

However, posts on how to unit test observer patterns suggest I could test to ensure (1) events are well subscribed and unsubscribed, and (2) each method to be subscribed well functions independently. So I'm not so sure of this point.

On the other hand, I also seem to believe that when a class contains all of its references as its class variables from its constructor, it's easier to understand to what extend a class is responsible for just by looking at the class variables.

However, the problem comes when I want to extend the MapEnter's effects. Let's say in addition to the three effects I initally planned, I now want to add a new functionality to it:

Effect List:

  1. Start to play a BGM of the area

Then now, I will need to change the constructor of MapEnter class to know BgmPlayer. Change its implementation on OnMapEnter(). Change Unit Test Cases. And so on and so forth.

This may enable Unit Test but is strongly tied with other classes and so it appears to have high coupling.

Approach 2: Publisher-Subscriber Pattern

A big plus of this approach is that now it is super easy to add any new ideas to MapEnter. It's as easy as adding lines of code to add/remove methods to the event. MapEnter now need not worry about taking N parameters in its constructor.

Here I'm applying the Observer Pattern even though I know exactly who are going to listen to this event at the compile time. Then I could achieve this without having this pattern. My concerns are:

  1. Does this reduce coupling? Is it good to use the observer pattern in this kind of cases?
  2. Does the Unit Test Argument in Approach 1 justify Approach 1?
  3. Wouldn't it be easier to understand the code structure if a class has all references to what it needs to call as in Approach 1? If other programmers in my team silently added new subscribers to the event of MapEnter, how would I know that without going through all of the event's references? Or is it expected that I should do so for every single event in my application when things go wrong?

If I am justified to use the observer pattern here because it reduces coupling, literally all methods could, and should, be invoking other methods by events, as long as the listeners do not care about who is called first and there is no chains of observers. Then this pattern will be everywhere, and that sounds like it's going to be painful to understand the code even if I ensure there is only 1 or 2 levels of observer chains.

Thanks in advance.

Aucun commentaire:

Enregistrer un commentaire