2

Suppose I have a Player that wants to build a Tower in the Board of a tabletop game.

My first approach to this would have been something like:

function onConfirm() {
  player.consumeResources();
  board.addTower();
}

Meaning this event knows the interfaces of player and board so it has some level of coupling.

If the observer pattern was used, with an observable buildTower, to which Board and Player were subscribed to in some initialization procedure:

class Player() {
  onTowerBuilt() {
    this.decreaseResources();
  }
}

class Board() {
  onTowerBuilt() {
    this.addTower();
  }
}

function onConfirm() {
  buildTower.notify();
}

Player and Board each receive the notification, and so the logic for discounting player resources is in the player, and for placing the tower in the board is in the board. Which from my persepective reduces coupling and increases cohesion.

So after this, some questions:

  1. Is this a good use of the observer pattern?

  2. If before this event, a check was needed for seeing whether the player has enough resources to build a tower, what would be a good way of doing so?

2

The observer pattern is definitely nice for decoupling the interface events from the game logic, but aside from that, you probably want a single listener that has enough information to process the event entirely. I would rather avoid events to be fired inside the game logic, they tend to obfuscate execution path.

Whether or not Player should handle that is unclear, but if checking the player's ressources is necessary, you would end up with something more similar to this:

function onConfirm() {
  requestTower.notify();
}
class Player() {
  onTowerRequest() {
    if this.hasResources(){
        this.decreaseResources();
        this.board.addTower();
    }
  }
}

There are design flaws with this. This imply the Player knows about Board. And if we think about it, this event processing has little reason to belong to an actual Player object, if Player is used as a game container for player-related information.

So I usually group the I/O handling to a higher level class, call it for example Game. It makes it logical for this class to have both Player and Board, and dispatch the processing to them accordingly.

function onConfirm() {
  requestTower.notify();
}
class Game() { 
  onTowerRequest(){ 
      // May be necessary to solve who is requesting player here
      if player.hasResources(){
          player.decreaseResources();
          this.board.addTower();
      }
  }
}
  • This is the most intuitive approach, that I've implemented before as well, however, isn't this Game class tightly coupled with Player and Board? what would be your justifications for coupling the system in this way? – Mateo de Mayo Sep 18 at 1:37
  • @MateodeMayo Well, the design being intuitive is already one thing, another reason it is somewhat more "bearable" is that dependencies goes in a hierarchical way. In the end if you successfully implement Game you would have no cross-dependency between object it contains, although it's often difficult because game logic itself is often difficult to split. Last, coupling is one thing, but there are also many things to consider in design, and this one have a clear advantage of segregating roles without sacrificing readability. – Arthur Havlicek Sep 18 at 7:01
2

This seems a nice usage of the observer pattern, that in theory decouples somewhat the different kinds of objects. However, I’m not sure that the scenario is sufficient.

Suppose you’d have smallTower and bigTower with different resource needs. The orchestration according to your scenario would require to know what kind of tower is to be build, create the tower object (which knows the resources needed), then check if the player has enough resources and reserve them, then subscribe to the tower notifications, build the tower, and notify the elements that the tower is build to consume the reserved resources.

As you see there is in fact in this scenario an interdependence between tower, player and board before the notification can happen. Especially considering that the tower belongs to the player. It is moreover weird to have to subscribe to the new object before being sure that it can be build.

I’d therefore suggest to consider the use of the builder pattern (attention: the GoF builder, and not Bloch’s builder pattern). The responsibility of that object would be to verify that the building is feasible and then orchestrate the interrelation between the board, the tower and the player.

This builder would have to know the interface of the 3 objects and would depend on them. But the other objects would remain as loosely coupled as possible.

You could still foresee an observer for all the events happening to the tower that might be of interest (tower is finished, tower is damaged, tower is destroyed).

  • Even though my specific scenario was about creating a Tower, I can see this kind of interdependence when be a problem in other parts of the system, e.g. "Player wants to improve a Tower", how would this method accomodate to those situations? and how would it be different from having a Game class as proposed by Arthur in the other answer? – Mateo de Mayo Sep 18 at 1:32
  • 1
    @MateodeMayo I was addressing the specific case of creation because the responsibilities cannot be put on an object which is not yet there. I assumed that the responsibilities could be properly divided between objects for your event logic to work as-is. If this is not the case, you could hand over the interaction coordination of game actions to a concrete mediator. I'd tend to have one concrete mediator for each different action (separation of concerns). In Arthur's approach the Game object seems to be a single big mediator that might risk to become a god object difficult to maintain. – Christophe Sep 18 at 6:26
2

This is a very good candidate for use of the observer pattern because it does decrease coupling in your case and, in my opinion, it makes the whole concept more elegant.

Your problem is very valid and, of course, it can occur in many different scenarios and across a large variety of domains. Domain events may, or may not, take place actually at the end. The most typical and elegant (again, in my opinion) solution to the problem of almost-occurring events comes after you realize that your event is not one event, actually, but two!

You need two distinct events happening sequentially in the same handler, and a structure that will be passed along with the events to all listeners, so they can communicate potential problems back to the handler. The first event signifies validation logic, and the second event is the actual domain event occurrence.

I am sorry for any potential mistakes, but I am not entirely sure what your language is. I hope the following can adequately illustrate the main idea:

class Player() {
    onBuildTowerRequested(eventFeedback) {
      if (!this.hasResources)
      {
          eventFeedback.Cancel();
      }
    }

    onBuildTowerConfirmed() {
        this.decreaseResources();
    }
}

class Board() {
    onBuildTowerRequested(eventFeedback) {
      //Anything to validate?
    }    

    onBuildTowerConfirmed() {
      this.addTower();
    }
}

function onBuildTowerRequested(eventFeedback) {
    if (eventFeedback.cancel != true)
    {
        //Fire the confirmation event.
        //buildTower.notify() should NOT be simply called here because
        //listeners have additional logic to perform upon confirmation.
        //It is imperative that the onConfirmed event is fired as well.
    }
    //If the eventFeedback.cancel is true, do NOT fire the onConfirmed
    //event. This way, validation logic is decoupled from domain event
    //logic and you acquire wider flexibility in the range of scenarios
    //you can implement.
}

function onBuildTowerConfirmed() {
    //This will only be fired if no participating listener has cancelled the validation.
    buildTower.notify();
}

In common practice, events are paired as onValidating something and onValidated something, although you may name them as it best suits your domain and implemented logic. These two occur sequentially and one immediately after the other (onValidating and then onValidated, obviously), and all listeners have a chance to cancel the onValidating event through a passed feedback structure (the event-arguments). After the onValidating event, if nobody has requested a cancel (because of insufficient resources or for any other reason), the onValidated event is raised, to which all ensuing changes are declared to be inevitable, but have also been verified as possible to occur! On the other hand, if the onValidating event has its feedback structure's cancel variable set to true, the onValidated event will not be fired/raised.

Notice that, in my example above, I assumed the eventFeedback structure uses a method to notify cancel, rather than direct access to a bool cancel, for example. This is to avoid problems such as, for example, having the 2nd of 5 listeners change the cancel to true, and the 5th listener changing it to false again (by mistake), which would introduce a hard-to-find bug, let alone it would make the order of event "listenings" matter. If you employ this, make sure you use a method and make the cancel variable read-only, so if any of the listeners cancels, the cancel is irreversible. You can also make the listeners check for cancel first, so that they avoid performing expensive validation if the validation has already been canceled by a previous listener.

  • I can see this method being useful for recurring types of chained notifications, it seems a little convoluted for something that is very easily resolved with something like a Game class that is very coupled to Player and Board, is the effort for reducing coupling worth it in this case? – Mateo de Mayo Sep 18 at 1:35
  • Based on available time and task complexity, a more tightly-coupled solution might be better. It always comes down to how extensible you want your design to be. In any case, you generally have to judge based on your own use case. If this helps, you can think of validation-enabled events as events, during which many things may need to be checked before establishing that they actually can take place. If such a "double"-event is in place, you can include additional independent "participants" in the validation process of the event easily later, rather than work out a way to "couple them in". – Vector Zita Sep 18 at 1:49
  • Apart from that, such "validation-enabled" events are not really that convoluted. It's just 2 events instead of 1. The 1st invites all listeners to perform validation (so each listener knows what they validate), the 2nd runs only if this validation is successful. This way, an event that depends on multiple listeners "agreeing" saves you from having to make them coexist in some scope. So it's having two events instead of one for every domain event (I don't think it's that bad), compared to having to couple every new-coming "listener" with an external "know-them-all" (e.g. Game) class. – Vector Zita Sep 18 at 1:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.