Strategy and Decorator Design Patterns in Ruby

If you haven’t looked at too many class diagrams, or you are only familiar with dynamically typed languages, it can be confusing.

Each box with a bold title represents a class.

In Ruby, we won’t actually be programming all of the classes we would in Java or C++, for example.

All of the boxes which do not say Concrete are the architecture of our inter-class APIs that we are designing.

In statically typed languages, we have to define these in code as Interfaces, Prototypes, Abstract Classes, etc.

depending on the language.

In dynamically typed languages, they are defined in the documentation and in our minds as the engineers of the class ecosystem.

There are ups and down to both statically and dynamically typed languages; statically typed classes error check for you, help you be more precise and cleaner in your code, and ensure you are designing your system correctly, while dynamically typed languages require less code and can be a lot simpler to create and read IF designed well and properly documented.

If designed badly, they can be much harder to read and debug.

But now getting back to the Decorator Class Diagram; basically, this diagram tells us that we have a classConcreteComponent and we want to extend its functionality.

This class has the functions and properties as defined by Component.

So, we make another class with the same API as ConcreteComponent (i.

e.

it conforms to the Component design) except that it has one extra reference: a Component(which is going to be the original class you wanted to extend the functionality of or any decorator it is already wearing), and we call this new design Decorator.

We can make as many different types of Decorator classes, as long as it adheres to this design.

Above you can see that the Component can do an Operation().

So, our Decorator also has to be able to do anOperation().

Since our Decorator has a reference to Component, it just looks at how Component does Operation(), mimics that output, and then changes what it needs to and returns this new-and-improved output from its Operation() method.

That is what the dotted lines are showing you above.

Pseudo-Code:class NumberComponent func operation(x){ return x }endclass BinaryNumberDecorator constructor(component){ this.

c = component } func operation(x){ return this.

c.

operation(x).

toBinary() }endc = new NumberComponent()d = new BinaryNumberDecorator(c)x = 2c.

operation(x) // outputs what your original class returnsd.

operation(x) // extends functionality of NumberComponent using the same function callI’ll show more complex, working ruby code below.

StrategyStrategy Class DiagramHere, the Context is the class I want to extend the functionality of.

So, I make a Strategy class that can perform that function.

It is interchangeable because each ConcreteStrategy conforms to the same Strategy interface.

Note that unlike the Decorator, the Strategy does not need to share an interface with Context.

This design pattern is convenient when the functionality I want is complex and can be implemented with different algorithms.

Let’s say I am creating a class which needs to perform the Fast Fourier Transform (FFT).

Since the FFT can be calculated in different ways, and I may want to switch to a faster/better algorithm in the future, I can implement a Strategy to perform the calculation.

Pseudo-Code:class DataContext constructor(data, fftStrategy = new Radix2Strategy()){ this.

data = data this.

fftStrategy = fftStrategy } func fft(){ return this.

fftStrategy.

fft(this.

data) }endclass BruteForceFFTStrategy func fft(data){ .

perform brute force fft return calculated_fft }endclass Radix2Strategy func fft(data){ .

perform radix 2 strategy return calculated_fft }endIntuitionDecorators are kind of like Russian nesting dolls.

You take an object you want and nest it inside of your class which uses the inner functionality and adds onto it to makes it more functional.

So actually, decorators are more like in MIB when the little Arquillian is controlling the humanoid body inside the head.

He is like the original Component and the body is like a Decorator.

He could then decorate himself further by having his human decoration operate some sort of exoskeleton.

At that point, the little alien and his exterior would have the same methods, like moveArm() or push(), but the decorated exoskeleton would have a much different output (i.

e.

he could push harder).

As such, the decorator has to have the same interfaces as the underlying class.

It eats the original Component and allows the world to interact with it the same way it would interact with the original Component.

Strategies, on the other hand, are kind of like replaceable cards in a robot.

Imagine you pulled off the back plate and there were slots with digital cards inserted for each function such as eating, walking, talking, etc.

You could take any of them out and replace them when an algorithm was updated or you wanted to change the behavior of the robot.

In general, if you are wondering whether to use a Strategy or Decorator design pattern, a rule of thumb is to keep the base class simple in a decorator.

If the Component is too complicated, you will have to mirror too many methods in each decorator, and it will be better to use a strategy.

Also, decorators are widely used when you want to dynamically add functionality to an object, not necessarily an entire class (and perhaps withdraw this functionality at a later time).

Strategies are commonly used to perform a function which is very complicated and can be performed in different ways (perhaps with different time/space complexities).

Code ExampleDecorators:Strategies:That covers the basics.

You can see more in-depth descriptions and discussion in the reference below.

References:[1] DESIGN PATTERNS Elements of Reusable Object-Oriented Software, Gamma, Helm, Johnson, Vlissides.

. More details

Leave a Reply