如何将Java Builder模式与含可选字段的Scala样例类结合?
用Scala优雅处理Java Builder模式的可选参数
在Scala开发中,我们常会用到采用Builder模式的Java API(比如Google Maps Java Client)。通常我们会用Scala样例类收集参数,并用Option类型处理可选值,但原始写法会产生大量重复的map+getOrElse代码,显得繁琐。
先看原始的实现代码:
case class WlanHint( mac: String, signalStrength: Option[Int], age: Option[Int], channel: Option[Int], signalToNoiseRatio: Option[Int] ) val builder = WifiAccessPoint.WifiAccessPointBuilder().MacAddress(wlanHint.mac) val b = wlanHint.signalStrength.map(signalStrength => builder.SignalStrength(signalStrength)).getOrElse(builder) val b2 = wlanHint.age.map(age => b.Age(age)).getOrElse(b) val b3 = wlanHint.channel.map(channel => b2.Channel(channel)).getOrElse(b2) val b4 = wlanHint.signalToNoiseRatio.map(signalToNoiseRatio => b3.SignalToNoiseRatio(signalToNoiseRatio)).getOrElse(b3) b4.createWifiAccessPoint()
下面给出几种更优雅的优化方案:
方案一:用Option.fold替代map+getOrElse
Option的fold方法可以直接处理Some和None两种情况,避免重复写getOrElse,让代码更连贯:
case class WlanHint( mac: String, signalStrength: Option[Int], age: Option[Int], channel: Option[Int], signalToNoiseRatio: Option[Int] ) val finalBuilder = WifiAccessPoint.WifiAccessPointBuilder() .MacAddress(wlanHint.mac) // fold参数:None时返回当前builder,Some时调用对应Builder方法 .signalStrength.fold(_)(_.SignalStrength) .age.fold(_)(_.Age) .channel.fold(_)(_.Channel) .signalToNoiseRatio.fold(_)(_.SignalToNoiseRatio) finalBuilder.createWifiAccessPoint()
方案二:封装隐式类增强Builder
通过隐式类给Java Builder添加Scala风格的扩展方法,让调用语义更清晰,同时复用逻辑:
case class WlanHint( mac: String, signalStrength: Option[Int], age: Option[Int], channel: Option[Int], signalToNoiseRatio: Option[Int] ) // 定义隐式类,封装Option参数的处理逻辑 implicit class WifiBuilderOps(builder: WifiAccessPoint.WifiAccessPointBuilder) { def withSignalStrength(ss: Option[Int]): WifiAccessPoint.WifiAccessPointBuilder = ss.fold(builder)(builder.SignalStrength) def withAge(age: Option[Int]): WifiAccessPoint.WifiAccessPointBuilder = age.fold(builder)(builder.Age) def withChannel(channel: Option[Int]): WifiAccessPoint.WifiAccessPointBuilder = channel.fold(builder)(builder.Channel) def withSignalToNoiseRatio(snr: Option[Int]): WifiAccessPoint.WifiAccessPointBuilder = snr.fold(builder)(builder.SignalToNoiseRatio) } // 使用扩展方法链式调用 val finalBuilder = WifiAccessPoint.WifiAccessPointBuilder() .MacAddress(wlanHint.mac) .withSignalStrength(wlanHint.signalStrength) .withAge(wlanHint.age) .withChannel(wlanHint.channel) .withSignalToNoiseRatio(wlanHint.signalToNoiseRatio) finalBuilder.createWifiAccessPoint()
方案三:用管道操作符实现无中间变量链式调用
如果你的项目支持管道操作符(Scala 2.13+可直接用|>,也可以自定义),可以彻底消除中间变量,让流程更直观:
case class WlanHint( mac: String, signalStrength: Option[Int], age: Option[Int], channel: Option[Int], signalToNoiseRatio: Option[Int] ) // 自定义管道操作符(Scala 2.13+可省略此步骤) implicit class PipeOps[A](a: A) { def |>[B](f: A => B): B = f(a) } // 链式处理整个Builder流程 WifiAccessPoint.WifiAccessPointBuilder() .MacAddress(wlanHint.mac) |> (b => wlanHint.signalStrength.fold(b)(b.SignalStrength)) |> (b => wlanHint.age.fold(b)(b.Age)) |> (b => wlanHint.channel.fold(b)(b.Channel)) |> (b => wlanHint.signalToNoiseRatio.fold(b)(b.SignalToNoiseRatio)) |> (_.createWifiAccessPoint())
这些方案都能有效减少冗余代码,让逻辑更清晰,符合Scala的函数式编程风格。
内容的提问来源于stack exchange,提问作者Chris W.
相关产品推荐
相关产品推荐

