【问题标题】:Scala handling missing with nested OptionalsScala 处理缺少嵌套 Optionals
【发布时间】:2016-05-18 15:25:35
【问题描述】:

假设我有一个对象A,它可能有一个OptionB,它还可能引用另一个可选对象C(嵌套的选项)

我见过几种访问C 的方法,例如理解、模式匹配或flatMap,它们看起来都不错。但是如果我必须返回某种错误消息或在缺少BC 或提供类似cannot execute because B is missingC not set 的消息时抛出异常怎么办?我只能退回到(嘲笑):

if(a.b.isDefined)
{
   if(a.b.c.isDefined)
   {
           //do work
   }
   else throw new Exception1 //or call something other
}
else throw new OtherException //or do something other

我怎样才能以更实用或更流畅的方式处理?

【问题讨论】:

  • 您是否考虑过使用Either[A, B] 作为内部类型?
  • 其实没有。我更多地使用 Java 8,所以我有点受 Optional 的影响。您的具体意思是什么?

标签: scala


【解决方案1】:

您可以添加一个方法将 Option 转换为 Try 并在值为 none 时失败,例如

implicit class OptionWrapper[T](val o: Option[T]) extends AnyVal {
    def tryGet(e: => Throwable): Try[T] = o match {
        case Some(x) => Success(x)
        case None => Failure(e)
    }   
}

那你就可以了

a.b.tryGet(new OtherException).flatMap(_.tryGet(new Exception1))

【讨论】:

    【解决方案2】:

    你可以使用getOrElse[B >: A](default: ⇒ B): B:

      a.b.getOrElse(throw new OtherException).c.getOrElse(throw new Exception1)
    

    使用implicit 包装器和AnyVal 优化(如前所述)是个好主意,但是您的代码变得不那么可读了。我认为使用现有方法而不是每次都编写包装器更可取。

    关于map,这里真的没用。如果您不需要抛出Exception,这是有道理的。

    【讨论】:

    • 我比@Lee 更喜欢你的答案,只是因为它更具可读性。直觉上这不是我会选择隐含的东西,虽然它看起来很酷。
    【解决方案3】:

    我不会将 Option 转换为其他东西,而是这样做:

        def getC(a: A) = a.b.map { b1 =>
            b1.c.getOrElse {
                throw new UndefinedC
            }
        }.getOrElse {
            throw new UndefinedB
        }
    

    简而言之,如果定义了 b,那么 map 会让你使用它,然后你可以在 c 上使用 getOrElse 来返回它,如果没有则抛出异常。 最后一个 getOrElse 适用于 b 所以如果 map 返回一个 c 的实例,它将返回它,否则如果 b 未定义,它会让你抛出一个例外。

    我使用 Scalatest 编写了一个快速测试来证明这一点:

    import org.scalatest._
    
    class TestClass extends FlatSpec with Matchers with OptionValues with Inside with Inspectors {
        case class C( name: String ) {
            def echo = name
        }
    
        case class B( c: Option[C] )
    
        case class A( b: Option[B] )
    
        class UndefinedB extends Exception
        class UndefinedC extends Exception
    
        def getC( a: A ) = {
            a.b.map { b1 =>
                b1.c.getOrElse {
                    throw new UndefinedC
                }
            }.getOrElse {
                throw new UndefinedB
            }
        }
    
        "With both b and c defined c.echo" should "be equal to the c.name value" in {
             val c = new C( name = "Alessandro" )
             val b = new B( Some( c ) )
             val a = new A( Some( b ) )
    
             assert( getC(a).echo.equals( "Alessandro" ) )
        }
    
        "With only b defined" should "throw UndefinedC" in {
            val c = new C( name = "Alessandro" )
            val b = new B( None )
            val a = new A( Some( b ) )
    
            intercept[UndefinedC] { getC(a).echo.equals( "Alessandro" ) }
        }
    
        "With only b not defined" should "throw UndefinedB" in {
            val c = new C( name = "Alessandro" )
            val b = new B( Some( c ) )
            val a = new A( None )
    
            intercept[UndefinedB] { getC(a).echo.equals( "Alessandro" ) }
        }
    }
    

    编辑: 顺便说一句,Alexandr Dorokhin 的答案比我的样板要少,如果你在测试中使用它,它们就会通过。

    【讨论】:

    • TDD 方法的荣誉 :)
    猜你喜欢
    • 1970-01-01
    • 2017-01-13
    • 1970-01-01
    • 1970-01-01
    • 2020-05-20
    • 2018-02-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多