Как да обозначим вътрешен сингълтън в Scala

За да упражня способностите си за ООП върху специфични за Scala функции, се опитах да проектирам игра, в която имам Player клас. Той има абстрактен метод play, който решава, като има списък с играчи (различен от играча, на който е извикан методът), какво действие да предприеме. Исках да попреча на метода play да променя състоянието на тези други играчи. Правилният начин да играете е да хвърлите Spell и да оставите системата да отрази ефекта му върху другите играчи.

И все пак методът play се нуждае от достъп за четене до другите играчи, за да реши стратегия. Така създадох вътрешен сингълтон ReadOnlyPlayer. Направих го сингълтън, за да предотвратя копирането отново и отново и просто да връщам този сингълтон всеки път.

abstract class Player(private var _health: Int = 0) {
    Check.isPositive(_health)

    def health = _health

    def play(players: List[/*?*/]) // how to denote inner type ReadOnlyPlayer ?

    def hit(damage: Int) = { _health = max(0, _health - damage); this }

    def safeCopy = ReadOnlyPlayer

    final object ReadOnlyPlayer extends Player {
        override def health = _health

        // noop
        override def hit (damage: Int  ) = this
        override def play(players: List[/*?*/]) = ()
    }
}

Не мога да го накарам да се компилира заради реда, на който съм поставил коментар. Знам за няколко заобиколни решения на този проблем:

  • ако беше клас вместо сингълтън, можех да използвам Player#ReadOnlyPlayer. Опитах, работи добре. Това обаче изисква да се създава ново копие всеки път, така че ако исках да направя това, всъщност би било по-добре да създам отделен неизменен клас.

  • Бих могъл ръчно да внедря сингълтон модел и винаги да връщам едно и също копие на този клас.

  • Бих могъл да направя класа частен и да декларирам само Player, но искам клиентите ми да знаят, че изрично знаят, че няма да могат да променят екземпляра Player. Мога да направя това, като използвам запечатана празна характеристика със смислено име.

Знам как мога да се справя с това по различни начини, така че въпросът ми е по-скоро от любопитство: как може да се обозначи вътрешен сингълтън тип?


person Dici    schedule 02.10.2015    source източник


Отговори (3)


Обърнете внимание, че отговарям, без да разбирам напълно какво се опитвате да направите, като правите списък с вътрешни сингълтони.

Обикновено типът сингълтън е достъпен чрез използване на SingletonName.type. Така че във вашия случай ще изглежда така:

abstract class Player(private var _health: Int = 0) {

  def health = _health

  def play(players: List[ReadOnlyPlayer.type]) = ()// how to denote inner type ReadOnlyPlayer ?

  def hit(damage: Int) = { _health = Math.max(0, _health - damage); this }

  def safeCopy = ReadOnlyPlayer

  final object ReadOnlyPlayer extends Player {
    override def health = _health

    // noop
    override def hit (damage: Int  ) = this
    override def play(players: List[ReadOnlyPlayer.type]) = ()
  }
}
person lloydmeta    schedule 02.10.2015
comment
Работи наистина. Бихте ли разширили малко по този въпрос? Не знаех, че това дори съществува - person Dici; 02.10.2015
comment
Това всъщност се компилира, но не работи за този дизайн, тъй като този тип е свързан с конкретното копие на Player, на което се извиква методът. Мисля, че ще се придържам към третото си решение. Няма ли нещо сравнимо с Outer#Inner, когато Inner е сингълтон вместо клас? - person Dici; 02.10.2015
comment
(1) Разбира се, сингълтън по дефиниция е единственият обект от своя тип, така че неговият подпис на типа не е неговото име, но трябва да бъде достъпен чрез .type, (2) Боя се, че не, тъй като, разширявайки се от (1 ) всеки сингълтон ще бъде специфичен за обекта и по този начин обвързан, както казвате, така че списъкът на вътрешни сингълтони няма много смисъл. Ако целта ви е да направите копия на плейъра само за четене, бих казал, че новият начин за копиране е най-добрият + идиоматичен. Ако трябва да избягвате много копия на плейъри само за четене, нещо подобно може да ви помогне (отново, само пример): gist.github.com/lloydmeta/e947a8f19b9edb585e6c - person lloydmeta; 02.10.2015
comment
Приемам отговора ви и публикувам окончателния си дизайн. Имате ли мнение по въпроса като подвъпрос? - person Dici; 02.10.2015
comment
Не мисля, че се различава много от това, което предлагаш, но ако предпочиташ някое от тях, ще ми е интересно да разбера защо - person Dici; 02.10.2015
comment
Между две от тях, мисля, че твоята ми харесва повече :D; единственото нещо, което бих направил, е да сложа отмените на noop в дефиницията на ReadOnlyPlayer, тъй като тези поведения са универсални за всички обекти от този тип. Като цяло обаче съм склонен да стоя настрана от променливи данни, тъй като не са идиоматични и донякъде трудни за разумно, но нямам опит в програмирането на игри, където чувам, че това е норма :) - person lloydmeta; 02.10.2015
comment
Добре благодаря. Аз също нямам предистория, просто търсех достатъчно сложен проблем с дизайна, за да изпробвам ООП аспекта на Scala, тъй като изследвах главно функционалния аспект. MMO-подобните игри са интересно предизвикателство в това отношение. Както и да е, благодаря отново! - person Dici; 02.10.2015

Както посочи @lloydme, това, което се опитвах да постигна, всъщност нямаше смисъл. Ето решението, което най-накрая избрах:

sealed trait ReadOnlyPlayer extends Player

abstract class Player(private var _health: Int = 0) {
    Check.isPositive(_health)

    def health = _health

    def play(players: List[ReadOnlyPlayer])

    def hit(damage: Int) = { _health = max(0, _health - damage); this }

    lazy val safeCopy: ReadOnlyPlayer = ReadOnlyCopy

    private object ReadOnlyCopy extends ReadOnlyPlayer {
        override def health = _health

        // noop
        override def hit (damage: Int) = this
        override def play(players: List[ReadOnlyPlayer]) = ()
    }
}
person Dici    schedule 02.10.2015

Списък с вложени обекти:

$ scala
Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_60).
Type in expressions to have them evaluated.
Type :help for more information.

scala> class X(id: Int) { object Y { def y = id } }
defined class X

scala> val xs = List.tabulate(10)(new X(_))
xs: List[X] = List(X@5f77d0f9, X@463fd068, X@895e367, X@1b266842, X@7a3793c7, X@42b3b079, X@651aed93, X@4dd6fd0a, X@bb9e6dc, X@5456afaa)

scala> val Ys = xs map (_.Y)
Ys: List[x$1.Y.type forSome { val x$1: X }] = List(X$Y$@43c67247, X$Y$@fac80, X$Y$@726386ed, X$Y$@649f2009, X$Y$@14bb2297, X$Y$@69adf72c, X$Y$@797501a, X$Y$@1a15b789, X$Y$@57f791c6, X$Y$@51650883)

scala> val ys = Ys map (_.y)
ys: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
person som-snytt    schedule 02.10.2015
comment
Благодаря за отговора ви, но не мисля, че това отговаря напълно на въпроса, който е да знам как да се позова на типа на вътрешния обект, така че да мога да предам списък от ReadOnlyPlayer сингълтони от различни външни Player инстанции. От другия отговор изглежда, че е невъзможно, защото всички тези типове са специфични за всеки екземпляр. Оттук и използването на черта, разширена от вътрешния обект - person Dici; 02.10.2015
comment
Всички в списъка с Ys имат различни външни екземпляри на X. Екзистенциалният тип изразява, че стойността x е неизвестна. - person som-snytt; 02.10.2015
comment
Да, забелязах това странно forSome :D Ще направя някои експерименти с него извън интерпретатора - person Dici; 02.10.2015