В моем проекте много событий, которые очень похожи. Вот сокращенный пример:
object Events {
final case class UpdatedCount(id: Int, prevValue: Double, newValue: Double)
extends PropertyEvent[Double]
final case class UpdatedName(id: Int, prevValue: String, newValue: String)
extends PropertyEvent[String]
}
Черта выглядит так:
trait PropertyEvent[A] {
val id: Int
val prevValue: A
val newValue: A
}
Существует фабрика, которая используется для получения соответствующего события во время выполнения. Это вызывается другим универсальным методом, который использует частичные функции для получения preValue
и newValue
:
object PropertyEventFactory{
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, prop: B): PropertyEvent[A]= prop match{
case UpdatedCount(_,_,_) => UpdatedCount(id, preValue, newValue)
case UpdatedName(_,_,_) => UpdatedName(id, preValue, newValue)
}
}
IntelliSense IntelliJ жалуется на preValue
и newValue
, но компилятор может это выяснить и успешно построить.
Вот базовая спецификация, показывающая, как это может быть вызвано:
"Passing UpdatedCount to the factory" should "result in UpdatedCount" in {
val a = PropertyEventFactory.getEvent(0, 1d,2d, UpdatedCount(0,0,0))
assert(a.id == 0)
assert(a.prevValue == 1)
assert(a.newValue == 2)
}
Есть ли способ достичь этого, передав UpdatedCount
как тип вместо объекта? Создание временной версии UpdatedCount
только для того, чтобы получить фактическое UpdatedCount
Событие, имеет запах кода для меня. Я пробовал много способов, но в итоге столкнулся с другими проблемами. Есть идеи?
Редактировать 1:
Добавлена функция вызова getEvent
и некоторый дополнительный вспомогательный код, помогающий продемонстрировать схему использования.
Вот базовый объект, который обновляется. Простите за использование vars в классе case, поскольку это значительно упрощает примеры.
final case class BoxContent(id: Int, var name: String, var count: Double, var stringProp2: String, var intProp: Int){}
Команда, использованная для запроса обновления:
object Commands {
final case class BoxContentUpdateRequest(requestId: Long, entity: BoxContent, fields: Seq[String])
}
Вот постоянный субъект, который получает запрос на обновление BoxContent
в Box
. Метод, который вызывает фабрику, находится здесь в функции editContentProp
:
class Box extends PersistentActor{
override def persistenceId: String = "example"
val contentMap: BoxContentMap = new BoxContentMap()
val receiveCommand: Receive = {
case request: BoxContentUpdateRequest =>
val item = request.entity
request.fields.foreach{
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, UpdatedName.apply(0,"",""))
case "count" => editContentProp(item.id, item.count, contentMap.getCountProp, contentMap.editCountProp, UpdatedCount.apply(0,0,0))
case "stringProp2" => /*Similar to above*/
case "intProp" => /*Similar to above*/
/*Many more similar cases*/
}
}
val receiveRecover: Receive = {case _ => /*reload and persist content info here*/}
private def editContentProp[A](key: Int, newValue: A, prevGet: Int => A,
editFunc: (Int, A) => Unit, propEvent: PropertyEvent[A]) = {
val prevValue = prevGet(key)
persist(PropertyEventFactory.getEvent(key, prevValue, newValue, propEvent)) { evt =>
editFunc(key, newValue)
context.system.eventStream.publish(evt)
}
}
}
Edit2:
Предложение, сделанное в комментариях, чтобы выставить фабричный метод для каждого события и затем передать фабричный метод, кажется лучшим подходом.
Вот модифицированный Box
класс:
class Box extends PersistentActor{
override def persistenceId: String = "example"
val contentMap: BoxContentMap = new BoxContentMap()
val receiveCommand: Receive = {
case request: BoxContentUpdateRequest =>
val item = request.entity
request.fields.foreach{
case "name" => editContentProp(item.id, item.name, contentMap.getNameProp, contentMap.editNameProp, PropertyEventFactory.getNameEvent)
case "count" => editContentProp(item.id, item.count, contentMap.getCountProp, contentMap.editCountProp, PropertyEventFactory.getCountEvent)
case "stringProp2" => /*Similar to above*/
case "intProp" => /*Similar to above*/
/*Many more similar cases*/
}
}
val receiveRecover: Receive = {case _ => /*reload and persist content info here*/}
private def editContentProp[A](key: Int, newValue: A, prevGet: Int => A,
editFunc: (Int, A) => Unit, eventFactMethod: (Int, A, A) => PropertyEvent[A]) = {
val prevValue = prevGet(key)
persist(eventFactMethod(key, prevValue, newValue)) { evt =>
editFunc(key, newValue)
context.system.eventStream.publish(evt)
}
}
}
А вот и модифицированный PropertyEventFactory
:
object PropertyEventFactory{
def getCountEvent(id: Int, preValue: Double, newValue: Double): UpdatedCount = UpdatedCount(id, preValue, newValue)
def getNameEvent(id: Int, preValue: String, newValue: String): UpdatedName = UpdatedName(id, preValue, newValue)
}
Если один из комментаторов, предложивших этот подход, захочет предложить ответ с этим содержанием, я буду рад объявить его.