2014-09-30 2 views
4

불변의 오브젝트로 도메인을 모델링 할 때 문제가 있습니다.불변의 오브젝트로 상속 + 변경 가능 상태 시뮬레이션

변경 가능한 디자인

하나 개의 기본 특성 WObject (세계 객체)와 OwnedObj 같은 특정 작업을 구현하기위한 특성의 부하 (HP/소유자/takeDamage), 이동식 (movementLeft /의 moveTo), 파이터 (공격/공격). , 당신이 (배를 이동 말할 수) 사용자가 어떤 작업을 수행하고자하는 경우

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 새로운 OBJ를 반환하는

경우 불변의 디자인 요법, 어떤 타입이 반환합니까?

trait Movable[Self <: Movable[Self]] 접근 방식을 사용해 보았습니다.하지만이 경우 Movable [_]을 사방으로 가져 가야하며 실존 유형이 빠져 있어야합니다. Movable[_] with Fighter[_]을 원하면 어떻게해야합니까? _ 같은 유형입니까?

는 또한 유형 경계 접근 방식과 특성 내부 추상 형식 자체를 시도했다, 그러나 이것은 다음과 같은 경우에 털이 얻을 시작합니다

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 
} 

그리고는 코르벳 동반자 객체에서 구현 될 것 typeclasses에서 작업을 정의합니다.

그러나 제한을 지정하는 방법을 잘 모르겠습니다.

클라이언트 측에서 어떻게 이동 작업을 구현합니까? (Movable[T] with Fighter[T] forSome {type T}) : Polymorphic updates in an immutable class hierarchy

답변

3

당신은 실존를 사용하여 "_에서와 같은"사건을 작성할 수 있습니다

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) 
} 

도움말 관련

: 감사.

정확하게 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)) 
} 

이 목록에이를 적용하려면 (예 : 목록 유형 Fighter :: Corvette :: HNil의 모든 요소의 종류를 알 수 있도록 당신이 HList으로 목록을 유지 중), 또는 실존 적 (예 : trait ObjAndMove {type T; val obj: T; val evidence: Move[T]} 다음에 List[ObjAndMove]을 사용하여) 목록 항목에 증거를 포함 시키십시오.

+0

attackReachable은 2 가지를 할 수 있습니다. 1) 대상을 공격 할 수없는 경우 '데이터'만 반환해야합니다. 2) 목표에 도달 할 수 있으면 업데이트 된 데이터를 반환해야하며 대상은 변경되고 자기는 공격 대상으로 표시됩니다. 왜냐하면 컴파일러는 Self == data.value._2.Self (Self의 수정에서 되돌아 온다.) – arturaz

+0

또한 Monocle vs Shapeless 렌즈에 대해 자세히 설명 할 수 없기 때문에 self type 1이 불가능해진다. – arturaz

+0

다른 유형의 목록 만 가지고 있다면 moveTo를 어떻게 호출합니까? (WorldObject의 기본 유형을 말할 수 있습니다)? 당신은 typeclass가 차기 위해 구체적인 유형까지 패턴 매치를해야합니까? – arturaz

관련 문제