In the previous article, we examined some common coding anti-patterns used by programmers new to Ruby. This article will explore some of the design anti-patterns Ruby Rookies often apply to their solutions, offering some alternatives using of one Ruby’s most used constructs: the Module.
The Factory Fallacy
Developers who come to Ruby from Java tend to have a particular fondness for factory classes and methods. Many new Ruby-ists will write their factories like this:
class Shape def initialize(*args) # code for dynamically creating attributes from args list end def draw raise "not allowed here" end end class Triangle < Shape def draw "drawing triangle" end end class Square < Shape def draw "drawing square" end end # ...more shapes here class ShapeFactory def self.build(shape, *args) case shape when :triangle Triangle.new(*args) when :square Square.new(*args) when :circle Circle.new(*args) end end end
They can now decide on the fly what kind of shape they want to create, using a common constructor method.
puts ShapeFactory.build(:triangle, 3, 2, 45) puts ShapeFactory.build(:square, 5)
There’s nothing seriously wrong with this approach, apart, of course, from the fact that it’s totally unnecessary. The clues are there: we have this
Shape base class that doesn’t really add any value to our code other than to serve the class hierarchy. Also, the fact that we have a separate class (
ShapeFactory) for something that any Ruby class can easily do by itself (i.e. dynamically create instances of itself) leaves a bad taste in the experienced Ruby-ist’s mouth. This design style is often followed by proponents of class-oriented languages like C# or Java, where everything has to fit in a class hierarchy. Ruby, on the other hand, is object-oriented, so everything —even classes— are objects and class hierarchies are not always necessary. With that in mind, we can think about the factory pattern like this:
We want to create objects of a generic ‘type’, but have object-specific behaviour.
We want to create specialized objects in a abstracted manner.
Modules as Object Decorators
A Ruby Module sits somewhere between Java’s Interface and C#’s Abstract Class, but is more flexible than either of them. Let’s re-design our Shapes solution using modules:
class Shape def initialize(*args) end end module Triangle def draw "drawing triangle" end end module Square def draw "drawing square" end end
We can extend a Shape object with some specialized behavior:
triangle = Shape.new( 3, 2, 45).extend(Triangle) square = Shape.new(5).extend(Square)
We’re now dynamically decorating our shapes with the behavior we need, so our triangle is a Shape the behaves like a Triangle
puts triangle.draw => drawing triangle
In the process, we’ve done away with the class hierarchy, the Factory class, and produced cleaner and leaner code. Sweet.
‘Type’ Withdrawal Symptoms
Some people may feel uneasy with the fact that a triangle is a Shape that behaves like a Triangle, not a ‘real’ Triangle:
p triangle => #<Shape:0x00000000956d98>
If you’re one of these people, rest assured: Ruby is flexible enough to accommodate anyone’s needs. We can easily track ‘type’ using Module’s hook methods:
class Shape attr_accessor :type def initialize(*args) @type =  end end module Triangle def draw "drawing triangle" end def self.extended(mod) mod.type << :Triangle end end
Now we can tell exactly what ‘type’ we’re dealing with:
triangle = Shape.new( 3, 2, 45).extend(Triangle) puts triangle.type => Triangle
You may have noticed that the
type attribute is an Array. This is because we can potentially extend a Shape with more than one Module. The following is semantically correct although conceptually nonsensical:
my_shape = Shape.new( 3, 2, 45).extend(Triangle).extend(Square) puts my_shape.type => Triangle Square
The catch here is that the newest module extension will override any existing methods with the same name. So calling the
draw method on our shape will now draw a Square. Ruby gives us great power, but it’s up to us to use it sensibly.
I^3^ (Inheritance Inhibits Implementation)
Learn PHP for free!
Make the leap into server-side programming with a comprehensive cover of PHP & MySQL.
RRP $11.95 Yours absolutely free
Let’s re-visit our rookie Shape design. This time, we need to be able to create 3-dimensional shapes, as well as 2D ones. The most basic approach would be this:
Although this design works, it presents us with a maintenance problem. Namely, it effectively doubles our code-base. Not only do we have twice the number of shapes to maintain, but our Factory also doubles in size. More observant Rookies will try to mitigate this problem by noticing that many 3D shapes are just 2D shapes extended along the Z-axis. A Cube is just a 3D Square, a Cylinder is but a 3D Circle, and so on. So they may add an extra method into the 2D shapes that transforms them into 3D.
This approach will certainly trim down the class hierarchy and save some coding, but it presents a new set of problems:
- If we have the
#transformmethod in our base class, then every derived class will carry this method even if it can’t use it (i.e.Pyramid) so we get redundancy in our design.
- We can eliminate redundancy by adding the
#transformmethod only to the classes that need it, but then we end up with a lot of duplication.
- We are likely to break the Liskov Substitution principle (that’s the L in SOLID Design Principles)). By transforming a 2D Shape into a 3D one we invalidate its
drawmethod, which means that we can’t substitute a 2D with a 3D object unless we override
These problems are caused by the core issue that, although the
draw behavior applies to all types of shapes, its implementation is fundamentally dependent on the shapes’ dimensions. There seems to be no way to overcome these without returning to our previous multi-branch class design. It appears we can’t have lean, flexible code while correctly modeling our problem domain. Or can we? Once again, Modules come to the rescue.
We will use the
ThreeD module to ensure our Shapes have the correct behavior. When we extend a shape object with it, the
ThreeD module will inject the correct implementation of the
#draw method into the object, overriding the existing implementation. We’re turning a previous weakness to our advantage:
class Shape attr_accessor :type def initialize(*args) @type =  end end module Triangle def draw "drawing triangle" end def self.extended(mod) mod.type << :Triangle end end module Square def draw "drawing square" end def self.extended(mod) mod.type << :Square end end module ThreeD def self.extended(mod) mod.type << :ThreeD case mod.type.first when :Triangle mod.instance_eval do def draw(depth) puts "drawing a Wedge" end end when :Square mod.instance_eval do def draw(depth) puts "drawing a Box" end end end end end
The ThreeD module uses the
type attribute to determine what type of Shape it is extending and dynamically creates the appropriate
draw method for it. Any other methods already mixed-in by previous Modules remain in place. Check it out:
sq = Shape.new.extend(Square) puts sq.draw => drawing square sq.extend(ThreeD) puts sq.draw(4) => drawing a Box puts sq.type => Square ThreeD
This way, a shape has only the behavior it needs and only when it needs it. No duplication, no redundancy and a SOLID design.
Inheritance and factory-based designs are necessary (sometimes, the only) design choices in many languages. However, they’re not always the best way to model certain real life problems. Ruby is a multi-paradigm language and, as such, offers some more creative design alternatives. In this article, we used Modules and Ruby metaprogramming techniques to eliminate factories and complex or inadequate class hierarchies. I hope you enjoyed it.
Are there any anti-patterns that you find in code often? How do you mitigate these anti-patterns?
Fred is a software jack of all trades, having worked at every stage of the software development life-cycle. He loves: solving tricky problems, Ruby, Agile methods, meta-programming, Behaviour-Driven Development, the semantic web. Fred works as a freelance developer, and consultant, speaks at conferences and blogs here .
Jump Start Git, 2nd Edition
Visual Studio Code: End-to-End Editing and Debugging Tools for Web Developers