美文网首页
Scala设计模式:磁铁模式(Magnet Pattern)

Scala设计模式:磁铁模式(Magnet Pattern)

作者: 歌湾汐云 | 来源:发表于2019-02-13 11:58 被阅读0次

磁铁模式(Magnet Pattern)是Scala中独有的实现函数重载的一种模式。它是由spray框架的设计者提出的,在spray框架中大量使用。

磁铁模式有什么用?

我们设计API时,需要尽量简洁。有时表达的语义是一样的,但是参数的类型不同,怎么办?一般的做法就是利用函数重载。但Java和Scala中的函数重载存在一些问题:

  1. JVM的类型擦除机制会导致认不出高阶参数的重载。比如:
    // 类型擦除导致函数重载冲突,编译报错
    def notWork(ls: List[Int]): List[Int] = ls.map(_ * 2)
    def notWork(ls: List[String]): List[String] = ls ++ ls  // ERROR: double definition
    
  2. 不方便扩展。增加对新的参数类型时,必须在API中定义。

磁铁模式可以很优雅的解决这些问题——把“重载”的函数,像磁铁一样,一块一块贴上去。

磁铁模式怎么做?

磁铁模式利用了Scala语言的隐式转换的特性。模式的结构如下:


  • Client中定义一个API,型如:
    def operation(magnet: Magnet): magnet.Result
    
  • Magnet是个接口(特质),其中需要定义一个抽象类型、一个抽象方法。
    traitMagnet {
      type Result
      def apply(): Result
    }
    
  • 剩下的,就是定义一系列不同的隐式转换类,把不同的类型都隐式转换成Magnet类型,进而实现了函数重载的效果。例如:
    implicit class fromInt(x: Int) extends Magnet {
      override type Result = Int
      override def apply(): Result = x * 2
    }
    

一个完整的例子

假设,现在我们要设计一个接口叫做Doubling,它其中的double方法可以把传入的参数“翻倍”。具体怎么翻倍,不同类型有不同的翻法。比如Int型就是乘以2、String型就是变成两个相同的字符串连在一起、List[Int]就是把列表中的每个元素都乘以2……

我们可以这样实现(完整的代码可以看这里

class Doubling {
  def double(magnet: DoubleMagnet): magnet.Result = magnet()
}

// Magnet Interface
trait DoubleMagnet {
  type Result
  def apply(): Result
}

// Implicit Conversions
object DoubleMagnet {

  implicit class fromInt(x: Int) extends DoubleMagnet {
    override type Result = Int
    override def apply(): Result = x * 2
  }

  implicit class fromListInt(ls: List[Int]) extends DoubleMagnet {
    override type Result = List[Int]
    override def apply(): Result = ls.map(_ * 2)
  }

  implicit class fromListString(ls: List[String]) extends DoubleMagnet {
    override type Result = List[String]
    override def apply(): Result = ls ++ ls
  }

  // overloading with different number of parameters
  implicit class fromStringIntTuple(para: Tuple2[String, Int]) extends DoubleMagnet {
    override type Result = String
    override def apply(): String = para._1 * para._2
  }

}

测试一下效果吧:

object App extends App {
  val doubling = new Doubling()
  println(doubling.double(2))
  println(doubling.double(List(1, 2, 3)))
  println(doubling.double(List("a", "b", "c")))
  println(doubling.double("a", 5))
}

运行结果如下:

4
List(2, 4, 6)
List(a, b, c, a, b, c)
aaaaa

相关文章

网友评论

      本文标题:Scala设计模式:磁铁模式(Magnet Pattern)

      本文链接:https://www.haomeiwen.com/subject/qcmheqtx.html