9

I am asking for help regarding a design pattern. I am very used to interfaces in java, and I dont know how to get a similar mechanism in ruby. What it need is kind of interface which has a method e.g. contacts. In order to get contacts I need to make a call against an api, this could be google, linkedid or whatever webservice. So I d like to use an interface which provides me the contacts method and i dont want to know anything about the provider beyond.

My first attempt looks like this (pseudo code):

Module AbstractContact
 def contacts
  #do some stuff with @data
  @data
 end
end

class Impl
  include AbstractContact
 def build_provider(provider)
  if provider == :google
   #get the data from google
    gdata= api_call
   @data = gdata
   elsif provider == :linkedin
   end
 end
end


c=Impl.new
c.build_provider

c.contacts

But I am really not sure, if this is the "ruby way to go".

Help, suggestions and recommendations are welcome. Best, Phil

dc10
  • 2,160
  • 6
  • 29
  • 46

4 Answers4

6

Strategy pattern could be applied here

class Provider
  def contacts
    raise "Abstract method called"
  end
end

class Google < Provider
  def contacts
    data = # Google API call
  end
end

class LinkedIn < Provider
  def contacts
    data = # LinkedIn API call
  end
end

class Impl
  def initialize(provider)
    case provider
    when :google
      @provider = Google.new
    when :linkedin
      @provider = LinkedIn.new
    else
      raise "Unknown provider"
    end
  end
  
  def contacts
    @provider.contacts
  end
end

impl = Impl.new(provider)
impl.contacts
Hoa
  • 3,179
  • 1
  • 25
  • 33
4

There are several good approaches to this. One is to encapsulate the different functionality into modules:

module Google
  def contacts
    puts 'contacts from Google'
  end
end

module LinkedIn
  def contacts
    puts 'contacts from LinkedIn'
  end
end

class Impl
  def self.create provider
    o = new
    o.extend(provider)
  end
end

Impl.create(Google).contacts
Impl.create(LinkedIn).contacts

Output:

contacts from Google
contacts from LinkedIn

Here the create method on Impl is a factory method for Impl instances that adds in the methods from the given module. Just make sure that the modules implement the same method names and return compatible values.

Catnapper
  • 1,875
  • 10
  • 12
2

Modules are usually used to exclude common behavior of multiple objects. I believe that all you need in this case is duck-typing. Just implement method contacts in all classes that would share interface in Java solution.

Please note that this solution allows you to keep objects of different types in a single collection, and while you iterate over them (or in any other way you would like to use these common-interface objects) you just call this contacts method, not caring what type they really are.

If you need some common behavior in all classes implementing this interface you can create module that would base on existence of contacts method, and include it in all classes that could use it.

samuil
  • 5,001
  • 1
  • 37
  • 44
2

I really like @Bui The Hoa's answer, but I'll add the following:

I like this approach a lot, especially the raising of the error in the base Provider class. However I don't think having the case statement in the Impl is good Strategy pattern use. It violates the single purpose principle as it makes the implementation responsible for keeping track of all possible provider. It also violates the open-close principle of classes being open to extension but not to change, because when you add a new providers you'd have to change the case statement.

Why not just do

impl = Impl.new(Google.new)

Since the raise "Unknown provider" error would be taken care of this way:

impl = Impl.new(BadProvider.new) => Error
pixelearth
  • 13,674
  • 10
  • 62
  • 110