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