简体   繁体   中英

Does Scala have something similar to C#'s explicit interface implementation?

In C#, you can implement interfaces explicitely. The explicitely implemented methods can then only be called through a variable that has the interface as its static type. This allows you to avoid name/return type conflicts and provide different implementations of the same method depending on the static type of this .

For example:

interface IFoo
{
    int DoSomething();
}

interface IBar
{
    string DoSomething();
}

class Impl : IFoo, IBar
{
    int IFoo.DoSomething() { /* Implementation for IFoo */ }
    string IBar.DoSomething() { /* A different implementation for IBar */ }
    public void DoSomething() { /* Yet another implementation for Impl */ }
}

How would you handle this case in Scala:

trait Foo {
    def doSomething(): Int
}

trait Bar {
    def doSomething(): String
}

class Impl extends Foo with Bar {
    /* only one "doSomething()" visible here (that of Bar?) */
    /* what now... ? */
}

If you're just trying to make your class follow two separate incompatible interfaces, you'd have to write wrappers instead. For instance,

implicit case class ImplAsFoo(impl: Impl) extends Foo {
  def asFoo = this
  def doSomething: Int = 5
}

Now you can

impl.asFoo

at the use-site to switch over to the Foo wrapping.

In some cases, though, it may be more natural use the type class pattern instead to provide pluggable functionality:

trait IFoo[A] { def doSomething: Int }
trait IBar[A] { def doSomething: String }

// These need to be companions so :paste if you're using REPL
class Impl { def doSomething { println("Hey!") } }
object Impl {
  implicit object FooImpl extends IFoo[Impl] { def doSomething = 5 }
  implicit object BarImpl extends IBar[Impl] { def doSomething = "salmon" }
}

def needsFoo[A <: Impl: IFoo](a: A) = implicitly[IFoo[Impl]].doSomething

scala> needsFoo(new Impl)
res1: Int = 5

scala> (new Impl).doSomething
Hey!

It's not exactly the same, but this also handles the issue of having different implementations without naming schemes tripping you up. (If you needed to doSomething with the impl object, you'd pass it as a parameter in the implicit object that handles that case.)

If you've already got traits, then of course this won't help you. But when you're designing from scratch, rather than having a pile of traits with incompatible methods you might instead try type classes.

Finally, if you cannot help having a bunch of untyped things jumbled together out of which you need to pick the Foo s, you have to invent more elaborate schemes like so:

trait CanBeFoo { def asFoo: Foo }
trait Foo { def doSomething: Int }

// :paste these two together
class Impl extends CanBeFoo {
  def doSomething { println("Ho!") } 
  def asFoo = ImplAsFoo(this)
}
case class ImplAsFoo(impl: Impl) extends Foo {
  def doSomething = 6
}

val myList = List("salmon", new Impl, new Foo { def doSomething = 4 })
def doIt(f: Foo) { println(f.doSomething) }
myList.foreach {
  case f: Foo => doIt(f)
  case cf: CanBeFoo => doIt(cf.asFoo)
  case _ => println("nuh-uh")
}

// Produces
// nuh-uh
// 6
// 4

You might prefer an intermediate map:

myList.map{ case cf: CanBeFoo => cf.asFoo; case x => x }.foreach{
  case f: Foo => println(f.doSomething)
  case _ => println("nuh-uh")
}

No, those are strictly incompatible base types. When a class extends a trait, it has that trait as a part of its type identity. It cannot have mutually incompatible type identities as would be the case for any class that extends Foo with Bar (as you wrote those traits).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM