Author: David Martinez
Created: April 12, 2024 - Updated: April 16, 2024
Read Time: 10 min
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.
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.
πΈ 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.
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.
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.
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.
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.
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.
π 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.
# Button "Interface"# (since interfaces does not exist in Ruby, we will use a module to define the interface)module Buttondef renderraise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"enddef on_clickraise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"endendclass MacButtoninclude Buttondef renderputs 'MacButton render'enddef on_clickputs 'MacButton on_click'endendclass WindowsButtoninclude Buttondef renderputs 'WindowsButton render'enddef on_clickputs 'WindowsButton on_click'endend# Dialogclass Dialogdef initialize@button = create_buttonenddef render@button.renderenddef on_click@button.on_clickenddef create_buttonraise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"endendclass WindowsDialog < Dialogdef create_buttonWindowsButton.newendendclass MacDialog < Dialogdef create_buttonMacButton.newendend# Clientdef maindialog = WindowsDialog.newdialog.renderdialog.on_clickdialog = MacDialog.newdialog.renderdialog.on_clickend# OutputWindowsButton renderWindowsButton on_clickMacButton renderMacButton on_click
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.
π» You can find this and other design patterns here π