DEV Community

David Martinez
David Martinez

Posted on

Factory Method Pattern in Ruby

This is the first post in the series on design patterns in Ruby.
This series will cover different design patterns, including creational, structural, behavioral, and others.

In this post, we will address the Factory Method design pattern. We will see how it works, use cases, advantages and disadvantages,
as well as tips on cases where it is useful to implement this pattern.

What?

The Factory Method is a creational design 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 defines a method that should be used for creating objects instead of a direct call to the constructor.
Subclasses can override this method to change the classes of objects that will be created.

When?

🔸 This pattern is useful when a class cannot anticipate the class of objects it should create.

🔸 This pattern is useful when a class wants its subclasses to specify the objects it will create.

🔸 This pattern is useful when you want to provide a library of classes so that users can extend it.

🔸 This pattern is useful if you want to save resources and improve performance by reusing existing objects instead of creating new ones.

Problem

Imagine you are working on a Windows application that deploys multiple UI elements such as buttons, inputs, textareas, etc.
In the first version of the application, you create a class for each UI element, so the Dialog class stores the code to create,
manipulate, and display a dialog on the Windows OS. Your app gains popularity, so after some time, you are asked to add support for other operating systems.

Since much of the Dialog logic is based on Windows, you need to create a new class for each operating system your app supports.
Worse, if support for another operating system is requested in the future, you will have to create a new class to support the new operating system.

As a result, your code becomes difficult to maintain, as each class has similar logic but with small differences.
Additionally, every time support for a new operating system is requested, a new class must be created to support the new operating system.

Solution

The Factory Method pattern solves the previous problem by defining a method that should be used for creating objects instead of a direct call to the constructor.
Subclasses can override this method to change the classes of objects that will be created.

dialog-diagram-factory-method

Now it is possible to override the create_button method in the Dialog subclasses to create a specific button for each operating system.
However, the subclasses of the products can return different types of objects, which is why the factory method of the product base must return
a defined type as an interface.

dialog-diagram-factory-method

In this example, both WindowsButton and MacButton implement the Button interface, which declares a render method that will be implemented by the subclasses.
Each button class implements the render method differently, so that the Windows button renders a button with a Windows style, and the Mac button renders a
button with a Mac style.

🔹 The factory method in WindowsDialog returns WindowsButton objects, while the factory method in MacDialog returns MacButton objects.

🔹 The client code does not see a difference between WindowsButton and MacButton objects, since both implement the same interface.

🔹 The client treats all buttons in the same way, regardless of their concrete class.

🔹 The client knows that all buttons have a render method, but it does not know how this method is implemented in each concrete class.

How?

1️⃣ A common interface is declared for all products that will be produced by the creator and its subclasses.

2️⃣ The concrete products are different implementations of the common interface.

3️⃣ The creator declares a factory method that returns an object of the concrete products. It is important that the return type is the common interface.

4️⃣ The subclasses of the creator override the factory method to return different types of products.

💡 Note: The factory method should not create new objects all the time. Instead, the factory method should return existing objects stored in a cache or other source.

Why?

📍 The Factory Method pattern follows the Open/Closed principle, which states that classes should be open for extension but closed for modification.

📍 The Factory Method pattern allows subclasses to extend the logic of object creation without modifying the superclass code.

📍 The Factory Method pattern follows the Single Responsibility Principle, which states that a class should do only one thing and do it well.

📍 The Factory Method pattern allows subclasses to change the type of objects that will be created without modifying the superclass code.

📍 The Factory Method pattern allows subclasses to reuse the superclass code to create objects of different types.

Show me the code

# Button "Interface"
# (since interfaces does not exist in Ruby, we will use a module to define the interface)
module Button
  def render
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end

  def on_click
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

class MacButton
  include Button

  def render
    puts 'MacButton render'
  end

  def on_click
    puts 'MacButton on_click'
  end
end

class WindowsButton
  include Button

  def render
    puts 'WindowsButton render'
  end

  def on_click
    puts 'WindowsButton on_click'
  end
end


# Dialog
class Dialog
  def initialize
    @button = create_button
  end

  def render
    @button.render
  end

  def on_click
    @button.on_click
  end

  def create_button
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

class WindowsDialog < Dialog
  def create_button
    WindowsButton.new
  end
end

class MacDialog < Dialog
  def create_button
    MacButton.new
  end
end

# Client
def main
  dialog = WindowsDialog.new
  dialog.render
  dialog.on_click

  dialog = MacDialog.new
  dialog.render
  dialog.on_click
end

# Output
WindowsButton render
WindowsButton on_click
MacButton render
MacButton on_click
Enter fullscreen mode Exit fullscreen mode

Conclusion

The Factory Method is a creational design 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 useful when a class cannot anticipate the class of objects it should create.

🔺 This pattern is useful when a class wants its subclasses to specify the objects it will create.

🔺 This pattern is useful when you want to provide a library of classes so that users can extend it.

🔺 This pattern is useful if you want to save resources and improve performance by reusing existing objects instead of creating new ones.

References

💻 You can find this and other design patterns here 📚

Top comments (0)