Solving Design Anti-Patterns in Ruby: Fix the Factory

Share this article

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)
factory_classic

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.

Or, even:

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)
object_decorator

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)

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:

hierarchy_1

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.

class hierarchy 1

This approach will certainly trim down the class hierarchy and save some coding, but it presents a new set of problems:

  1. If we have the #transform method 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.
  2. We can eliminate redundancy by adding the #transform method only to the classes that need it, but then we end up with a lot of duplication.
  3. 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 draw method, which means that we can’t substitute a 2D with a 3D object unless we override #draw first.

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.

Method Injection

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
method_injector

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.

Summary

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?

Frequently Asked Questions (FAQs) about Design Anti-Patterns in Ruby

What is a design anti-pattern in Ruby?

A design anti-pattern in Ruby, or any other programming language, is a common solution to a problem that tends to produce negative outcomes. It’s a “bad” design pattern that can lead to code that is difficult to understand, maintain, or scale. While it may seem like a good solution in the short term, it often leads to more problems down the line.

How can I identify a design anti-pattern in my Ruby code?

Identifying a design anti-pattern in your Ruby code can be challenging, especially if you’re not familiar with the concept. However, there are a few signs you can look for. If your code is difficult to understand, maintain, or scale, it may be using an anti-pattern. Other signs include code that is overly complex, tightly coupled, or not reusable.

What is the Factory design pattern in Ruby?

The Factory design pattern in Ruby is a creational pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. This pattern is particularly useful when a class cannot anticipate the type of objects it needs to create or when a class wants its subclasses to specify the objects it creates.

How can I fix the Factory design anti-pattern in Ruby?

Fixing the Factory design anti-pattern in Ruby involves refactoring your code to use the Factory design pattern correctly. This typically involves creating a Factory class or method that is responsible for creating objects, rather than having this logic scattered throughout your code. This can make your code more maintainable, scalable, and easier to understand.

What are the benefits of using the Factory design pattern in Ruby?

Using the Factory design pattern in Ruby can have several benefits. It can make your code more maintainable, as it centralizes the object creation process. It can also make your code more scalable, as it allows you to easily create different types of objects without changing the underlying code. Additionally, it can make your code easier to understand, as it separates the concerns of object creation and object usage.

Are there any drawbacks to using the Factory design pattern in Ruby?

While the Factory design pattern in Ruby can have many benefits, it’s not without its drawbacks. One potential drawback is that it can lead to more complex code, as you need to create a separate Factory class or method. Additionally, it can make your code harder to understand if it’s not implemented correctly.

Can I use the Factory design pattern in other programming languages?

Yes, the Factory design pattern is not specific to Ruby. It’s a common design pattern that can be used in many object-oriented programming languages, including Java, C++, and Python. The implementation may vary slightly depending on the language, but the underlying concept is the same.

What are some other common design anti-patterns in Ruby?

There are many other common design anti-patterns in Ruby, including the Singleton anti-pattern, the God Object anti-pattern, and the Spaghetti Code anti-pattern. These anti-patterns can lead to code that is difficult to understand, maintain, or scale, and should be avoided whenever possible.

How can I avoid using design anti-patterns in my Ruby code?

Avoiding design anti-patterns in your Ruby code involves understanding what these anti-patterns are and why they’re problematic. It also involves learning and using good design patterns, like the Factory design pattern, that can help you write better code. Additionally, regularly reviewing and refactoring your code can help you spot and fix any anti-patterns.

Where can I learn more about design patterns and anti-patterns in Ruby?

There are many resources available for learning about design patterns and anti-patterns in Ruby. Online tutorials, blogs, and forums can provide a wealth of information. Additionally, there are several books available on the subject, including “Design Patterns in Ruby” by Russ Olsen and “Refactoring: Ruby Edition” by Jay Fields, Shane Harvie, and Martin Fowler.

Fred HeathFred Heath
View Author

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.

GlennGobject oriented programming
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week