在 Scala 类型中编码复杂的层次结构规则
Encoding complex hierarchy rules in Scala types
假设我在树结构中有一个复杂的节点层次结构,其中某些类型的节点只能有某些(可能很多,甚至可能包括它自己的类型)类型的子节点。
假设我们有一棵员工树,想要编码哪些类型的员工可以成为其他类型的老板。
一种方法是定义我们的 Employee
类型,President
、CTO
、Manager
,以及它们对应的 "Subordinate" 类型,PresidentSubordinate
, CTOSubordinate
, ManagerSubordinate
。然后你可以在他们可以成为老板的任何 Employee
上扩展 *Subordinate
。然后你可以这样做:
sealed trait Employee
sealed trait PresidentSubordinate extends Employee
sealed trait VicePresidentSubordinate extends Employee
sealed trait CTOSubordinate extends Employee
sealed trait ManagerSubordinate extends Employee
sealed trait DeveloperSubordinate extends Employee
case class President(subordinates: Seq[PresidentSubordinate])
case class VicePresident(subordinates: Seq[VicePresidentSubordinates])
extends PresidentSubordinate
case class CTO(subordinates: Seq[CTOSubordinate])
extends PresidentSubordinate
with VicePresidentSubordinate
case class Manager(subordinates: Seq[ManagerSubordinate])
extends VicePresidentSubordinate
case class Developer(subordinates: Seq[DeveloperSubordinate])
extends CTOSubordinate
with ManagerSubordinate
with VicePresidentSubordinate
with DeveloperSubordinate // Devs can be bosses of other devs
// Note, not all employees have subordinates, no need for corresponding type
case class DeveloperIntern()
extends ManagerSubordinate
with DeveloperSubordinate
这种方法对我的六个左右的树节点类型很有效,但我不知道这是否是最好的方法,因为类型的数量增加到 10 种或 50 种。也许有一个更简单的解决方案,可能使用显示的模式 here 是合适的。像
class VicePresidentSubordinate[T <: Employee]
object VicePresidentSubordinate {
implicit object CTOWitness extends VicePresidentSubordinate[CTO]
implicit object ManagerWitness extends VicePresidentSubordinate[Manager]
implicit object DeveloperWitness extends VicePresidentSubordinate[Developer]
}
但是我不确定结果 类 会是什么样子,因为这显然无法编译:
case class VicePresident(subordinates: Seq[VicePresidentSubordinate]) extends Employee
感谢您的帮助!
当您说 "it works, but can I do something else?" 时,我不完全确定您要查找的属性。
过去我做过这样的事情,不在类型系统中做所有事情可能会变得有用。例如,如果您的角色动态变化,您可能希望拥有单个 Employee class(具有 "title" 属性和 "subordinates")属性并在运行时验证标题和依赖项,例如,基于在配置文件上。
如果您的结构不是动态的,那么您可以将其保留在代码中。我会使用抽象类型来减少特征并表示您想要的关系。例如:
// An employee has a name
trait Employee {
val name: String
}
// A subordinate has a manager
trait Subordinate[M <: Manager[M]] {
val manager: M
manager.subordinates += this
}
// A manager has subordinates
trait Manager[M <: Manager[M]] {
val subordinates = mutable.Set[Subordinate[M]]()
}
// A middle level can both have a manager and manage others.
trait Middle[M <: Manager[M], S <: Manager[S]] extends Manager[M] with Subordinate[S]
// President does not have a manager, it manages only.
case class President(name: String) extends Manager[President]
// Some traits that report to the president.
case class VicePresident(name: String, manager: President) extends Middle[VicePresident, President]
case class CTO(name: String, manager: President) extends Middle[CTO, President]
// An intern can't manage
case class Intern(name: String, manager: CTO) extends Subordinate[CTO]
// A simple test
def main(args: Array[String]): Unit = {
val bob = President("Bob")
val alice = VicePresident("Alice", bob)
val lucas = CTO("Lucas", bob)
val sam = Intern("Sam", lucas)
println(alice.manager) // President(Bob)
println(bob.subordinates) // Set(CTO(Lucas,President(Bob)), VicePresident(Alice,President(Bob)))
sam.subordinates // Compiler error
}
注意保持下属和经理的指针是如何完全由基本特征完成的。 Objects 立即为您添加的任何新 class 链接!
假设我在树结构中有一个复杂的节点层次结构,其中某些类型的节点只能有某些(可能很多,甚至可能包括它自己的类型)类型的子节点。
假设我们有一棵员工树,想要编码哪些类型的员工可以成为其他类型的老板。
一种方法是定义我们的 Employee
类型,President
、CTO
、Manager
,以及它们对应的 "Subordinate" 类型,PresidentSubordinate
, CTOSubordinate
, ManagerSubordinate
。然后你可以在他们可以成为老板的任何 Employee
上扩展 *Subordinate
。然后你可以这样做:
sealed trait Employee
sealed trait PresidentSubordinate extends Employee
sealed trait VicePresidentSubordinate extends Employee
sealed trait CTOSubordinate extends Employee
sealed trait ManagerSubordinate extends Employee
sealed trait DeveloperSubordinate extends Employee
case class President(subordinates: Seq[PresidentSubordinate])
case class VicePresident(subordinates: Seq[VicePresidentSubordinates])
extends PresidentSubordinate
case class CTO(subordinates: Seq[CTOSubordinate])
extends PresidentSubordinate
with VicePresidentSubordinate
case class Manager(subordinates: Seq[ManagerSubordinate])
extends VicePresidentSubordinate
case class Developer(subordinates: Seq[DeveloperSubordinate])
extends CTOSubordinate
with ManagerSubordinate
with VicePresidentSubordinate
with DeveloperSubordinate // Devs can be bosses of other devs
// Note, not all employees have subordinates, no need for corresponding type
case class DeveloperIntern()
extends ManagerSubordinate
with DeveloperSubordinate
这种方法对我的六个左右的树节点类型很有效,但我不知道这是否是最好的方法,因为类型的数量增加到 10 种或 50 种。也许有一个更简单的解决方案,可能使用显示的模式 here 是合适的。像
class VicePresidentSubordinate[T <: Employee]
object VicePresidentSubordinate {
implicit object CTOWitness extends VicePresidentSubordinate[CTO]
implicit object ManagerWitness extends VicePresidentSubordinate[Manager]
implicit object DeveloperWitness extends VicePresidentSubordinate[Developer]
}
但是我不确定结果 类 会是什么样子,因为这显然无法编译:
case class VicePresident(subordinates: Seq[VicePresidentSubordinate]) extends Employee
感谢您的帮助!
当您说 "it works, but can I do something else?" 时,我不完全确定您要查找的属性。
过去我做过这样的事情,不在类型系统中做所有事情可能会变得有用。例如,如果您的角色动态变化,您可能希望拥有单个 Employee class(具有 "title" 属性和 "subordinates")属性并在运行时验证标题和依赖项,例如,基于在配置文件上。
如果您的结构不是动态的,那么您可以将其保留在代码中。我会使用抽象类型来减少特征并表示您想要的关系。例如:
// An employee has a name
trait Employee {
val name: String
}
// A subordinate has a manager
trait Subordinate[M <: Manager[M]] {
val manager: M
manager.subordinates += this
}
// A manager has subordinates
trait Manager[M <: Manager[M]] {
val subordinates = mutable.Set[Subordinate[M]]()
}
// A middle level can both have a manager and manage others.
trait Middle[M <: Manager[M], S <: Manager[S]] extends Manager[M] with Subordinate[S]
// President does not have a manager, it manages only.
case class President(name: String) extends Manager[President]
// Some traits that report to the president.
case class VicePresident(name: String, manager: President) extends Middle[VicePresident, President]
case class CTO(name: String, manager: President) extends Middle[CTO, President]
// An intern can't manage
case class Intern(name: String, manager: CTO) extends Subordinate[CTO]
// A simple test
def main(args: Array[String]): Unit = {
val bob = President("Bob")
val alice = VicePresident("Alice", bob)
val lucas = CTO("Lucas", bob)
val sam = Intern("Sam", lucas)
println(alice.manager) // President(Bob)
println(bob.subordinates) // Set(CTO(Lucas,President(Bob)), VicePresident(Alice,President(Bob)))
sam.subordinates // Compiler error
}
注意保持下属和经理的指针是如何完全由基本特征完成的。 Objects 立即为您添加的任何新 class 链接!