我正在寻找一种干净的面向对象的方式来模拟以下(在Scala中):
一个人可以是:
这表明我们引入了Person
超类和子类:
Manager
Mathematician
TennisPlayer
HobbyistProgrammer
Volunteer
Painter
Manager
类包含以下方法:getSalary()
,workLongHours()
,findNewJob()
等。TennisPlayer
类包含以下方法:{{1 }},getWorldRanking()
,playGame()
等等。等等。此外,类strainAnkle()
中还有一些方法,例如Person
。一个生病的经理失去了工作,网球运动员在赛季中停止了比赛。
此外,课程不可变。也就是说,例如becomeSick()
返回一个新的strainAnkle()
,它有一个紧张的脚踝,但所有其他属性保持不变。
现在的问题是:我们如何模拟一个人既可以是TennisPlayer
又可以Manager
的事实?
解决方案保留不变性和类型安全性非常重要。
我们可以实现以下类:
TennisPlayer
ManagerAndMathematician
ManagerAndTennisPlayerAndPainter
但这导致了类的组合爆炸。
我们也可以使用traits(with state),但是我们如何实现ManagerAndPainter
这样的方法,这些方法需要返回一个具有相同特征的新人,但是具有{的新状态{1}}特质。同样,我们如何实现findNewJob()
等方法?
问题:如何在Scala中以干净的OO方式实现这一点?请记住:不可变性和类型安全是必须的。
答案 0 :(得分:6)
这对我而言并不像是继承的理想情况。也许你试图强迫事物进入继承模式,因为使用不可变值来处理组合似乎很尴尬。这是实现它的几种方法之一。
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))
}
}
答案 1 :(得分:4)
一种干净的面向对象的解决方法不一定是Scala特有的。人们可以遵循一般的面向对象设计原则,即支持组合而不是继承,并使用类似Strategy pattern的东西,这是避免类爆炸的标准方法。
答案 2 :(得分:2)
我认为这可以通过类似于type-safe builders的方式解决。
基本思想是通过类型参数表示“状态”,并使用含义来控制方法。例如:
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
来使用它。