0
votes

Let's say,

class ClientFunc {
  def client(s: String): Future[Service] = ....

  def m1(s: String) = {
    client(s).map( ...... )
  } 
  ...//there are multiple such methods like m1 in this class which 
depends on "def client".       
}

Now we have to add one more client of same type, with different implementation, and we want to use it along with existing one, based on requirement.

So there are two ways to solve this problem. solution one: using Inheritance, like make client method abstract in parent & provide children classA & clientB for implementations, for two different clients.

class clientA extends ClientFunc {
   def client(s: String): Future[Service] = ....
}  
class clientB extends ClientFunc {
   def client(s: String): Future[Service] = ....
} 

Usage as usual,

 clientAInstance.m1("str")
 and
 clientBInstance.m1("str")

As per use-case is i have to use clientA & clientB at a time, so i need to inject both clients in service.

Other solution: is by making "def m1" like functions higher order and pass client in it, keep ClientFunc class as is, add one more function for other client, like(def clientB),

class ClientFunc {
  def clientA(s: String): Future[Service] = ....
  def clientB(s: String): Future[Service] = ....

  def m1(s: String, cl:String => Future[Service]) = {
   cl(s).map( ...... )
  } 

}

Now whenever i have to call, i will just call like,

 ClientFuncInstance.m1("str", ClientFuncInstance.clientA)
 and
 ClientFuncInstance.m1("str", ClientFuncInstance.clientB)

no need inject ClientFunc two times.

Question is which one should be preferred way in Scala / functional programming? & why? if any other better way, please suggest.

1
Will you need to pattern match over the different types of clients? If not, you might use @Tim's answer.francoisr
@francoisr based on flag i have to use, if flag is true use clientA else use clientB.Ashok Waghmare

1 Answers

1
votes

In my view the best way is to inject client in the constructor:

class ClientFunc(client: String => Future[Service]) {
  def m1(s: String) = {
    client(s).map( ...... )
  } 
  ...
}

You can add new implementations (including mocks for testing) without having to change the existing code.


[ Update after comments ]

You would use it like this:

def clientA(s: String): Future[Service] = ....
def clientB(s: String): Future[Service] = ....

val clientFuncA = new ClientFunc(clientA)
val clientFuncB = new ClientFunc(clientB)

class Service(funcA: ClientFunc, funcB: ClientFunc) {
  def flag = ...
  def client = if (flag) { funcA } else { funcB }

  def m1(s: String) = {
    client.m1(s)
  }
}

val service = new Service(clientFuncA, clientFuncB)