【发布时间】:2019-04-19 00:32:29
【问题描述】:
在我的项目中,我有许多非常相似的事件。这是一个简短的示例:
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)
}
}
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调用函数和一些额外的支持代码来帮助演示使用模式。
这是正在更新的基本实体。请原谅在案例类中使用 var,因为它使示例更加简单。
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])
}
这是一个持久性参与者,它接收更新 Box 中的 BoxContent 的请求。调用工厂的方法在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)
}
}
}
编辑2: cmets 中提出的为每个事件公开工厂方法然后传递工厂方法的建议似乎是最好的方法。
这里是修改后的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)
}
如果建议此方法的评论者之一想针对此内容提出答案,我将很乐意支持它。
【问题讨论】:
-
如果你在调用
getEvent的时候就知道了事件类型,那为什么不给每个事件一个单独的工厂方法,只调用合适的方法呢? -
getEvent被另一个通用方法调用,所以我不知道事件类型。它有 2 个函数参数(用于获取preValue和newValue值)和一个PropertyEvent[A]参数,然后它会在发出请求时传递给getEvent。 -
我认为我们需要查看调用函数,因为这可能是问题所在。但一般来说,您不能使用类型参数来决定如何处理数据,您需要一个值。
-
为什么你的例子编译,让我感到困惑。谁能解释一下?
-
回复第一条评论:
def getEvent[A, B <: PropertyEvent[A]](id: Int, preValue: A, newValue: A, fact: (Int, A, A) => B): PropertyEvent[A] = fact(id, preValue, newValue),然后使用PropertyEventFactory3.getEvent(0, 1d, 2d, UpdatedCount.apply)调用它