2017-06-19 70 views
0

在我的單元測試中,我需要生成各種類型的事件,這些事件都是從一個抽象的Event類繼承而來,但創建方式不同。例如,事件A和B具有下列特徵:在Scala中創建需要不同構造函數參數的子類型的一般方法

def makeEventA(a: Int, b: String): EventA 

def makeEventB(p: String, q: Long, r: Long): EventB 

核心邏輯的兩個事件是相同的,對Event任何子類的定義,所以我想創造行爲的功能和再利用這些也是。爲此,我想創造一個「機」特質從中我可以讓每個人事件並使用單元測試中結果:

trait EventMaker { 
    def make[T <: Event](...): T 
} 

class EventAMaker extends EventMaker { 
    override def make[EventA](...) = /* custom implementation */ 
} 

// similar for EventBMaker 

當時的想法是有單元測試,我在正確的類混合並基於此調用適當的make方法。我想用ScalaTest內置的ScalaCheck支持來改變參數,這樣我就不必硬編碼所有的值和不同的事件,並且基本上覆制粘貼相同的代碼(參數列表除外)。

我在努力的是make的簽名。它應該爲具體的類別提供適當的參數,這些類別在數量,類型和名稱上都不相同。我試圖達到甚至是可行的/明智的,有沒有更好的方法,或者我該如何繼續?

我想到的是要匹配的各種事件,並調用相應的make方法(工廠設計模式),而導致同一問題的替代方法:

def make[T <: Event](eventType: T, ...): T = eventType match { 
    case EventA => new EventMakerA(...) 
    case EventB => new EventMakerB(...) 
    case _ => ... 
} 

另一種選擇是使用Option爲所有參數默認爲None,以便籤名匹配。但是,這看起來很浪費,並不能完全改善可讀性。

回答

0

我相信,你最好的選擇是將參數打包到不透明的Tuple中,然後動態或靜態檢查,如果元組匹配Event所需的參數。

假設下面的設置:

import scala.reflect._ 

sealed trait Event 

case class EventA(a: Int, b: String) extends Event 
object EventA { 
    // this val is needed only for the dynamic checking approach 
    final val tag = classTag[EventA] 
} 

case class EventB(p: String, q: Long, r: Long) extends Event 
object EventB { 
    // this val is needed only for the dynamic checking approach 
    final val tag = classTag[EventB] 
} 

如果動態檢查,你只是比賽反對論據,並檢查它們是否與正確的長度和正確的元素類型的元組。這不是類型安全的,並且可以在運行時拋出ClassCastException S,如果事件的參數有類型的類型參數(例如,l: List[Int]t: (Int, Double)),這樣類型擦除踢

的代碼可以在不同的方式組織:

def make[T <: Event, Args](obj: Class[T], args: Args)(implicit tag: ClassTag[T]): T = ((tag, args) match { 
    case (EventA.tag, (a: Int, b: String)) => EventA(a, b) 
    case (EventB.tag, (p: String, q: Long, r: Long)) => EventB(p, q, r) 
    case (otherTag, otherArgs) => sys.error("wrong args for tag") 
}).asInstanceOf[T] 

scala> make(classOf[EventA], (10, "foo")) 
res4: EventA = EventA(10,foo) 

scala> make(classOf[EventB], ("bar", 10L, 20L)) 
res5: EventB = EventB(bar,10,20) 

或者你也可以解壓縮到一個類能夠通過精確Event類型爲類型參數:

class EventMaker[T <: Event] { 
    def apply[Args](args: Args)(implicit tag: ClassTag[T]): T = ((tag, args) match { 
    case (EventA.tag, (a: Int, b: String)) => EventA(a, b) 
    case (EventB.tag, (p: String, q: Long, r: Long)) => EventB(p, q, r) 
    case (otherTag, otherArgs) => sys.error("wrong args for tag") 
    }).asInstanceOf[T] 
} 
def make2[T <: Event] = new EventMaker[T] 

scala> make2[EventA](10, "foo") 
res6: EventA = EventA(10,foo) 

scala> make2[EventB]("bar", 10L, 20L) 
res7: EventB = EventB(bar,10,20) 

也可以使用shapeless庫(或使用手寫宏)靜態檢查參數。如果提供的參數不匹配case類所需的參數,這將導致編譯錯誤。此外,如果個別事件由案例分類表示,則此效果最佳,但也可以用於支持一些功能shapeless.ops.function.FnToProduct

import shapeless._ 

class EventMaker3[T <: Event] { 
    def apply[Args, H <: HList, H0 <: HList](args: Args)(implicit 
    genObj: Generic.Aux[T, H],  // conversion between HList and case class 
    genArgs: Generic.Aux[Args, H0], // conversion between HList and arguments tuple 
    ev: H0 =:= H     // assert that HList representations of the case class 
            // and provided arguments are the same 
): T = genObj.from(genArgs.to(args)) 
} 
def make3[T <: Event] = new EventMaker3[T] 

scala> make3[EventA](10, "foo") 
res8: EventA = EventA(10,foo) 

scala> make3[EventB]("bar", 10L, 20L) 
res9: EventB = EventB(bar,10,20) 

scala> make3[EventA](1, 2) 
<console>:21: error: Cannot prove that H0 =:= H. 
     make3[EventA](1, 2) 
        ^
相關問題