在子类中扩展Scala模式匹配,同时保持复杂性



我有一个Scala问题。想象一下,您正在构建处理不同操作的代码,即

operation match {
   case A => doA()
   case B => doB()
   case _ => throw new Exception("Unknown op: " + operation)
}

现在,想象一下,稍后你想要构建一个新版本,并且你想要扩展操作C的模式匹配。你怎么能这样做,操作分辨率仍然是O(1)?

我的意思是,我可以修改上面的代码做:

   case _ => handleUnknownOperation(operation)

子类可以实现handleUnknownOperation to do:

operation match {
   case C => doC()
}

但这是愚蠢的,因为这意味着C运算需要O(2)。

扩展这种模式匹配结构还有其他想法或最佳实践吗?

干杯,Galder

为了回答最初的问题,模式匹配被有效地转换为一系列if/else语句,而只是一系列测试。最后的case _只是一个失败(没有相关的测试)。因此,在一场比赛中有A、B和C,在一次比赛中有B和A,然后委托给另一场与C匹配的比赛,这两者之间几乎没有区别。以以下为例:

class T
case class A(v: Int) extends T
case class B(v: Int) extends T
case class C(v: Int) extends T
class Foo {
  def getOperation(t: T): Unit = {
    t match {
      case A(2) => println("A")
      case B(i) => println("B")
      case _ => unknownOperation(t)
    }
  }
  def unknownOperation(t: T): Unit = println("unknown operation t=" + t)
}
class Bar extends Foo {
  override def unknownOperation(t: T): Unit = t match {
    case C(i) => println("C")
    case _ => println("unknown operation t=" + t)
  }
}

使用jad反编译Foo.class,我们得到:

public void getOperation(T t) {
label0:
    {
        T t1 = t;
        if(t1 instanceof A)
        {
            if(((A)t1).v() == 2)
            {
                Predef$.MODULE$.println("A");
                break label0;
            }
        } else
        if(t1 instanceof B)
        {
            Predef$.MODULE$.println("B");
            break label0;
        }
        unknownOperation(t);
    }
}

所以,我想说,你不应该担心性能[*]。

然而,从设计的角度来看,我可能会稍微改变一下,或者像Frank建议的那样使用Command模式,或者不覆盖子类中的unknownOperation,而是覆盖getOperation,它与C匹配,然后委托给super.getOperation(),这对我来说似乎更整洁

class Bar extends Foo {
  override def getOperation(t: T): Unit = t match {
    case C(i) => println("C")
    case _ => super.getOperation(t)
  }
}

[*]需要注意的是复杂性。Scala 2.10之前的版本中存在一个问题,即模式匹配器生成的.class文件过于复杂(嵌套提取器生成指数空间字节码),因此如果您使用的匹配非常复杂,这可能会导致问题。这在2.10中通过虚拟模式匹配器进行了修复。如果您有这个问题,那么解决这个错误的方法之一就是将模式匹配拆分为不同的方法/类。

re:一个handleUnknownOperation方法,这不是一个非常干净的OO设计。例如,如果要处理v3子类的D操作,这就有点奇怪了。相反,只需创建一个子类可以覆盖的handle方法:

class BaseProtocol {
  def handle(operation: Operation) = operation match {
    case A => doA()
    case B => doB()
    case _ => throw new Exception("Unknown op: " + operation)
  }
}
class DerivedProtocol extends BaseProtocol {
  override def handle(operation: Operation) = operation match {
    case C => doC()
    case _ => super.handle(operation)
  }
}

re:效率,你可能过早地进行了优化,但我不会让它阻止我:-)

你所有的operation都是object吗?如果是,则可以将match语句替换为Map

class BaseProtocol {
  def handlers = Map(A -> doA _, B -> doB _)
  def handle(operation: Operation) =
    handlers.get(operation)
      .getOrElse(throw new Exception("Unknown op: " + operation))
      .apply()
}
class DerivedProtocol extends BaseProtocol {
  override def handlers = super.handlers + (C -> doC _)
}

根据我上面的评论,我是从设计角度而不是性能角度来考虑这个问题的。

因此,问题稍微转移到:如何编写一个支持几种可能操作的协议实现,并使其具有版本控制能力,以便可以调整操作查找?

评论中给出了一种解决方案的子类化方法。为了保持操作解析机制不变,我甚至建议用另一种查找机制来替换模式匹配。

这里有一个想法:

  • 基类/性状CanPerformOperations。这个类保留了一个散列映射,它将操作(看起来像是使用案例objects,这会很好地工作)映射到执行实际操作的函数。它还提供了注册操作的方法,即修改哈希映射,并通过在哈希映射中查找操作函数并执行它来执行与命令相关联的操作
  • ProtocolVersion1 extends CanPerformOperations分别为AB命令注册操作doAdoB
  • ProtocolVersion2 extends ProtocolVersion1另外为C命令注册操作doC
  • ProtocolVersion2_1 extends ProtocolVersion2可以为A命令注册新的操作,因为2.1版本中的行为与1.0版本中的不同

最重要的是,这将查找和命令执行机制分离为一个特性/基类,并为您提供了定义具体实现所支持的命令的充分灵活性。

当然,所有这些都与Command模式有关。

原则上,这并不是你问题本身的答案,因为比赛没有延长。实际上,它实现了你想要做的事情,同时保持(摊销的)恒定的复杂性。

最新更新