M Mobile Shell:Reference:Language:Object Oriented Programming

Материал из mShellWiki
Перейти к: навигация, поиск

Объектно-ориентированное программирование (en)

[править заголовок, править ссылку на оригинал, править текст, править список подразделов, править список разделов]

Начиная с версии 3.00 m предлагает следующие возможности ООП.

  1. Классы с полями и (виртуальными) функциями.
  2. Одиночное наследование для создания иерархий классов.
  3. Создание экземпляров классов (объектов) и инициализация функциями-конструкторами.
  4. Полиморфизм через переопределение функций.
  5. Ссылки на экземпляры функций обеспечивают механизм обратного вызова для слушателей или обработчиков событий ("делегатов").

Классы и экземпляры классов (en)

[править заголовок, править ссылку на оригинал, править текст]

Класс — объявление связанных переменных (полей) и функций ("методов"), их обрабатывающих. Класс только описывает тип шаблона; данные создаются при создании экземпляра класса.

Классы могут основываться на существующих классах, наследуя все их поля и функции. Наследуемые функции могут быть переопределены, чтобы изменить поведение или функциональность, предлагаемые классом.

Класс объявляется ключевым словом class, за которым следует имя класса, его поля и его функции, заканчиваемые ключевым словом end:

class Sum
  s
  function add(x)
    s+=x
  end
  function res()
    return s
  end
end

Это объявляет класс Sum с полем s и двумя функциями: одна для добавления величины x к s, другая для получения суммы всех добавленных величин.

Класс всегда принадлежит модулю, в котором объявлен (он может быть встроенным модулем или основным скриптом). Поэтому класс уникально определяется модулем, в котором объявлен, и его именем внутри модуля: если класс Sum объявлен в модуле Aggreg, на него нужно ссылаться как Aggreg.Sum (или с соответствующим псевдонимом для Aggreg) в других модулях.

Класс должен быть объявлен перед тем, как может быть использован. Это означает, что если два класса ссылаются друг на друга, по крайней мере один должен быть объявлен с использованием forward и определен позже. В следующем примере или C, или D должен быть предварительно объявлен, поскольку класс C ссылается на класс D и наоборот.

class C forward // даем знать о C без подробностей

class D
  x: C // C может быть использован, но C.y не виден
end

class C // определяем C
  y
  function f(d: D)
    return y*d.x.y
  end
end
EBNF
__
ClassIdentifier = [ModulePrefix] Identifier .
ClassDeclaration = class Identifier
  ( forward | [ is ClassIdentifier ] ClassBody end ) .
ClassBody = { VariableDeclaration | FunctionDeclaration [';'] }  .

Объявление переменных (дополнение) (en)

[править заголовок, править ссылку на оригинал, править текст]

Переменная может быть объявлена так, чтобы ссылаться на экземпляры заданного класса (или быть null). Это позволяет непосредственно обращаться к полям и функциям экземпляра. Например, чтобы определить переменную x, ссылающуюся на экземпляр класса Sum, после первого присваивания (т.е. "определения") переменной x должно идти двоеточие и класс, на который она ссылается:

x:Sum=null

Переменная не может быть переопределена или определена не полностью: первое встречающееся присваивание в исходном коде должно определять ее тип, или она останется неопределенного типа (как и обычная переменная в m).

Всякий раз, когда выражение присваивается переменной определенного класса, присваиваемое значение проверяется. Если оно не является экземпляром соответствующего класса и не null, возникает исключение ExcNotSuchInstance:

x:Sum=null
a=23*7
x=a // a содержит число, не экземпляр класса Sum
→ возникло ExcNotSuchInstance

Объявление функций (дополнение) (en)

[править заголовок, править ссылку на оригинал, править текст]

Параметры функций похожи на локальные переменные и могут быть определены так, чтобы содержать экземпляры заданного класса. Например, функция для умножения экземпляра s класса Sum на параметр f и возвращения экземпляра с результатом может быть определена так:

function multiply(s: Sum, f): Sum
  ...
end

Как и в случае присвоения переменным определенного класса, присваиваемые параметрам значения проверяются при вызове функции, и возвращаемое значение проверяется при выходе из функции:

multiply("no sum", 3)
→ возникло ExcNotSuchInstance

function getsum(): Sum
  return "тоже не sum"
end
y=getsum()
→ возникло ExcNotSuchInstance

Поля класса (en)

[править заголовок, править ссылку на оригинал, править текст]

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

Обращение к полям класса происходит так:

  • Чтобы получить доступ к полу класса переменной-экземпляра, добавьте точку и имя поля:
s:Sum=...
s.s=0
  • Чтобы получить доступ к полу класса выражения без определенного типа, оно должно быть приведено к требуемому типу до доступа к любому из его полей. Возникнет ExcNotSuchInstance, если выражение не является экземпляром требуемого типа.
sums=[...]
print sums[3].(Sum)s // доступ к s требует приведения
  • Внутри функции класса поля доступны непосредственно, как показано в функциях add() и res() класса Sum: s можно использовать как любую другую переменную.
EBNF
__
InstanceSelector = '.' [ '(' ClassIdentifier ')' ]
  (FieldIdentifier |
   FunctionIdentifier '(' [ActualParameterList] ')') .

Функции класса (en)

[править заголовок, править ссылку на оригинал, править текст]

Большинство классов также содержат функции. Функции класса работают с экземпляром, тотчнее с его полями. Например, функция add в классе Sum увеличивает значение поля s экземпляра.

Внктри функции класса C экземпляр доступен через предопределенный параметр this. Использование this для доступа к полу экземпляра может пригодиться когда есть параметр с таким же именем:

class C
  x
  function setx(x)
    this.x=x // присвоить параметр x полю x
  end
end

Правила вызова функции класса такие же, как при доступе к полям класса:

  • Чтобы вызвать функцию класса переменной-экземпляра заданного типа, добавьте точку и имя функции:
s:Sum=...
s.add(3) // вызывает add для s
  • Чтобы вызвать функцию класса выражения без определенного типа, оно должно быть приведено к требуемому типу до вызова любой функции. Возникнет ExcNotSuchInstance, если выражение не является экземпляром требуемого типа.
sums=[...]
sums[3].(Sum)add(4) // требует приведения Sum
  • Внутри функции класса другая функция класса может быть вызвана непосредственно, без указания экземпляра.
class Sum
  ...
  function addtwice(x)
    // то же, что и this.add(x); this.add(x)
    add(x); add(x)
  end
end

Есть два способа определить функцию класса: либо внутри тела класса, либо объявления ее как forward и затем определить ее вне класса, добавив идентификатор класса к имени функции как префикс. Последнее может потребоваться когда классы и их функции ссылаются друг на друга.

class C forward // даем знать о C без подробностей

class D
  x: C // C может быть использован, но C.y не виден
  function mult(a) forward
end

class C // описываем C
  y
end

function D.mult(a) // C определен, теперь определяем D.mult
  return x.y*a
end

Далее показано как переопределить функции класса в подклассе.

EBNF
__
ClassFunctionDeclaration = function ClassName '.' Identifier
  FunctionBody .

Наследование, под- и суперклассы (en)

[править заголовок, править ссылку на оригинал, править текст]

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

Чтобы определить новый класс, расширяющий существующий класс, добавьте is и имя существующего класса после идентификатора нового класса:

class Avg is Sum
  n // счетчик элементов для вычисления среднего
  function add(x) // переопределение Sum.add()
    s+=x; n++
  end
  function res() // переопределение Sum.res()
    return s/n
  end
  function count()
    return n
  end
end

Это устанавливает следующую простую иерархию классов:

  • Avg является подклассом для Sum (каждый класс может иметь много подклассов).
  • Sum является суперклассом для Avg (каждый класс, за исключением .Instance, имеет ровно один суперкласс).

Поскольку, <ocde>Avg</code> расширяет Sum, он наследует его поле s и его функции add() и res(). Функции переопределены для осуществления усреднения, и расширенный класс также получил новую функцию count(), возвращающую количество элементов.

Функции суперклассов всегда доступны через встроенный параметр super. Он ссылается на текущий экземпляр, так же, как this, но определяется как экземпляр суперкласса при выборе того, какую функцию вызывать. Таким образом, переопределение функций в Avg может быть также записана как:

function add(x) // переопределение Sum.add()
  super.add(x); n++
end
function res() // переопределение Sum.res()
  return super.res()/n
end

Определяя функцию класса как forward, но не описывая ее, можно получить абстрактные классы. Например, интерфейсо-подобный абстрактный класс Aggregator может стать базовым классом для всех суммирующих классов Sum и Avg:

class Aggregator
  function add(x) forward
  function res() forward
end

class Sum is Aggregator
  ...

Создание экземпляров и конструкторы (en)

[править заголовок, править ссылку на оригинал, править текст]

Определение класса определяет функцию с таким же именем, функцию его создающую. Вызов этой функции обладает следующими эффектами:

  1. Создается новый экземпляр.
  2. Все поля класса (и его суперклассов) приравниваются null.
  3. Вызывается конструктор класса (функция init()) нового экземпляра, с параметрами, которые были переданы создающей функции.
  4. Возвращается новый экземпляр.
// создать новый экземпляр Sum и присвоить его x
x:Sum=Sum()
print x
→ .Sum(s=null)

При определении (переопределении) функции init() класса Sum его поле s может быть правильно инициализировано нулем:

class Sum
  s
  function init()
    s=0
  end
  function add(x)
  ...
end

x:Sum=Sum()
print x
→ .Sum(s=0)

Функция init() может принимать произвольные параметры, и их количество и тип могут различаться для каждого суперкласса:

class Person
  name
  height

  function init(name="unknown",height=180)
    this.name=name; this.height=height
  end
end
print Person()
→ .Person(name=unknown,height=180)
print Person("Lucky Luke")
→ .Person(name=Lucky Luke,height=180)
print Person("Joe",155)
→ .Person(name=Joe,height=155)

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

EBNF
__
InstanceCreation = ClassIdentifier '(' [ActualParameterList] ')' .

Базовый класс .Instance (en)

[править заголовок, править ссылку на оригинал, править текст]

Есть единственный базовый класс .Instance, который является неявным базовым классом для всех классов. Он объявлен как пустой класс с пустым конструктором:

class Instance
  function init() end
end

Поэтому два следующих объявления эквивалентны

class Sum
  ...
end
class Sum is .Instance
  ...
end

Даже хотя нет большой разницы, предпочтительней корректно создавать экземпляр .Instance:

x:.Instance=.Instance()

Ссылки на функции экземпляра (en)

[править заголовок, править ссылку на оригинал, править текст]

Ссылка на функцию экземпляра похожа на ссылку на функцию (см. раздел Ссылки на функции (en)), но всегда работает с заданным экземпляром, определяемым при получении ссылки.

Ссылки на функции экземпляров наиболее полезны для реализации обратного вызова в объектно-ориентированной среде, например, слушателей событий. Иногда их также называют "делегаты" или "делегирующие функции", поскольку ссылки на функции работают как делегаты экземпляра, посланные другому экземпляру.

Рассмотрим следующую передачу значений в массиве a функции-параметру c:

function consume(a, c)
  for v in a do
    c(v)
  end
end
function out(n)
  print n
end
consume([7,-8,9], &out) // ссылка на обычную функцию
→ 7
-8
9
s:Sum=Sum()
consume([7,-8,9], s.&add) // ссылка на функцию экземпляра
print s, s.res()
→ .Sum(s=8), 8

Второй вызов consume() вызывает s.add(v) при каждом вызове c(v).

EBNF
__
InstanceFunctionReference =
  '.' [ '(' ClassIdentifier ')' ] '&' Identifier .


© 2004-2009 airbit AG, CH-8008 Zürich
перевод от m-shell.ru

Наши друзья
Личные инструменты