Highlight - Template method pattern
One of my favorite design patterns.
It’s excellent to abstract changing behavior and keep common behavior seperated.
The code reuse and cohesion are high while the coupling suffers a bit since most implementations use inheritance to implement it.
In general sense inheritance has higher coupling value than composition.
Template method pattern
A behavior pattern.
The template method pattern uses a method to abstract away a behavior and reuse a common behavior.
A good guideline to follow:
Detect changing and static behavior, and abstract the dynamic parts of the code.
This kind of reuse allows us to simplify and seperate concerns to different behavior units.
Diagram and usage
There are couple of ways to implement it depending on your language of choice.
In classes hierarchy - a common method is used while one or more methods are abstracted/virtualized.
In this example the method B is overriden in ClassB and ClassC.
However methods A and C are the common behavior.
Common behavior and changing behavior
Behavior may change or stay static.
If we know a behavior may change depending on runtime, configuration or environment we need to properly set our code for these changes.
Reading source data
We want to abstract getting data for the server’s behavior.
We need to translate Info into Data when returning.
1 | class Data { /* Some data*/}; |
From a common source we may derive and implement different sources:
DBSource:
1 | class DBSource : public Source |
GRPCSource
1 | class GRPCSource : public Source |
When a behavior is static or non changing we don’t need to abstract it.
For example in the source we get data of Info but we need to return Data class.
This behavior doesn’t change - only the source of the Info changes:
Source:
1 | class Source |
But if the behavior changes we should abstract it and create a method/interface.
In case of a method - this method is a template method.GetData is the template method, note that it’s not public.
1 | class Info {}; |
Now the implementors will need to provide how to GetData.
1 | class DBSource : public Source |
Example - Designing a spell system
Different types of spells will cause different effects however we also have common behavior.
- Healing spells - would add HP to the player.
- Support spells - Add an effect or heal others.
- Attack spells - Will cause damage to the opponent.
The code will be written in C#.
We’ll focus on the AttackSpell.
GetDamage is the template method while ReduceShield is also a template method but take note that one is abstract and the other is virutal.
In this practice we can choose to implement a basic implementation or keep it unknown.
If spells decide to act differently on magic shields they can.
For exmaple we may want to implement a powerful IgnoreShieldAttackSpell variant.
1 | public interface ISpell |
The common behavior:entity receives damage according to how much spell shield it has.
The changing behavior:
Damage casted by the deriving attack spells is reduced from the entitys state.
FirBallSpell:
The FireBallSpell will cause more damage if the entity is type of Nature.
1 | public class FireBallSpell : AttackSpell |
And we can implement different bonus effects according to states:
- The entity uses wooden weapons.
- The entity is weak to fire damage.
- etc…
Alternatives
When behavior changes but changes too much we need to decide if we want to alternate the implementation.
The most common way to abstract a behavior is to use an interface,
In this sense we implement Strategy pattern instead.
Another 2 alternatives depending on your implementation may be:
- Mediator pattern.
- Visitor pattern.
If these don’t suit you, consider other behavior patterns.
Thank you for reading!