LibraryAbout

๐ŸŽจ Exploring the Abstract Factory Design Pattern in Ruby


author

Author: David Martinez

Created: May 6, 2024 - Updated: May 8, 2024

Read Time: 10 min

Ruby

Welcome! Today, let's embark on a journey into the Abstract Factory design pattern. We'll dive deep into its workings, explore real-world scenarios, and unveil its magic!

Unveiling the Abstract Factory Pattern โœจ

The Abstract Factory is a creational design pattern that furnishes an interface for creating families of related or dependent objects without specifying their concrete classes.

This pattern revolves around a super-factory, known as an Abstract Factory, which generates other factories. These factories, in turn, produce objects related to specific families.

When to Use the Abstract Factory Pattern?

๐Ÿ”ธ Use this pattern when your code needs to interact with various families of related products, yet you prefer not to bind it to the concrete classes of those products.

๐Ÿ”ธ It's beneficial when providing a library of classes for users to extend.

Problem Scenario

Consider developing a native application intended for multiple operating systems like Windows, Mac, and Linux. This application incorporates various UI elements such as buttons, inputs, text areas, and dialogs.

Rendering these UI elements in accordance with each operating system's style poses a challenge. Additionally, avoiding code alterations whenever a new OS is supported is crucial.

Solution

The Abstract Factory pattern suggests creating separate factory interfaces for each UI element type, like buttons, inputs, text areas, etc. Each factory interface defines methods to create these UI elements, returning abstract products such as Button, Input, Textarea, etc.

UML Diagram

Further, interfaces are declared for each distinct product of the UI elements, such as Button, Input, Textarea, etc., with common methods. Concrete factories implement these interfaces and produce concrete products like WindowsButton, WindowsInput, WindowsTextarea, MacButton, MacInput, MacTextarea, etc. In this example, the Abstract Factory pattern is utilized to create UI elements for Windows and Mac operating systems.

๐Ÿ”น The Abstract Factory interface declares factory methods to create UI elements like createButton, createInput, createTextarea, etc.

๐Ÿ”น Concrete factories implement these factory methods to create OS-specific UI elements.

๐Ÿ”น Concrete products implement UI element interfaces like Button, Input, Textarea, etc.

๐Ÿ”น Client code utilizes the Abstract Factory interface to create UI elements without knowledge of their concrete classes.

๐Ÿ”น All UI elements are treated uniformly by client code, regardless of their concrete class.

๐Ÿ”น Client code is aware of the render method in all UI elements but doesn't concern itself with how it's implemented in each concrete class.

How does it Work?

1๏ธโƒฃ Concrete products implement UI element interfaces like Button, Input, Textarea, etc.

2๏ธโƒฃ Concrete factories implement the Abstract Factory interface, producing concrete products tailored to specific OSes.

3๏ธโƒฃ The Abstract Factory interface declares factory methods for creating UI elements like createButton, createInput, createTextarea, etc.

4๏ธโƒฃ Abstract factories spawn concrete factories that adhere to the Abstract Factory interface.

๐Ÿ’ก Note: The abstract factory can optimize by reusing existing objects rather than creating new ones every time.

Why Embrace the Abstract Factory?

๐Ÿ”ฎ Ensures compatibility among products obtained from a factory.

๐Ÿ”ฎ Avoids tight coupling between concrete products and client code.

๐Ÿ”ฎ Facilitates seamless replacement of the entire product family by switching concrete factories.

๐Ÿ”ฎ Enables introducing new product variants without disrupting existing client code.

๐Ÿ”ฎ Aligns with the Open/Closed Principle by extending the codebase with new classes instead of modifying existing ones.

๐Ÿ”ฎ Adheres to the Single Responsibility Principle by consolidating product creation in one place, enhancing code maintainability.

Show me the code

# GUIFactory "Interface"
# (since interfaces does not exist in Ruby, we will use a module to define the interface)
module GUIFactory
def create_button
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def create_checkbox
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
class WinFactory
include GUIFactory
def create_button
WindowsButton.new
end
def create_checkbox
WindowsCheckbox.new
end
end
class MacFactory
include GUIFactory
def create_button
MacButton.new
end
def create_checkbox
MacCheckbox.new
end
end
# Checkbox
module Checkbox
def render
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def on_change
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
class MacCheckbox
include Checkbox
def render
puts 'MacCheckbox render'
end
def on_change
puts 'MacCheckbox on_change'
end
end
class WindowsCheckbox
include Checkbox
def render
puts 'WindowsCheckbox render'
end
def on_change
puts 'WindowsCheckbox on_change'
end
end
# Button
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
# Application
class Application
def initialize(factory)
@factory = factory
end
def create_ui
button = @factory.create_button
checkbox = @factory.create_checkbox
button.render
checkbox.render
end
end
# Client
def main
op_system = 'mac'
if op_system == 'win'
factory = WinFactory.new
else
factory = MacFactory.new
end
app = Application.new(factory)
app.create_ui
end
# Output
MacButton render
MacCheckbox render

Conclusion

The Abstract Factory pattern furnishes an interface for creating families of related or dependent objects without specifying their concrete classes.

๐Ÿ”บ This pattern is advantageous when your code interacts with various families of related products without relying on their concrete classes.

๐Ÿ”บ It's beneficial when providing a library of classes for users to extend.

๐Ÿ”บ It ensures compatibility among products obtained from a factory, avoids tight coupling between concrete products and client code, and facilitates seamless replacement of the entire product family.

๐Ÿ”บ The Abstract Factory pattern aligns with the Open/Closed Principle by extending the codebase with new classes instead of modifying existing ones.

Join the Quest!

  • Design Patterns: Elements of Reusable Object-Oriented Software
  • Abstract Factory Pattern
  • Code example

๐Ÿ’ป You can find this and other design patterns here ๐Ÿ“š