我在使用不可变对象建模域时遇到问题。
可变设计
一个基本特征WObject(世界对象)和用于实现特定操作的特征,如OwnedObj(hp / owner / takeDamage),Movable(movementLeft / moveTo),Fighter(攻击/攻击)。
在层次结构的末尾,你有一个可变的类,它混合了适当的特征:
class Corvette(var position: Vect2) extends WObject with OwnedObj with Movable with Fighter
如果用户想要进行操作(比如说让船移动),你可以:
val opt = objects.collectFirst { case obj: Movable if obj.position == position => obj }
opt.fold(Log.error(s"Movable at $position not found!")) { obj =>
obj.moveTo(position) // return type is Unit
}
不变设计
如果 moveTo 必须返回一个新对象,它返回什么类型?
我尝试过使用trait Movable[Self <: Movable[Self]]
方法,但这需要将Movable [_]随处可见,而这些存在类型很快就会失控。如果我想要Movable[_] with Fighter[_]
怎么办?是_同一类型?
我也尝试使用类型边界方法在特征中使用抽象类型Self,但是在以下场景中这开始变得毛茸茸:
def takeDamage(obj: OwnedObj): obj.Self = if (Random.nextDouble()) obj.takeDamage else obj.self
嵌套这一点,你得到像
这样的类型 def attackReachable(
data: WObject.WorldObjUpdate[Self]
): WObject.WorldObjUpdate[data.value._2.Self]
哪个太可怕了。
我在考虑放弃继承并使用合成+类型类,但我不太清楚该怎么做。
例如:
case class WObject(position: Vect2, id: UUID=UUID.randomUUID())
case class OwnedObj(owner: Owner)
case class Movable(movementLeft: Int)
case class Fighter(attacked: Boolean)
case class Corvette(obj: WObject, owned: OwnedObj, movable: Movable, fighter: Fighter)
// Something that has both WObject and Movable
trait MovableOps[A <: ???] {
def moveTo(obj: A, target: Vect2): A
}
然后在类型类中定义操作,这将在Corvette配套对象中实现。
但我不确定如何指定约束。
更多关于如何从客户端实施移动操作的信息?
val opt = objects.collectFirst { case obj: ??? if obj.position == position => obj }
opt.fold(Log.error(s"Movable at $position not found!")) { obj =>
objects = objects - obj + obj.moveTo(position)
}
帮助赞赏:)
答案 0 :(得分:3)
您可以使用存在主语(Movable[T] with Fighter[T] forSome {type T})
编写“相同_ in with”案例。
如果我已正确理解您的attackReachable
示例,我不会过分担心路径依赖类型。您通常可以允许推断它们,具体调用将具有“实际”类型。策略性地使用隐式=:=
或Leibniz
参数,您知道类型实际上是相同的,可以阻止事情失控。或者更简单地说,您可以要求类型相同:
def doSomething[T <: Moveable { type Self = T }](t: T): T =
t.somethingThatReturnsTDotSelf()
如果你想进入构图路线,我能想到的最好的方法是使用无形镜片(我无法与单片镜头相比,因为我没有使用它们):
trait Move[A] {
val lens: Lens[A, (WObject, Movable)]
}
/** This could be implicitly derived with Generic if you really want to -
or you could use Records. */
implicit def moveCorvette = new Move[Corvette] {
val lens = lens[Corvette].obj ~ lens[Corvette].movable
}
def moveTo[A: Move](obj: A, target: Vect2) = {
val l = Lens[A, (Wobject, Movable)]
val remainingMoves = l.get(obj)._2.movementLeft - 1
l.set(obj)((target, remainingMoves))
}
要将其应用于列表,您可以将列表保留为HList,以便了解所有元素的类型(例如,您的列表的类型为Fighter :: Corvette :: HNil
),或者在列表条目中包含证据与存在主义(例如trait ObjAndMove {type T; val obj: T; val evidence: Move[T]}
然后使用List[ObjAndMove]
)