Udemy-akka-essential #07 Changing Actor Behavior

Ada·2023년 10월 18일

Akka

목록 보기
29/32

액터의 행동을 시간에 따라 변경하려면 현재 상태를 추적하고 메시지 핸들러 내에서 해당 상테에 따라 다르게 메시지에 반응해야 한다.

강의에서는 어머니, 아이 두 개의 액터를 사용하였다.

어머니가 아이에게 음식을 주면 아이의 상태가 변한다.

채소를 받은 아이는 슬퍼지고, 초콜릿을 받은 아이는 행복해진다.

초반에는 강의에서 var를 사용하여 변수로 액터의 상태를 변경했는데, 이는 불변성 원칙에 어긋나므로 아래의 방법들로 개선하였다.

Stateless Fuzzy Kid 클래스 도입

이 클래스를 통해 불변성을 강화하고 메시지 핸들러를 효율적으로 관리 하게 되었다.

happyReceive 와 sadReceive

두 가지의 다른 메시지 핸들러를 도입하여 아이 액터의 상태에 따라 적절한 핸들러를 사용하도록 하였다.

context.becom

이를 사용하여 메시지 핸들러를 동적으로 변경할 수 있게 되었다.
이 방법은 아카에서 동적으로 메시지 핸들러를 변경하는 중요한 패턴 중 하나이다.

아래는 강의에서 사용한 예시코드이고, 주석 안에 공부 내용이 포함되어 있다.

package part2actors

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import part2actors.ChangingActorBehavior.Mom.MomStart


// 시간에 따라 변화되는 액터의 행동
object ChangingActorBehavior extends App {

  // FussyKid 동반 객체
  // 음식이 야채인지, 초콜릿인지 엄마가 메시지를 보내고, 아이의 대답이 기쁨/슬픔으로 나뉘어 회신 됨
  object FussyKid {
    case object KidAccept
    case object KidReject
    val HAPPY = "happy"
    val SAD = "sad"
  }

  // FussyKid 액터 클래스
  class FussyKid extends Actor {
    import FussyKid._
    import Mom._

    // 아이의 내부 상태 (기본적으로 행복한 상태로 시작) , 아이의 프로토콜, var 변수를 사용함, 비추천
    var state = HAPPY

    // 메시지를 받을 때의 행동
    override def receive: Receive = {
      case Food(VEGETABLE) => state = SAD
      case Food(CHOCOLATE) => state = HAPPY
      case Ask(_) =>
        if (state == HAPPY) sender() ! KidAccept
        else sender() ! KidReject
    }
  }

  // 상태를 변경하지 않고 context.become을 사용해 행동만 변경하는 FussyKid 액터 클래스
  class StatelessFussyKid extends Actor {

    // 아이와 엄마의 상태, 메시지 가져오기 위해 임포트
    import FussyKid._
    import Mom._

    // 기본적으로 행복한 상태의 리시브 핸들러를 사용
    override def receive: Receive = happyReceive

    def happyReceive: Receive = {
      case Food(VEGETABLE) => context.become(sadReceive, false) // 야채를 받으면 슬픈 리시브 핸들러로 전환
      case Food(CHOCOLATE) =>
      case Ask(_) => sender() ! KidAccept  // 행복한 상태면 KidAccept 메시지 회신
    }

    def sadReceive: Receive = {
      case Food(VEGETABLE) => context.become(sadReceive, false) // 야채를 받으면 상태 유지
      case Food(CHOCOLATE) => context.unbecome() // 초콜릿을 받으면 이전 상태로 복귀
      case Ask(_) => sender() ! KidReject // 슬픈 상태면 KidReject 메시지 회신
    }
  }

  // 엄마 동반 객체
  // 아이에게 음식 메시지를 보낸 후 놀고싶은지, 아닌지 질문함
  object Mom {
    // kid 액터와 통신하기 위한 액터 참조
    case class MomStart(kidRef: ActorRef)           // 아이 액터를 시작하기 위한 메시지
    case class Food(food: String)
    case class Ask(message: String) // do you want to play?
    val VEGETABLE = "veggies"
    val CHOCOLATE = "chocolate"
  }

  // 엄마 액터 클래스
  class Mom extends Actor {
    import FussyKid._
    import Mom._

    // 메시지를 받을 때의 행동
    override def receive: Receive = {
      case MomStart(kidRef) =>
        // test our interaction
        kidRef ! Food(VEGETABLE)
        kidRef ! Food(VEGETABLE)
        kidRef ! Food(CHOCOLATE)
        kidRef ! Food(CHOCOLATE)
        kidRef ! Ask("do you want to play?")
      case KidAccept => println("Yay, my kid is happy!")
      case KidReject => println("My kid is sad, but as he's healthy!")
    }
  }

  // 액터 시스템 생성
  val system = ActorSystem("changingActorBehaviorDemo")
  val fussyKid = system.actorOf(Props[FussyKid])
  val statelessFussyKid = system.actorOf(Props[StatelessFussyKid])
  val mom = system.actorOf(Props[Mom])

  // 엄마 액터에게 아이 액터를 시작하라는 메시지 전송
  mom ! MomStart(statelessFussyKid)

  /*
   메시지의 인과관계

    mom receives MomStart
      kid receives Food(veg) -> kid will change the handler to sadReceive
      kid receives Ask(play?) -> kid replies with the sadReceive handler =>
    mom receives KidReject
   */
profile
백엔드 프로그래머

0개의 댓글