【问题标题】:Is there any way to map a Behavior from one type to another有没有办法将行为从一种类型映射到另一种类型
【发布时间】:2021-03-28 03:54:06
【问题描述】:

我有一个场景需要提供特定类型的行为。此 Behavior 还需要处理在 Event Stream 上发布的事件。所以说具体的类型是:

case class DoSomething(i: Int)

然后我需要实现一个函数来返回一个行为来处理这种类型的消息:

def foo(): Behavior[DoSomething]

然后我还需要在事件流上处理以下消息:

case class PublishedEvent(str: String)

我想出的唯一解决方案是从我的 DoSomething 行为中生成另一个演员,然后将消息转发给它:

sealed trait Command
case class Command1(str: String) extends Command
case class Command2(str: String) extends Command

def foo(): Behavior[DoSomething] = Behaviors.setup { context =>
    val actor = context.spawnAnonymous[Command](Behaviors.setup { context =>
        context.system.eventStream ! EventStream.Subscribe(context.messageAdapter {
            case PublishedEvent(str) => Command2(str)
        })
        Behaviors.receiveMessage {
            case Command1(str) =>
                println("Received Command1: " + str)
                Behaviors.same
            case Command2(str) =>
                println("Received Command1: " + str)
                Behaviors.same
        }
    })
    Behaviors.receiveMessage {
        case DoSomething(i) =>
            actor ! Command1(i.toString)
            Behaviors.same
    }
}

我的问题是有什么方法可以避免产生一个新的演员并在同一个演员内完成这一切?即有没有办法可以将Behavior[Command] 映射到Behavior[DoSomething]

【问题讨论】:

  • 我不太清楚你的意思,你为什么不直接创建一个actor并将命令作为消息传递给它?
  • 我可能应该指定这是针对 Akka 2.6.x 的,它应该是完全类型安全的

标签: scala akka akka-typed


【解决方案1】:

我也经常遇到这个问题,我发现的最佳解决方案是在常见的“超级Behavior”上使用narrow[...] 方法来缩小客户端应该发送给相应参与者的消息类型。通过定义一个通用接口(在 scala 中当然是一个trait),可以将两种消息类型的处理组合在一个Behavior 中:

sealed trait CommandOrDoSomething // #TODO find better name

final case class DoSomething(i:Int) extends CommandOrDoSomething

sealed trait Command extends CommandOrDoSomething
case class Command1(str: String) extends Command
case class Command2(str: String) extends Command

def getBehavior(): Behavior[CommandOrDoSomething] = Behaviors.setup { context =>
    Behaviors.receiveMessage {
        // handle all kinds of messages here
    }
}

现在,如果您想将Behavior[DoSomething] 传递给客户端,您可以像这样简单地获取它:

def foo(): Behavior[DoSomething] = getBehavior().narrow[DoSomething]

您可以将Command trait 保留为您当前上下文的私有,但我认为您至少必须向外界公开通用超级特征。使用联合类型,因为它们将在 Scala 3 中可用,这可以避免,并且您不需要人工的超级特征。

【讨论】:

    【解决方案2】:

    文档(以及签名)表明Behavior 上的transformMessages 可以工作,前提是外部发送的消息将 1:1 映射到内部消息:

    def transformMessages[Outer: ClassTag](matcher: PartialFunction[Outer, T]): Behavior[Outer]
    

    即使用Behavior[T].transformMessages[Outer] { ??? } 实现的actor 将产生ActorRef[Outer]

    sealed trait External
    
    case class DoSomething(i: Int) extends External
    
    private sealed trait Internal
    
    private case class Command1(str: String) extends Internal
    private case class Command2(str: String) extends Internal
    
    private def internalBehavior: Behavior[Internal] = Behaviors.setup { context =>
      // do stuff with context
      Behaviors.receiveMessage {
        case Command1(s) => ???
        case Command2(s) => ???
      }
    }
    
    import akka.actor.typed.Behavior.BehaviorDecorators
    def externalBehavior: Behavior[External] =
      internalBehavior.transformMessages {
        case DoSomething(i) => Command1(i.toString)
      }
    

    在这种情况下,内部命令在其范围之外是不可见的。我看到的主要潜在缺点(除了将PartialFunction 带入打字的轻微恶心之外)是在带有Behavior[Internal] 的演员中,您无法获得ActorRef 演员的外部“个性”,除非您有一些东西像疯了一样

     case class DoSomething(i: Int, recipient: ActorRef[External]) extends External
    
     private case class Command1(str: String, externalPersonality: ActorRef[External]) extends Internal
    

    值得注意的是,在内部消息由您控制的情况下,可以绕过 1:1 限制。

    【讨论】:

      猜你喜欢
      • 2021-04-11
      • 2011-05-30
      • 1970-01-01
      • 2021-04-18
      • 1970-01-01
      • 2017-03-28
      • 2015-08-12
      • 1970-01-01
      • 2011-11-20
      相关资源
      最近更新 更多