组合子类型(斯卡拉)
我正在寻找一种干净的面向对象的方式来模拟以下(在Scala中):
一个人可以是:
这表明我们引入了一个Person
超类和子类:
Manager
Mathematician
TennisPlayer
HobbyistProgrammer
Volunteer
Painter
Manager
类具有诸如getSalary()
, workLongHours()
, findNewJob()
等方法TennisPlayer
类具有如下方法: getWorldRanking()
, playGame()
, strainAnkle()
等。 另外还有类Person
中的方法,比如becomeSick()
。 一位有病的经理失去了工作,网球运动员在赛季中停止了比赛。
此外,这些类是不可变的 。 也就是说,例如, strainAnkle()
返回一个新的TennisPlayer
,它有一个紧张的脚踝,但其中所有其他属性保持不变。
现在的问题是:我们如何模拟一个人既可以是Manager
又可以是TennisPlayer
?
该解决方案保留了不变性和类型安全性是非常重要的。
我们可以实现以下类:
ManagerAndMathematician
ManagerAndTennisPlayerAndPainter
ManagerAndPainter
但是这会导致类的组合式爆炸。
我们也可以使用特征(与状态),但是我们该如何实现诸如findNewJob()
这样的方法,它需要返回一个混合了相同特征的新人,但是具有Manager
特征的新状态。 同样,我们如何实现诸如becomeSick()
?
问题:你如何在Scala中以干净的面向对象的方式实现它? 记住:不可变性和类型安全是必须的。
这对我来说不是理想的继承理由。 也许你试图强制东西进入继承模式,因为处理具有不可变值的组合似乎很尴尬。 以下是几种方法之一。
object Example {
abstract class Person(val name: String) {
def occupation: Occupation
implicit val self = this
abstract class Occupation(implicit val practitioner: Person) {
def title: String
def advanceCareer: Person
}
class Programmer extends Occupation {
def title = "Code Monkey"
def advanceCareer = practitioner
}
class Student extends Occupation {
def title = "Undecided"
def advanceCareer = new Person(practitioner.name) {
def occupation = new Programmer
}
}
}
def main(args: Array[String]) {
val p = new Person("John Doe") { def occupation = new Student }
val q = p.occupation.advanceCareer
val r = q.occupation.advanceCareer
println(p.name + " is a " + p.occupation.title)
println(q.name + " is a " + q.occupation.title)
println(r.name + " is a " + r.occupation.title)
println("I am myself: " + (r eq r.occupation.practitioner))
}
}
让我们试试看:
scala> Example.main(Array())
John Doe is a Undecided
John Doe is a Code Monkey
John Doe is a Code Monkey
I am myself: true
所以这个工作方式有点有用。
这里的诀窍是,每次职业(这是一个内部阶层)决定改变事情时,你就创建了你的人的匿名子类。 它的工作是创建一个完整的新角色的新人; 这可以通过implicit val self = this
和Occupation
上的隐式构造函数来帮助解决,这会有助于自动加载人员的正确实例。
你可能需要一个职业列表,因此可能需要帮助者方法来重新生成专业列表。 就像是
object Example {
abstract class Person(val name: String) {
def occupations: List[Occupation]
implicit val self = this
def withOccupations(others: List[Person#Occupation]) = new Person(self.name) {
def occupations = others.collect {
case p: Person#Programmer => new Programmer
case s: Person#Pirate => new Pirate
}
}
abstract class Occupation(implicit val practitioner: Person) {
def title: String
def addCareer: Person
override def toString = title
}
class Programmer extends Occupation {
def title = "Code Monkey"
def addCareer: Person = withOccupations( this :: self.occupations )
}
class Pirate extends Occupation {
def title = "Sea Monkey"
def addCareer: Person = withOccupations( this :: self.occupations )
}
}
def main(args: Array[String]) {
val p = new Person("John Doe") { def occupations = Nil }
val q = (new p.Programmer).addCareer
val r = (new q.Pirate).addCareer
println(p.name + " has jobs " + p.occupations)
println(q.name + " has jobs " + q.occupations)
println(r.name + " has jobs " + r.occupations)
println("I am myself: " + (r eq r.occupations.head.practitioner))
}
}
一个干净的面向对象的解决方案并不一定是Scala特有的。 人们可以遵循一般的面向对象设计原则,即偏好合成而不是继承,并使用类似Strategy模式的东西,这是避免类爆炸的标准方法。
我认为这可以通过类似于类型安全的建造者的方式来解决。
基本思想是通过类型参数表示“状态”,并使用隐含来控制方法。 例如:
sealed trait TBoolean
final class TTrue extends TBoolean
final class TFalse extends TBoolean
class Person[IsManager <: TBoolean, IsTennisPlayer <: TBoolean, IsSick <: TBoolean] private (val name: String) {
// Factories
def becomeSick = new Person[TFalse, IsTennisPlayer, TTrue](name)
def getBetter = new Person[IsManager, IsTennisPlayer, TFalse](name)
def getManagerJob(initialSalary: Int)(implicit restriction: IsSick =:= TFalse) = new Person[TTrue, IsTennisPlayer, IsSick](name) {
protected override val salary = initialSalary
}
def learnTennis = new Person[IsManager, TTrue, IsSick](name)
// Other methods
def playGame(implicit restriction: IsTennisPlayer =:= TTrue) { println("Playing game") }
def playSeason(implicit restriction1: IsSick =:= TFalse, restriction2: IsTennisPlayer =:= TTrue) { println("Playing season") }
def getSalary(implicit restriction: IsManager =:= TTrue) = salary
// Other stuff
protected val salary = 0
}
object Person {
def apply(name: String) = new Person[TFalse, TFalse, TFalse](name)
}
它会变得很罗嗦,如果事情变得复杂,你可能需要像HList那样的东西。 这是另一个实现,它更好地分离了问题:
class Person[IsManager <: TBoolean, IsTennisPlayer <: TBoolean, IsSick <: TBoolean] private (val name: String) {
// Factories
def becomeSick = new Person[TFalse, IsTennisPlayer, TTrue](name)
def getBetter = new Person[IsManager, IsTennisPlayer, TFalse](name)
def getManagerJob(initialSalary: Int)(implicit restriction: IsSick =:= TFalse) = new Person[TTrue, IsTennisPlayer, IsSick](name) {
protected override val salary = initialSalary
}
def learnTennis = new Person[IsManager, TTrue, IsSick](name)
// Other stuff
protected val salary = 0
}
object Person {
def apply(name: String) = new Person[TFalse, TFalse, TFalse](name)
// Helper types
type PTennisPlayer[IsSick <: TBoolean] = Person[_, TTrue, IsSick]
type PManager = Person[TTrue, _, _]
// Implicit conversions
implicit def toTennisPlayer[IsSick <: TBoolean](person: PTennisPlayer[IsSick]) = new TennisPlayer[IsSick]
implicit def toManager(person: PManager) = new Manager(person.salary)
}
class TennisPlayer[IsSick <: TBoolean] {
def playGame { println("Playing Game") }
def playSeason(implicit restriction: IsSick =:= TFalse) { println("Playing Season") }
}
class Manager(salary: Int) {
def getSalary = salary
}
为了获得更好的错误消息,你应该使用TBoolean的专用版本(即,HasManagerJob,PlaysTennis等),以及注释implicitNotFound
。