Улучшенное наследование в CoffeeScript

от автора

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.

ссылка на оригинал статьи http://habrahabr.ru/post/190494/