Abstract Factory Design Pattern in Ruby

1. Definition

The Abstract Factory Design Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. In other words, it's a factory of factories.

2. Problem Statement

Imagine you are developing a UI library that needs to be consistent across multiple platforms, like Windows and macOS. Each platform has different appearances and behaviors for buttons, windows, and other UI components. Creating platform-specific UI components individually can lead to a lot of duplicate code and loss of consistency.

3. Solution

The Abstract Factory Pattern suggests creating a separate factory for each family of products. Each of these factories is responsible for producing products of one kind. The client code is then decoupled from the concrete product classes and works only with their abstract interfaces and the factory interface.

4. Real-World Use Cases

1. UI libraries where different platforms provide distinct looks and behaviors.

2. Configuring an application with a set of products from either a "premium" family or a "basic" family.

3. E-commerce platforms with multiple vendors where each vendor offers products with slight variations.

5. Implementation Steps

1. Define product interfaces for each type of product in the product family.

2. Implement concrete product classes for all variants of the product.

3. Create the abstract factory interface that declares a set of factory methods for all types of products.

4. Implement a concrete factory for each product family.

6. Implementation in Ruby

# Abstract products
class Button
  def render
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end
class Window
  def render
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end
# Concrete products
class MacOSButton < Button
  def render
    'Rendering MacOS Button'
  end
end
class MacOSWindow < Window
  def render
    'Rendering MacOS Window'
  end
end
class WindowsButton < Button
  def render
    'Rendering Windows Button'
  end
end
class WindowsWindow < Window
  def render
    'Rendering Windows Window'
  end
end
# Abstract factory
class GUIFactory
  def create_button
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
  def create_window
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end
# Concrete factories
class MacOSFactory < GUIFactory
  def create_button
    MacOSButton.new
  end
  def create_window
    MacOSWindow.new
  end
end
class WindowsFactory < GUIFactory
  def create_button
    WindowsButton.new
  end
  def create_window
    WindowsWindow.new
  end
end
# Client code
def client_code(factory)
  button = factory.create_button
  window = factory.create_window
  [button.render, window.render].join("\n")
end
mac_factory = MacOSFactory.new
puts client_code(mac_factory)
windows_factory = WindowsFactory.new
puts client_code(windows_factory)

Output:

Rendering MacOS Button
Rendering MacOS Window
Rendering Windows Button
Rendering Windows Window

Explanation:

1. Abstract products (Button and Window) provide an interface for all product variants.

2. Concrete products (MacOSButton, MacOSWindow, WindowsButton, and WindowsWindow) implement these interfaces.

3. The GUIFactory abstract class represents the abstract factory which declares the creation methods.

4. Concrete factories (MacOSFactory and WindowsFactory) provide the specific products for each family.

5. The client code remains decoupled from the concrete classes and communicates through the abstract interfaces.

7. When to use?

Use the Abstract Factory pattern when:

1. The system needs to be independent of how its objects are created, composed, and represented.

2. The system is configured with multiple families of products.

3. Products from one family are designed to work together, and this constraint needs to be enforced.

4. You want to provide a library of product families without exposing the concrete classes.


Comments