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!