CoffeeScript принёс в JS неплохую абстракцию классов, основанную на прототипах.
Реализовав известную модель наследования и дополнив её наследованием методов касса,
он позволяет легко строить иерархии классов, даже не зная о цепочках прототипов.
Но и эта модель может быть улучшена.
Приведенный способ не сможет полностью заменить существующий,
так как он использует свойство __proto__
, недоступное в некоторых реализациях JS.
Но он позволяет значительно расширить возможности наследования, работая при этом
поверх основной модели.
Кроме создания цепочки прототипов конструктров кофе использует следующий код
для наследования свойств класса:
for key of parent
child[key] = parent[key] if __hasProp_.call(parent, key)
То есть все свойства просто копируются. При таком наследовании теряется гибкость.
Простейший пример — при изменении метода предка не меняются методы в
наследованных классах. Также не наследуются неперечисляемые свойства.
Было бы гораздо лучше, если свойства класса тоже наследовались по цепочке
прототипов. Всё что нужно — после наследования класса средствами кофе удалить
всё унаследованное :) и установить child.__proto__ = parent
.
При таком наследовании у дочернего класса будут доступны все свойства предка,
которые так же можно переопределить. Но появляется возможность реализовывать
интересную функциональность, основанную на том что свойства принадлежат
прототипу, а не самому объекту.
Один из примеров — переменная экземпляра класса (class instance variable).
Object.defineProperty Parent, 'test',
get: -> @_test if @hasOwnProperty '_test'
set: (val) -> @_test = val
Parent.test = 1
Parent.test # => 1
Child.test # => undefined
Child.test = 2
Parent.test # => 1
Child.test # => 2
Этот подход к наследованию лежит в основе пакета coffee_classkit.
В этом пакете также реализованы методы работы с классами, взятые из Ruby:
include
, использующий append_features
, extend
, использующий extend_object
,
хуки inherited, included, extended
. Не стану здесь описывать их подробно:
они идиентичны аналогам из руби, только названия в кэмлкейсе.
Кто не не знаком с Ruby, надеюсь, без труда всё поймёт по исходнику,
тем более, что методы не больше шести строк.
Вся функциональность доступна с использованием обычного синтаксиса объявления класса:
classkit = require 'coffee_classkit'
class Child extends Parent
classkit.extendsWithProto @
classkit.include @, Mixin
Для удобвства в пакете есть класс, имеющий все эти методы в своем составе.
Унаследовав от него класс, можно использовать их в более явной и привычной форме:
class Example extends classkit.Module
@extendsWithProto()
@include Mixin
Также в пакет включен аналог ActiveSupport::Concern
:
class Mixin extends classkit.Module
@extendsWithProto().concern()
@includedBlock: ->
# выполняется в контексте базового класса
@instanceVariable 'test'
class @ClassMethods
someClassMethod: ->
someInstanceMethod: ->
class Base extends classkit.Module
@include Mixin
@someClassMethod()
(new Base).someInstanceMethod()
Больше простых примеров можно найти в тестах в репозитории.
С использованием описанных подходов, становится возможным писать модульный
объектно-ориентированный код, не врываясь в глобальное пространство имён.
Развёрнутый пример можно посмотреть в набросках проекта,
написанного с использованием CoffeeClasskit.
Автор: printercu