В недавней записи в блоге я показал несколько примеров того, как (и почему) можно обойти так называемое ограничение отсутствия наследования в Pony. Чтобы проиллюстрировать это, я показал, как мы можем создавать акторы, которые демонстрируют одно и то же поведение, создав трейт для описания этого поведения, например этот:

trait Container
  be enter_inv(ob: Any tag, from: Any tag)
  be leave_inv(ob: Any tag, to: Any tag)

Это позволило нам создать акторов, которые реализуют черту, чтобы с ними можно было обращаться одинаково, но нам все же пришлось немного уродовать, реализовав эти поведения:

be enter_inv(ob: Any tag, from: Any tag) =>
        // game object entered my inventory
        _out.print("object entered my inventory")
        _container.add(ob)
be leave_inv(ob: Any tag, to: Any tag) =>
        // game object left my inventory 
        _out.print("Object left my inventory")
        _container.remove(ob)

Это достигло нашей цели, но мы можем добиться большего. Здесь мы можем использовать трюк, который обеспечит реализацию по умолчанию для любых акторов, которые просто хотят реализовать инвентаризацию без каких-либо настроек.

Давайте взглянем на обновленную версию трейта Container:

trait Container
  be enter_inv(ob: Any tag, from: Any tag) =>
    invstorage().add(ob)
  be leave_inv(ob: Any tag, to: Any tag) =>
    invstorage().remove(ob)
  fun ref invstorage() : ActorStorage

Здесь есть пара интересных вещей. Во-первых, обратите внимание, что теперь есть реализация по умолчанию, связанная с первыми двумя поведениями. Если вы использовали трейты или примеси Scala в других языках, эта концепция должна показаться вам знакомой.

Каждая из этих реализаций фактически вызывает другой метод для одного и того же трейта, invstorage(). Это возвращает объект ActorStorage. С таким же успехом мы могли бы сделать ActorStorage еще одним трейтом, чтобы базовая реализация хранилища акторов могла изменяться без изменения трейта контейнера.

Теперь наш игрок, NPC, магазин и другие актеры могут получить реализацию контейнера по умолчанию, просто предоставив средства хранения инвентаря с помощью такого метода:

fun ref invstorage(): ActorStorage => _container

Это дает нам огромную мощность и гибкость. Вместо того, чтобы работать в сложной иерархии унаследованных классов, имея дело с родителями, братьями и сестрами и искусственными ограничениями, мы можем вместо этого решить, какие группы поведения мы хотим встроить в наших актеров.

Сочетание этой стратегии раскрытия класса данных со свойствами, субъектами, безопасностью типов Pony и системой субъектов дает разработчикам преимущество, которое нельзя упускать из виду.