组合子类型(在Scala中)

时间:2011-05-09 08:53:43

标签: scala

我正在寻找一种干净的面向对象的方式来模拟以下(在Scala中):

一个人可以是:

  • 某公司的经理
  • 数学家
  • 世界级的网球运动员
  • 业余爱好者程序员
  • 当地学校的志愿者
  • 创意画家

这表明我们引入了Person超类和子类:

  • class Manager
  • class Mathematician
  • class TennisPlayer
  • class HobbyistProgrammer
  • class Volunteer
  • class 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方式实现这一点?请记住:不可变性和类型安全是必须的。

3 个答案:

答案 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 = thisOccupation上的隐式构造函数帮助了这一点,它有助于自动加载正确的人员实例。

您可能需要一个职业列表,因此可能需要帮助方法来重新生成职业列表。像

这样的东西
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来使用它。