Skip to content

Latest commit

 

History

History
203 lines (142 loc) · 12.1 KB

3. Классы в CoffeeScript.md

File metadata and controls

203 lines (142 loc) · 12.1 KB

Классы в CoffeeScript

На приверженцев объектно-ориентированных языков классы в JavaScript действуют как чеснок на Дракулу (хотя вряд ли вы стали бы читать эту книгу, если бы придерживались таких взглядов). Однако классы в JavaScript столь же полезны, сколь и в других языках, а CoffeeScript сильно упрощает работу с ними.

За кулисами CoffeeScript использует JavaScript-прототипы для создания классов, добавляя немного синтаксического сахара для наследования статичных свойств и определении контекста исполнения. Всё, что требуется написать разработчику — это следующий код:

class Animal

В приведенном выше примере Animal - это название класса и полученной переменной, которую в дальнейшем можно использовать для создания экземпляров объекта. CoffeeScript использует функции-конструкторы, что позволяет создавать объекты с помощью оператора new.

animal = new Animal

Создать конструктор (функцию, вызываемую при создании экземпляра объекта) очень просто - создайте функцию с именем constructor, аналогично методу initialize в Ruby или __init__ в Python:

class Animal
  constructor: (name) ->
    @name = name

Более того, CoffeeScript предоставляет элегантную реализацию распространенного способа установки исходных значений полей объекта. Префикс @ автоматически превращает переменную в свойство экземпляра класса. На самом деле, этот синтаксический сахар работает даже для обыкновенных функций за пределами класса. Приведённый ниже пример эквивалентен предыдущему:

class Animal
  constructor: (@name) ->

Таким образом переменные, передаваемые при создании объекта, передаются в конструктор:

animal = new Animal("Parrot")
alert "Animal is a #{animal.name}

Свойства объекта

Добавление классу дополнительных свойств является довольно простым: оно полностью совпадает с добавление свойств в обычный объект. Нужно только убедиться, что свойства добавлены в тело класса.

class Animal
  price: 5

  sell: (customer) ->

animal = new Animal
animal.sell(new Customer)

В JavaScript часто приходится прибегать к смене контекста. В главе «Синтаксис» мы уже рассказывали о том, как привязать значение this к конкретному контексту, используя «жирную» стрелку: =>. Используя её мы можем быть уверены, что функция будет выполняться в том контексте, в котором была создана, независимо от контекста, в котором она вызывается. CoffeeScript расширяет поддержку «жирной» стрелки для классов, и поэтому используя её для метода класса можно не беспокоиться о контексте: this всегда будет являться текущим экземпляром объекта.

class Animal
  price: 5

  sell: =>
    alert "Give me #{@price} shillings!"

animal = new Animal
$("#sell").click(animal.sell)

В примере выше видно, что этот приём особенно полезен в коллбэках событий. Если бы мы использовали тонкую стрелку, метод sell() был бы вызван в контексте элемента #sell. В нашем же случае используется корректный контекст и this.price равно 5.

Статичные свойства

Как насчёт статичных свойств? В CoffeeScript this внутри класса ссылается на сам объект класса. Другими словами, вы можете задать статичные свойства класса, установив их непосредственно через this.

class Animal
  this.find = (name) ->      

Animal.find("Parrot")

Вы уже знаете, что в CoffeeScript существует аналог this - символ @, который позволяет нам писать статические свойства еще более кратко:

class Animal
  @find: (name) ->

Animal.find("Parrot")

Наследование и «Super»

Реализация классов не была бы полной без наследования в какой-либо форме, и CoffeeScript, естественно, реализовал его. Вы можете унаследовать класс от другого, используя ключевое слово extends. В примере ниже Parrot наследуется от Animal, получая все его свойства и методы.

class Animal
  constructor: (@name) ->

  alive: ->
    false

class Parrot extends Animal
  constructor: ->
    super("Parrot")

  dead: ->
    not @alive()

В примере выше, как вы могли заметить, мы используем ключевое слово super(). "За кулисами" происходит вызов функции прототипа родительского класса, выполненный в текущем контексте. В данном случае это будет Parrot.__super__.constructor.call(this, "Parrot");. На практике, эффект будет точно такой же, как при вызове super в Ruby или Python, вызовется переопределенная унаследованная функция.

Если вы не переопределили конструктор, по умолчанию CoffeeScript вызовет конструктор родителя в момент создания экземпляра класса.

CoffeeScript использует прототипное наследование, чтобы автоматически унаследовать все свойства экземпляра класса. Это гарантирует динамичность классов. Даже если вы добавите свойства в родительский класс после того как был создан наследник, свойство распространится на все унаследованные классы.

class Animal
  constructor: (@name) ->

class Parrot extends Animal

Animal::rip = true

parrot = new Parrot("Macaw")
alert("This parrot is no more") if parrot.rip

Хотя стоит отметить, что статические свойства копируются в дочерние классы, вместо наследования через прототипы, как это делается со свойствами экземпляров классов. Так получилось из-за деталей реализации прототипной архитектуры в Javascript, и обойти эту проблему довольно трудно.

Примеси

CoffeeScript не поддерживает примеси из коробки, но вы можете довольно легко реализовать их самостоятельно. Например, две функции, extend() и include(), которые добавляют свойства классу и экземплярам класса соответственно.

extend = (obj, mixin) ->
  obj[name] = method for name, method of mixin        
  obj

include = (klass, mixin) ->
  extend klass.prototype, mixin

# Usage
include Parrot,
  isDeceased: true

(new Parrot).isDeceased

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

Расширение классов

Применение примесей выглядят довольно изящно, но они не достаточно объектно-ориентированы. Но мы можем интегрировать примеси в CoffeeScript классы. Попробуем определить класс, именуемый Module, от которого мы сможем унаследоваться для поддержки примесей. Module будет иметь два статических метода, @extend() и @include(), которые будут использоваться для расширения классов статическими свойствами и свойствами экземпляров класса соответственно.

moduleKeywords = ['extended', 'included']

class Module
  @extend: (obj) ->
    for key, value of obj when key not in moduleKeywords
      @[key] = value

    obj.extended?.apply(@)
    this

  @include: (obj) ->
    for key, value of obj when key not in moduleKeywords
      # Assign properties to the prototype
      @::[key] = value

    obj.included?.apply(@)
    this

Придется немного повозиться с переменной moduleKeywords, чтобы гарантировать поддержку функции обратного вызова после расширения класса. Посмотрим на наш класс Module в действии:

classProperties = 
  find: (id) ->
  create: (attrs) ->

instanceProperties =
  save: -> 

class User extends Module
  @extend classProperties
  @include instanceProperties

# Usage:
user = User.find(1)

user = new User
user.save()

Как вы можете видеть, мы добавили несколько статичных свойств, find() и create() в класс User, а также метод экземпляра save(). Поскольку у нас есть функции обратного вызова, которые выполняются, когда модули расширяются, мы можете сократить процесс добавления статических свойств и свойства экземпляра:

ORM = 
  find: (id) ->
  create: (attrs) ->
  extended: ->
    @include
      save: -> 

class User extends Module
  @extend ORM

Очень просто и элегантно!