Mastodon

Understanding Factory Method and Strategy Design Patterns in Ruby on Rails

Rodrigo Souza
Rodrigo Souza October 16, 2024

As developers, we often faced scenarios where keeping our code clean and scalable became a challenge, principally when a new feature demands handling very complex code logic. Today, we will talk about two design patterns that can help you simplify complexity with an elegant, less-coupling, and flexible code architecture.

Switching Behaviors at Runtime with The Strategy Pattern

The Strategy Pattern is an excellent option for scenarios when we have behaviors that can be encapsulated. It helps you to change across these behaviors as the business requirements demand. A good use case would be when we need to switch the entire business logic based on a param received on a controller for example. Let's see how to use The Strategy pattern for this:

Let's imagine we are working on a feature for the app. We'll need to deliver a list of doctors that should allow to be sorted by rating, distance, or availability. Using The Strategy pattern we can make the sorting logic pluggable and avoid hardcode the algorithm on the controller:



# app/strategies/sorting_strategy.rb
  class SortingStrategy
    def sort(doctors)
      raise NotImplementedError, "Subclasses must implement the `sort` method"
    end
  end

  # app/strategies/sort_by_rating.rb
  class SortByRating < SortingStrategy
    def sort(doctors)
      doctors.order(rating: :desc)
  end

  # app/strategies/sort_by_distance.rb
  class SortByDistance < SortingStrategy
    def sort(doctors)
      doctors.order(distance: :asc)
    end
  end

  # app/controllers/doctors_controller.rb
  def index
    sorting_strategy = get_sorting_strategy(params[:sort_by])
    @doctors = sorting_strategy.sort(Doctor.all)
  end

  private

  def get_sorting_strategy(sort_by)
    case sort_by
    when 'rating'
      SortByRating.new
    when 'distance'
      SortByDistance.new
    else
      SortByRating.new
    end
  end


Now, the sorting logic is modular, and including a new sorter in the future requires just the creation of a new Sort class file without changing any code of the other classes.

The Factory Method Pattern

The Factory Method is a way to encapsulate the object creation logic. It can help you for example, when you need to instantiate different services depending on some input like user roles or choose payment types of the format of a received file.

The Factory Method pattern will provide you with a clean way to delegate the object creation to specialised classes. Let's see an example:

Imagine you are integrating an application with multiple payment gateways (e.g., PayPal, Stripe, etc.). Avoiding let the code on the controller, let's use the Factory Method pattern to delegate this responsibility:



# app/services/payment_processor_factory.rb
class PaymentProcessorFactory
  def self.create_processor(payment_method)
    case payment_method
    when :paypal
      PaypalProcessor.new
    when :stripe
      StripeProcessor.new
    else
      raise "Unknown payment method"
    end
  end
end

# app/controllers/payments_controller.rb
def process_payment
  processor = PaymentProcessorFactory.create_processor(params[:payment_method])
  processor.process(params)
end


Now we have a centralised point to manage object creation, making the controller leaner and more maintainable.

Conclusion

Design patterns like Factory Method and Strategy will make your code base flexible and better structured. They are perfect tools for scenarios where you are managing object creation or swapping out algorithms. The result will be code decoupled, clean, and more testable.

Let us know if you found this useful or show us your way to use these patterns on your applications.

See you all next time! :)

 

Ps. if you have any questions

Ask here