Правильно ли я использую (Scala) дженерики? - PullRequest
0 голосов
/ 19 октября 2018

В настоящее время у меня есть код со структурой, подобной следующей:

object Example {
  val doctor : Healer[Cancer]   = new Doctor
  val parent : Healer[Cold]     = new Parent
  val generic: Healer[Sickness] = new Generic

  def cure(sickness: Sickness): Boolean = sickness match {
    case cancer: Cancer => doctor.cure(cancer)
    case cold  : Cold   => parent.cure(cold)
    case other          => generic.cure(other)
  }
}

class Sickness
class Cancer extends Sickness
class Cold   extends Sickness
// other sicknesses

abstract class Healer[A <: Sickness] {
  def cure(sickness: A): Boolean
}

abstract class Treatment[A <: Sickness] {
  def cure(sickness: A): Boolean
}

class Doctor[A <: Cancer] extends Healer[A] {
  val treatments: List[Treatments[A]] = List(
    new Chemotherapy,
    new Surgery,
    new HopesAndPrayers
  )

  def cure(sickness: A): Boolean = {
    // ... choose a treatment
    treatment.cure(sickness)
  }
}

class Chemotherapy[A <: Cancer] extends Treatment[A] {
  def cure(cancer: A): Boolean = {
    // without generics, needs a check for Cancer
  }
}

// other Healers (e.g. Parent, Generic) and other Treatments (e.g. BedRest, HealthyFood)

Это как дженерики правильно / нормально используются?Должен ли / мог ли я выделить его на Treatment и / или Healer?Если да, то как?

Чтобы объяснить более подробно, это началось без обобщений, но когда я начал создавать подклассы Treatments, такие как Chemotherapy, я закончил с cure(sickness: Sickness) методами, которые содержат sickness match { case cancer: Cancer => ... }.Казалось, что если бы у меня был класс SomeSpecificTreatment, который имел дело только с SomeSpecificSickness, было бы разумно иметь метод cure(sickness: SomeSpecificSickness), а не cure(sickness: Sickness).

Поэтому я добавил дженерики в Treatment, и они, следовательно, пробились до Example.Когда это происходит, я обычно чувствую, что делаю что-то не так.Я особенно подозрительно отношусь к Example#cure.С большим количеством подклассов Sickness список дел может стать довольно длинным, и я инстинктивно чувствую, что это можно сделать по-другому и лучше (не так ли?).Я подумал об использовании Map для выполнения чего-то вроде healers.getOrElse(sickness.getClass, generic), но он не будет вести себя так же, как типы сопоставления с образцом, и у меня все равно обычно возникают проблемы с вложенными обобщениями.

Поскольку это может произойти: У меня нет контроля над Sickness или его подклассами, и я не могу изменить подпись Example#cure

1 Ответ

0 голосов
/ 19 октября 2018

Обычно я нахожу, что при проектировании связанных иерархий классов рано или поздно происходит что-то, что разрушает модель и приводит к взлому.

Рассматривали ли вы использование классов типов, которые очень распространены в функциональных языках программирования?.

case class Cancer(name: String)
case class Cold(name: String)

sealed trait Healer[S] {
  def name: String
  def cure(sickness: S): Unit = println(s"$name curing $sickness")
}

implicit val oncologist = new Healer[Cancer] {
  val name = "Oncologist"
}

implicit val mum = new Healer[Cold] {
  val name = "Mum"
}

def cure[S : Healer](sickness: S) = { 
  val healer = implicitly[Healer[S]]  
  healer.cure(sickness)
}

scala> cure(Cancer("Lung cancer"))
Oncologist curing Cancer(Lung cancer)

scala> cure(Cold("Flu"))
Mum curing Cold(Flu)

В противном случае несвязанные типы могут быть членами класса типов, который определяет какое-то общее поведение, например, сортировку.

Например, нам могут потребоваться целители для несвязанных состояний Рак, Ожоги иBrokenLeg, который может не вписаться в хорошую иерархию классов.Другое преимущество состоит в том, что мы можем ограничить область применения класса типов.Целитель может иметь смысл в больнице, где мы хотим вылечить пациента.В страховой компании нас может заинтересовать что-то еще, например, стоимость лечения от какого-либо заболевания.

...