Clean Architecture: Add observer pattern and conclusion

This commit is contained in:
rubenwardy 2018-07-15 16:51:37 +01:00
parent 5ee799b76b
commit bc58dce630
No known key found for this signature in database
GPG Key ID: A1E29D52FF81513C

View File

@ -20,6 +20,9 @@ There is no one good way of designing a mod, and good mod design is very subject
* [Cohesion, Coupling, and Separation of Concerns](#cohesion-coupling-and-separation-of-concerns) * [Cohesion, Coupling, and Separation of Concerns](#cohesion-coupling-and-separation-of-concerns)
* [Model-View-Controller](#model-view-controller) * [Model-View-Controller](#model-view-controller)
* [API-View](#api-view) * [API-View](#api-view)
* [Observer](#observer)
* [Conclusion](#conclusion)
## Cohesion, Coupling, and Separation of Concerns ## Cohesion, Coupling, and Separation of Concerns
@ -172,7 +175,7 @@ you tend to see a lot more of a less formal and strict kind of design -
varients of the API-View. varients of the API-View.
## API-View ### API-View
In an ideal world, you'd have the above 3 areas perfectly separated with all In an ideal world, you'd have the above 3 areas perfectly separated with all
events going into the controller before going back to the normal view. But events going into the controller before going back to the normal view. But
@ -192,3 +195,61 @@ are views for each type of thing.
Separating the mod like this means that you can very easily test the API part, Separating the mod like this means that you can very easily test the API part,
as it doesn't use any Minetest APIs - as shown in the as it doesn't use any Minetest APIs - as shown in the
[next chapter](unit_testing.html) and seen in the crafting mod. [next chapter](unit_testing.html) and seen in the crafting mod.
## Observer
Reducing coupling may seem hard to do to begin with, but you'll make a lot of
progress by splitting your code up well using a design like the one given above.
It's not always possible to remove the need for one area to communicate with
another, but there are ways to decouple anyway - one such way being the Observer
pattern.
Let's take the example of unlocking an achievement when a player first kills a
rare animal. The naive approach would be to have achievement code in the mob
kill function, checking the mob name and unlocking the award if it matches.
This is a bad idea however, as it makes the mobs mod coupled to the achievements
code. If you kept on doing this - for example, adding XP to the mob death code -
you could end up with a lot of messy dependencies.
Enter the Observer pattern. Instead of the mobs mod caring about awards, mobs
exposes a way for other areas of code to register their interest in an event
and receive data about the event.
{% highlight lua %}
mobs.registered_on_death = {}
function mobs.register_on_death(func)
table.insert(mobs.registered_on_death, func)
end
-- mob death code
for i=1, #mobs.registered_on_death do
mobs.registered_on_death[i](entity, reason)
end
{% endhighlight %}
Then the other code registers its interest:
{% highlight lua %}
-- awards
mobs.register_on_death(function(mob, reason)
if reason.type == "punch" and reason.object and reason.object:is_player() then
awards.notify_mob_kill(reason.object, mob.name)
end
end)
{% endhighlight %}
You may be thinking - wait a second, this looks awfully familiar. And you're right!
The Minetest API is heavily Observer based to stop the engine having to care about
what is listening to something.
## Conclusion
Good code design is subjective, and depends on the project you're making. As a
general rule, try to keep cohesion high and coupling low. Phrased differently,
keep related code together and unrelated code apart, and keep dependencies simple.
I highly recommend reading the [Game Programming Patterns](http://gameprogrammingpatterns.com/)
book. It's freely available to [read online](http://gameprogrammingpatterns.com/contents.html)
and goes into much more detail on common programming patterns relevant to games.