Passionate Development From Journeyman to Master

Using Factory Method Pattern

At work we are currently undergoing a major project to replace our underlying Identity Management System. To facilitate this change we have been relying on Factory Method Pattern.

I haven’t had to use this pattern much and it turns out to be a nice pattern that fits well with our needs.

This is a simplified version of code that needs changing:

module Membership
  class MemberSaveService
    attr_accessor :email_address

    def initialize(email_address)
      @email_address = email_address
    end

    def create_member_with_generated_password
    end
  end
end

With the Identity Management System in place - the create_member_with_generated_password logic needs to change. But we can’t just change the code now because we need to support the legacy system until certain time.

Let’s start simple, by introducing a simple switch to the method (note: we are using Flip to control our feature flag):

module Membership
  class MemberSaveService
    attr_accessor :email_address

    def initialize(email_address)
      @email_address = email_address
    end

    def create_member_with_generated_password
      if Flip.on?(:use_new_identity_system)
        new_system_create_member_with_generated_password
      else
        legacy_create_member_with_generated_password
      end
    end

    private

    def new_system_member_with_generated_password
    end

    def legacy_create_member_with_generated_password
    end
  end
end

This approach maybe fine if there is only one method that needs changing.

Things start to get hairy, when you have a couple of methods needing to change. Testing code would be messy too.

We can improve the design of the code by using Factory Method Pattern. I found Gregory Brown’s explanation of the pattern quite halpful:

The Factory Method pattern is used for putting a layer of abstraction on top of object creation so that directly working with its constructor is no longer necessary. This process can lead to more expressive ways of building new objects, and can also allow for the creation of new objects without explicitly referencing their class.

Let’s introduce instance method as our factory method, the code now looks like this:

module Membership
  class MemberSaveService
    def self.instance(email_address)
      if Flip.on?(:use_new_identity_system)
        Membership::MemberSaveService::NewMembershipAdapter.new(email_address)
      else
        Membership::MemberSaveService::LegacyMembershipAdapter.new(email_address)
      end
    end
  end
end

And now we have added two classes:

module Membership
  class MemberSaveService
    class NewMembershipAdapter
      attr_accessor :email_address

      def initialize(email_address)
        @email_address = email_address
      end

      def create_member_with_generated_password
      end
    end
  end
end

module Membership
  class MemberSaveService
    class LegacyMembershipAdapter
      attr_accessor :email_address

      def initialize(email_address)
        @email_address = email_address
      end

      def create_member_with_generated_password
      end
    end
  end
end

This is much better code than littering the code with if statements. Each of the adapters exposes the same method create_member_with_generated_password but hides the implementation detail to the caller.

There is a slight change on how we call the MemberSaveService now, from:

# old
Membership::MemberSaveService.new(email_address).create_member_with_generated_password

# new
Membership::MemberSaveService.instance(email_address).create_member_with_generated_password

It is very small change and maintain almost the same API as before.

And that’s it - it’s good to learn new things, I certainly glad I got exposure to this pattern.

Happy coding!

Comments