TL;DR;
Use this pattern if you have multiple similar classes, which differ in some logic implementation; your code contains if-else's to determine a proper algorithm to use.
Second pattern in a row targets to eliminate if-else's where possible - nice.
Definition
Strategy pattern allows separating algorithms from the clients using them.
We have an object of type Context, which holds a reference to an IStrategy object. Context passes some work to this IStrategy object.
Sounds pretty much the same as State Pattern, but the strategy is set once at the Context instantiating time (while state is changing after Context is instantiated).
Implementation
Let's write some code.
We still have a Player, but this time we're more interested in its codecs.
So we would end up with something like this:
export default class Player {
private paramA: number;
private paramB: number;
private paramC: number;
constructor(private codecType: AlgorithmType, private stream: Stream) {
// Setting up params
}
play() {
const algorithm = this.getAlgorithm();
switch (algorithm) {
case 'A':
this.decodeA();
break;
case 'B':
this.decodeB();
break;
case 'C':
this.decodeC();
break;
}
console.log('>>>> Play');
}
private getAlgorithm(): AlgorithmType {
// Some logic happens here to determine appropriate algorithm.
return this.codecType;
}
private decodeA(): Stream {
// Actual algorithm to decode the stream in some way
console.log('>>>> Decoding with A', this.paramA);
return this.stream;
}
private decodeB(): Stream {
// Actual algorithm to decode the stream in some way
console.log('>>>> Decoding with B', this.paramB);
return this.stream;
}
private decodeC(): Stream {
// Actual algorithm to decode the stream in some way
console.log('>>>> Decoding with C', this.paramC);
return this.stream;
}
}
We have all different ways of decoding a stream inside the Player class. We have a switch statement to determine which particular algorithm we should use. Not that bad right now, but imagine a real life situation where decode functions could contain tens of lines of code.
What Strategy suggests is:
- create an interface to describe the strategy class
- move each decoding algorithm into a standalone strategy class
- add a reference to the particular instance to you Player
Well, let's do all these with one small change: let's not create a class for each algorithm. Look at this:
interface Strategy {
decode(stream: Stream, context: DecodeContext): Stream;
}
class StrategyA implements Strategy {
decode(stream: Stream, context: DecodeContext) {
// implementation
}
}
We're in the TypeScript (JavaScript) world, functions are the first class citizens, we can pass them everywhere, we don't need to create a class just to have one function in it.
So this is what our pattern implementation might look like:
export default class Player {
private paramA: number;
private paramB: number;
private paramC: number;
constructor(private decode: Decode, private stream: Stream) {
// Setting up params
}
play() {
this.decode(this.stream, {
a: this.paramA,
b: this.paramB,
c: this.paramC,
});
console.log('>>>> Play');
}
}
And all decoding algorithms now live in their own functions:
export const decodeA: Decode = (stream: Stream, { a }: DecodeContext) => {
// Actual algorithm to decode the stream in some way
console.log('>>>> Decoding with A', a);
return stream;
};
export const decodeB: Decode = (stream: Stream, { b }: DecodeContext) => {
// Actual algorithm to decode the stream in some way
console.log('>>>> Decoding with B', b);
return stream;
};
export const decodeC: Decode = (stream: Stream, { c }: DecodeContext) => {
// Actual algorithm to decode the stream in some way
console.log('>>>> Decoding with C', c);
return stream;
};
What will it cost?
Potential Cons:
- We might increase the number of files, which some might consider a bad thing
- As we define the Decode interface for every algorithm we're passing all the params each algorithm might need. Less then ideal
Potential Pros:
- We got rid of the switch statement
- We separated concerns by moving algorithms implementations outside the Player class
Conclusion
I do like separating concerns and avoiding conditionals when possible, so this pattern seems really useful to me so far.
Hope this was a bit helpful 🙃
Source code if needed
Top comments (0)