DEV Community

David Martinez
David Martinez

Posted on

🎨 Exploring the Abstract Factory Design Pattern in 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.

abstract-factory-uml-diagram

Further, interfaces are declared for each distinct product of the UI elements, such as Button, Input, Textarea, etc., with common methods.

button-class-uml-diagram

Concrete factories implement these interfaces and produce concrete products like WindowsButton, WindowsInput, WindowsTextarea, MacButton, MacInput, MacTextarea, etc.

dialog-class-uml-diagram

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 do 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
Enter fullscreen mode Exit fullscreen mode

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.

Join the Quest!

💻 You can find this and other design patterns here 📚

Top comments (0)