组合子类型(斯卡拉)

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

一个人可以是:

  • 某公司的经理
  • 一位数学家
  • 世界级的网球运动员
  • 爱好者程序员
  • 当地一所学校的志愿者
  • 一位富有创意的画家
  • 这表明我们引入了一个Person超类和子类:

  • 班级Manager
  • 一流的Mathematician
  • class 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 = 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))
      }
    }
    

    一个干净的面向对象的解决方案并不一定是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

    链接地址: http://www.djcxy.com/p/82691.html

    上一篇: Combinatorial Subtyping (in Scala)

    下一篇: Scala immutable objects and traits with val fields