Search code examples
javascala

Scala: how to generalize behaviour around unrelated Java classes


I have two Java classes Foo and Bar (from a third part library which I am not supposed to change) and they are not related by any interface, abstract or any other classes.

However they have signature methods like

public String createToken(String id)

that is the same in both classes, and another method which is quite similar apart from the object type returned in the list:

for the class Foo => public List[FooItem] getItems(String id)

and for the class Bar => public List[BarItem] getItems(String id)

In my Scala code I am using these two classes in methods that are basically duplicated. For example:

def getFooItems(foo: Foo, id: String): Vector[FooItem] = {
  foo.createToken(id)
  foo.getItems(id).asScala.toVector
}

def getBarItems(bar:Bar, id: String): Vector[BarItem] = {
  bar.createToken(id)
  bar.getItems(id).asScala.toVector
}

As you can see the code is pretty much the same but for a lots of methods (and more than just two classes) it becomes quite a sort of huge duplicated code.

Is there any way with Scala to generalise the code to get a sort of:

def getItems[A,B](someClass:A, id: String): Vector[B] = {
  someClass.createToken(id)
  someClass.getItems(id).asScala.toVector
}

Or the duck typing the only way?


Solution

  • I suggest you try out typeclass pattern.

    First you have to define interface common for Foo and Bar:

    trait Tokenable[T, TItem] {
       def createToken(t: T, id: String): String
       def getItems(t: T, id: String): List[TItem]
    }
    

    Then you need to define implementation for every class you want to apply it to:

    object FooBar {
      class Foo {
        def createToken(id: String): String = ???
        def getItems(id: String): List[FooItem] = ???
      }
      class Bar {
        def createToken(id: String): String = ???
        def getItems(id: String): List[BarItem] = ???
      }
    
      class FooItem
      class BarItem
    
      implicit object FooTokenable extends Tokenable[Foo, FooItem] {
        def createToken(f: Foo, id: String) = f.createToken(id)
        def getItems(f: Foo, id: String) = f.getItems(id)
      }
    
      implicit object BarTokenable extends Tokenable[Bar, BarItem] {
        def createToken(b: Bar, id: String) = b.createToken(id)
        def getItems(b: Bar, id: String) = b.getItems(id)
      }
    }
    

    Now you can use it in any method with implicit Tokenable parameter:

    import FooBar._
    
    def getItemsAsVector[T, TItem](t: T, id: String)
        (implicit tok: Tokenable[T, TItem]) = {
      tok.createToken(t, id)
      tok.getItems(t, id).toVector
    }
    
    val vector = getItemsAsVector(new Foo, "my_id")