доступ к элементу класса подтипа, если он присутствует во всех используемых подтипах - PullRequest
0 голосов
/ 06 февраля 2020

Я пишу репортер ScalaTest, и в настоящее время у меня есть два события класса дел 'TestSucceeded' и 'TestFailed', которые расширяют 'Event', и у меня есть функция:

def getInfo(event: Event) {
  println(event.suiteName)
}

строка suiteName является член обоих классов классов TestSucceeded и TestFailed, но не Event, и я не могу найти лучший способ реализации, чем

def getInfo(event: Event) {
  if(event.isInstanceOf[TestSucceeded])
    println(event.asInstanceOf[TestSucceeded].suiteName)
  else println(event.asInstanceOf[TestFailed].suiteName)
}

Ответы [ 4 ]

3 голосов
/ 06 февраля 2020

Я могу придумать три способа сделать то, что вы хотите, без изменения определения любого из классов.

Во-первых, классы типа :

    object EventTypeClasses {
      sealed trait Named[T] { def suiteName(t: T) String }
      implicit object Succeeded extends Named[TestSucceed] {
         def suiteName(t: TestSucceeded) = t.suitName
      }
      implicit object Failed extends Named[TestFailed] {
         def suiteName(t: TestFailed) = t.suitName
      }
    }
    ...
    import EventTypeClasses._
    def getInfo[T <: Event : Named](event: T): Unit = {
       implicitly[Named[T]].suitName(event)
    }
}

Еще один способ - трюк "pimp my library":

    object EventWithName {
      sealed trait It { def suitName: String }
      implicit class Succeeded(e: TestSucceeded) extends It { def suitName = e.suitName }
      implicit class Failed(e: TestFailed) extends It { def suitName = e.suiteName } 
     }

     ... 


     def getInfo(event: EventWithName.It) = println(event.suiteName)

     ...
     import EventWithName._
     getInfo(new TestSucceeded(...))

Наконец, structural types , При этом используется рефлексия, и, как правило, это вызывает недовольство, но если вы собираетесь использовать его только для тестирования, это, вероятно, хорошо:

   type Named = { def suiteName: String }
   def getInfo(event: Named) = println(event.suiteName)

Вы также можете просто использовать match-case, если это единственное место вам это нужно, чтобы оно не повторялось:

    def getInfo(event: Event) = event match {
      case e: TestSucceeded => println(e.suiteName)
      case e: TestFailed => println(e.suiteName)
      case _ => ???
    }

Вы можете комбинировать это с вышеприведенной «сутенеркой», чтобы она выглядела лучше (чтобы вам не приходилось иметь дело с странный EventWithName.It, и пользователям getInfo не нужно импортировать последствия:

    object EventWithName {
      implicit class Pimped(val event: Event) extends AnyVal { 
        def suitName = event match { 
           case e: TestSucceeded => e.suiteName
           case e: TestFailed => e.suiteName
           case _ => ???
        }
      }
     }
     import EventWithName._ 
     def getInfo(e: Event) = {
       println(e.suiteName)
       doOtherThingsWithEvent(e)
     }

     ... 

     // No need to import implicits here
     getInfo(new TestSucceeded(...))
2 голосов
/ 06 февраля 2020

Рассмотрим алгебра c тип данных через sealed abstract class, например,

sealed abstract class Event(val suiteName: String)
case class TestSucceeded(suite: String) extends Event(suite)
case class TestFailed(suite: String) extends Event(suite)

def getInfo(event: Event): Unit = println(event.suiteName)

getInfo(TestSucceeded("ASpec"))   // ASpec
getInfo(TestSucceeded("BSpec"))   // BSpec
1 голос
/ 06 февраля 2020

Светоотражающий вызов, использующий структурный тип, сделает то, что вы хотите. Это не рекомендуется для производственного кода, но, вероятно, хорошо в тестовом коде

def getInfo(event: { def suiteName: String }) =
  event.suiteName
1 голос
/ 06 февраля 2020

Итак, у вас есть что-то вроде этого:

sealed trait Event
case class TestSucceeded(suiteName: String) extends Event
case class TestFailed(suiteName: String) extends Event
case class TestWithNoSuiteName() extends Event

Один из способов сделать это более чистым - использовать сопоставление с образцом:

def getInfo(event: Event) = println(event match {
  case e: TestSucceeded => e.suiteName
  case e: TestFailed => e.suiteName
})

getInfo(TestSucceeded("foo")) // foo
getInfo(TestFailed("bar")) // bar
getInfo(TestWithNoSuiteName()) // scala.MatchError

Другим способом было бы определить тип для предотвращения передачи Event без suiteName во время компиляции:

import scala.language.reflectiveCalls
type SuiteName = { val suiteName: String }
def getInfo(event: Event with SuiteName) = {
  println(event.suiteName)
}

getInfo(TestSucceeded("foo")) // foo
getInfo(TestFailed("bar")) // bar
getInfo(TestWithNoSuiteName()) // doesn't compile
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...