【问题标题】:How to inherit generic trait multiple times in Scala?如何在Scala中多次继承通用特征?
【发布时间】:2021-04-21 20:27:54
【问题描述】:

我有一个看起来像这样的特质:

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] {}

【问题讨论】:

  • 那行不通,您的 foo 方法必须选择一个(并且只有一个)T 才能使用。您可以使用类型类而不是继承。这样您就可以提供三个不同的版本:IngredientFoo[Pizza, Cheese]IngredientFoo[Pizza, Pepperoni]IngredientFoo[Pizza, Oregano] 并调用 def foo[X,T](food: X, ingredient:T)(implicit handler: IngredientFoo[X,T]) = handler.foo(food, ingredient)
  • 只有当你想让食物和配料都可以独立扩展时,你才会这样做。如果没有,只需直接编码到您拥有的已知类型:def fooCheese
  • 您可能需要提供更多有关您在此处尝试执行的操作的背景信息。
  • 最好解释一下您要建模的元问题,以便我们提出替代方案。

标签: scala generics inheritance diamond-problem


【解决方案1】:

我将提供 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:

    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运行

【讨论】:

  • 我不知道...您现在也可以致电pizza.foo(123)。该参数不再绑定为Ingredientpizza.foo(pizza) 打印 Oregano。这似乎不是很有用。
  • 现在您可以致电pizza.foo(new Cucumbers {})(另一种与披萨本身完全无关的成分)
  • @Thilo,解决您可以定义中间特征。我在秒选项中表现出的理智。
  • 这是一个不错的解决方案!在我的情况下,我没有修改成分特性的选项。我没有在我的问题中指定这一点,但我们在这里有多种解决方案可能会更好。发布问题时我已经在答案中发布了我的答案,因为我在搜索时没有发现类似的问题,也许它会对某人有所帮助。
  • 我在解决方案 1 中注意到的另一件事:这里可以使用 trait Pizza extends Ingredient 代替 trait Pizza extends Pepperoni with Cheese with Oregano
【解决方案2】:

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

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 中只有几个方法,我们可以创建重载并封装我们的支持对象:

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 中使用导入来实现。添加重载会更好,因为客户端不需要编写额外的导入并且在范围内没有支持对象。使用重载的另一个好处是可以在子类中重写方法。

【讨论】:

    【解决方案3】:

    如果意图是Pizza 需要所有这三种成分,那么如何

    class Pizza extends Ingredient[(Cheese, Pepperoni, Oregano)] {
    
       def foo(ingredients: (Cheese, Pepperoni, Oregano)) = {
          case (cheese, pepperoni, oregano) =>
              // do something with them
       }
    
     }
    

    【讨论】:

    【解决方案4】:

    也许您可以执行以下操作,但您必须更改 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)]
    

    【讨论】:

    • 这需要修改Ingredient 本身,这是我想避免的。
    猜你喜欢
    • 2020-11-11
    • 1970-01-01
    • 2018-06-02
    • 1970-01-01
    • 2015-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多