Design Patterns

almirs

almir-s

Posted on August 18, 2021

Design Patterns

What are design patterns?

By definition “Design patterns are typical solutions for common problems in software design. Each pattern is like a blueprint that you can customize to solve a particular problem.”

So, in other words their main occupation is a software DESIGN as one of the most important steps in software development. It is pointless to explain why good software organization and structure is essential, even if only one person is involved in the development process. Changes have to be made from time to time, and quality of design defines how painful this process will be. If you do it the right way, the next person who operates on your codebase should not be able to tell when a specific line of code is added. The software needs to be organized so that any change could be seamlessly integrated with the rest of the program. Understanding what existing code is doing and getting familiar with a context is often the most consuming part of programming although people overlook it.

Design Patterns: Elements of Reusable Object-Oriented Software is a book that covers essentials of design patterns and which is considered as a “bible” when it is up to this topic, it is more than twenty years old and it is still actual, which says how important good design is in the software development process in comparison to technologies and frameworks, and how much attention needs to be put in it.

This series of articles goes through some of the Design patterns covered in a book named “Game Programming Patterns” by Robert Nystrom. The book covers many patterns in details, providing details on how/when/why to use all of them on the examples from game development..
Reading and learning about design patterns helps in the process of software design, it gives solutions on known problems. It also increases the vocabulary of an individual, it gets him/her familiar with many concepts and ideas supporting his/her professional growth and is very enjoyable to read.

The idea of those articles is to try to provide some examples for patterns covered, explain where they can be used and potentially describe “non-pattern” solutions for the same problem and compare them. Additionally I want to provide my understanding of mentioned subject matter with code snippets in order to help myself and potential other people to get familiar and more confident with this topic. There will be provided some examples, code snippets or definitions from the book and it will be explicitly marked in order to distinguish book content from article content and to respect copyrights.

Observer Pattern

What is the Observer pattern?

Observer pattern is one of the most used patterns in the software industry, it is massively integrated in many languages and frameworks(MVC architecture in its background uses Observer). Basic idea of the pattern is to announce that the state of the “observed” object is changed in some interesting way without caring who receives that information. Information recipients would be entities which actually observe(they are observers) state behavior and are interested in some of its changes. There could be a lot of them, and they are interested in different events.

For example, let's say we have a game where we want to achieve some points when we “kill” some “monster”. Obviously we would like to decouple achievement logic from gameplay itself, separate it, put it in some other place, and let it know when a specific event occurs, in this case it is killing a monster. Simple code snippet that demonstrates this would be:

void update(Entity entity) {
    // Game flow logic
    if(monsterKilled()) {
        notify(entity, MONSTER_KILLED);
    }
}
Enter fullscreen mode Exit fullscreen mode

The achievement system should register itself so whenever notification is sent, it receives it. After that it should apply the logic related to rewards for achieving specific goals. Obviously it should do it without involving gameplay code. It should be easy to change the achievement system or drop it completely without touching gameplay logic.

The class which wants to know when something interesting happened is Observer, so let’s define it:

class Observer {
public:
  virtual ~Observer() {}
  virtual void onNotify(const Entity& entity, Event event) = 0;
};
Enter fullscreen mode Exit fullscreen mode

It is clear that we potentially want to have many observers looking for different kinds of events, so we declare Observer as an Interface.

The concrete class that implements observer is our Achievement system:

class AchievmentObserver : public Observer {
  public:
  void onNotify(Entity entity, Event event) override{
    switch (event.id) {
      case MONSTER_KILLED:
        std::cout << "MonstersKiller badge earned!!" << std::endl;
        break;
    }
  }
  ~AchievmentObserver() {}
};
Enter fullscreen mode Exit fullscreen mode

Of course we can have many more observers, let’s define one more in order to make it a bit closer to reality. When it comes to games, it is more than necessary to support sound engine, so let’s define simple sound observer:

class SoundObserver : public Observer {
  public:
  void onNotify(Entity entity, Event event) override {
    switch (event.id) {
      case MONSTER_KILLED:
        std::cout << "Apply monster kill sound" << std::endl;
        break;
    }
  }
  ~SoundObserver() {}
};
Enter fullscreen mode Exit fullscreen mode

And we also need an object that contains information about existing observers, that one is called Subject and its implementation would be like:

class Subject {
  private:
  std::vector<Observer*> observers_;

  public:
  void add(Observer* observer) { observers_.push_back(observer); }

  void notify(Entity entity, Event event) {
    for (auto& observer : observers_) {
      observer->onNotify(entity, event);
    }
  }
   void remove(Observer* observer) {}
};
Enter fullscreen mode Exit fullscreen mode

It contains a list of pointers of all observers, methods for adding and removing observers and notify() method which is used to notify observers (all in this case) about changes that have happened.

The subject communicates with the observers, but it isn’t coupled to them. In our example, no line of game code will mention achievements. Yet, it can still talk to the achievements system. That’s the clever part about this pattern. (R.N - Game Programming Patterns)

It is important that the subject contains a list of observers and not just one of them, so it won’t be overlapping between them.

And now we need to connect it all together through the Game engine, so we need to integrate Subject into the Game, so we have the ability to notify observers about interesting events.

class Game {
  private:
  Subject& subject_;

  public:
  Game(Subject& subject) : subject_(subject){};

  void killMonster(Entity entity, Event event) { 
    //Game logic
    subject_.notify(entity, event);
  }

  void completeLevel(Entity entity, Event event) {
    //Game logic
    subject_.notify(entity, event);
  }
  ~Game(){};
};
Enter fullscreen mode Exit fullscreen mode

The Game itself knows about the subject, and on interesting events, it emits notifications. Subject go through observers and give them heads up.

And basically it is about it, that is a basic pattern idea, one class that contains pointers of some other class.
It is highly overview and basic implementation, there are also other, more sophisticated implementations but in its essence that is it. For more details it is recommended to go through the book(there is also an online version) and go deeper into the subject.

The pattern also has some criticism and question marks such as “It is too slow” or “It does too much dynamic allocation”. Although those misconceptions come mainly from misunderstandings and wrong implementations, they are very well addressed but it is out of scope of this article. If you managed to develop some interest on the topic during reading, it is recommended to grab the book and find the answers to the mentioned questions.

The main engineer's occupation should be on when to use the pattern because even if it is correctly implemented on the wrong problem, it can end up making things even worse.

We use the pattern for the purpose of decoupling two pieces of code which can operate without knowing each other or any details about each other. Our achievement system doesn’t have to know anything about the game engine nor the game engine has to know anything about the achievement system, and that is where the pattern makes perfect sense. It is a real win situation because in the game logic we don’t want to mess up with achievement details and vice versa. It enables the subject to communicate to observers without being statically bound to them.

So basically, it is all about recognizing when and when not to use pattern. Observer pattern is a great way to let unrelated components to “talk” to each other without interfering.
And that is why it fits in our Game-Achievement example, they are almost independent domains potentially implemented by different people which could be treated independently.

But If you often need to think about both sides of some communication in order to understand a part of the program, don’t use the Observer pattern to express that linkage. Prefer something more explicit.

Github repository: https://github.com/almir-s/design_patterns/tree/main/observer

💖 💪 🙅 🚩
almirs
almir-s

Posted on August 18, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related