如何在 Scala 中多次继承泛型特征?

How to inherit generic trait multiple times in Scala?

我有这样的特质:

trait Ingredient[T] {
  def foo(t: T): Unit = {
    // Some complex logic
  }
}

以及我想要方法的类型:

class Cheese
class Pepperoni
class Oregano

如何制作另一个具有方法的特征:

def foo(t: Cheese)
def foo(t: Pepperoni)
def foo(t: Oregano)

不复制代码?以下将不起作用,因为它多次从同一特征非法继承:

trait Pizza extends Ingredient[Cheese] with Ingredient[Pepperoni] with Ingredient[Oregano] {}

解决方案是创建在 Pizza 特性中扩展 Ingredient 特性的对象。这样我们就有了:

trait Pizza {
  object CheeseIngredient extends Ingredient[Cheese]
  object PepperoniIngredient extends Ingredient[Pepperoni]
  object OreganoIngredient extends Ingredient[Oregano]
}

然后我们可以在继承的对象上调用我们的方法:

object LargePizza extends Pizza {
  def bar(cheese: Cheese, pepperoni: Pepperoni, oregano: Oregano): Unit = {
    CheeseIngredient.foo(cheese)
    PepperoniIngredient.foo(pepperoni)
    OreganoIngredient.foo(oregano)
  }
}

或者,如果我们在 Ingredient 特征中只有几个方法,我们可以创建重载并封装我们的支持对象:

trait Pizza {
  private object CheeseIngredient extends Ingredient[Cheese]
  private object PepperoniIngredient extends Ingredient[Pepperoni]
  private object OreganoIngredient extends Ingredient[Oregano]

  def foo(cheese: Cheese): Unit = CheeseIngredient.foo(cheese)
  def foo(pepperoni: Pepperoni): Unit = PepperoniIngredient.foo(pepperoni)
  def foo(oregano: Oregano): Unit = OreganoIngredient.foo(oregano)
}

object LargePizza extends Pizza {
  def bar(cheese: Cheese, pepperoni: Pepperoni, oregano: Oregano): Unit = {
    foo(cheese)
    foo(pepperoni)
    foo(oregano)
  }
}

同样可以通过在 LargePizza 中使用导入来实现。添加重载更好,因为客户端不需要编写额外的导入并且在范围内没有支持对象。使用重载的另一个好处是可以在子 类.

中覆盖这些方法

我将提供 2 个解决方案:

  1. 如果您可以将 CheesePepperoniOregano 定义为特征,您可以:

    trait Ingredient {
       def foo[T <: Ingredient](t: T): Unit = {
         println(t)
       }
     }
    

    然后扩展它:

    trait Cheese extends Ingredient {
      override def toString: String = "Cheese"
    }
    trait Pepperoni extends Ingredient {
      override def toString: String = "Pepperoni"
    }
    trait Oregano extends Ingredient {
      override def toString: String = "Oregano"
    }
    

    用法是:

    trait Pizza extends Ingredient
    
    val pizza = new Pizza { }
    
    pizza.foo(new Cheese { })
    pizza.foo(new Pepperoni { })
    pizza.foo(new Oregano { })
    

    代码 运行 在 Scastie

  2. 使用密封特性。这种方法将成分与问题中绑定的最终产品分开:

    sealed trait Ingredient
    sealed trait PizzaIngredient extends Ingredient
    case object Cheese extends PizzaIngredient
    case object Pepperoni extends PizzaIngredient
    case object Oregano extends PizzaIngredient
    case object Cucumber extends Ingredient
    

    然后定义Pizza特征:

    trait Pizza {
        def foo[T <: PizzaIngredient](t: T): Unit = {
          println(t)
        }
    }
    

    用法是:

    val pizza = new Pizza { }
    pizza.foo(Cheese)
    pizza.foo(Pepperoni)
    pizza.foo(Oregano)
    

    代码 运行 在 Scastie

也许您可以执行以下操作,但您必须更改 foo 方法:

    trait Ingredient[T] {

   def foo(ingredients: T*): Unit = {
     for (i <- ingredients) {
        // some complex logic
      }
   }
}
    class Cheese 
    class Pepperoni 
    class Oregano 

 
trait Pizza  extends Ingredient[(Cheese,Pepperoni,Oregano)]

如果 Pizza 需要所有这三种成分,

怎么样?
class Pizza extends Ingredient[(Cheese, Pepperoni, Oregano)] {

   def foo(ingredients: (Cheese, Pepperoni, Oregano)) = {
      case (cheese, pepperoni, oregano) =>
          // do something with them
   }

 }