355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Хэл Фултон » Программирование на языке Ruby » Текст книги (страница 7)
Программирование на языке Ruby
  • Текст добавлен: 24 сентября 2016, 06:40

Текст книги "Программирование на языке Ruby"


Автор книги: Хэл Фултон



сообщить о нарушении

Текущая страница: 7 (всего у книги 56 страниц) [доступный отрывок для чтения: 20 страниц]

1.5.3. Предложение case в Ruby

Во всех современных языках есть та или иная форма многопутевого ветвления. В C/C++ и Java это предложение switch, а в Pascal – предложение case. Служат они одной и той же цели и функционируют примерно одинаково.

Предложение case в Ruby похоже, но при ближайшем рассмотрении оказывается настолько уникальным, что варианты ветвления, принятые в С и в Pascal, кажутся близкими родственниками. Точного аналога предложению case в Ruby нет ни в каком другом знакомом мне языке, поэтому оно заслуживает особого внимания.

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

• Для начала рассмотрим тривиальный пример. Выражение expression сравнивается со значением value, и, если они совпадают, выполняется некоторое действие. Ничего удивительного.

case expression

 when value

  некоторое действие

end

В Ruby для этой цели есть специальный оператор === (называется оператором отношения). Иногда его еще называют (не совсем правильно) оператором ветвящегося равенства. Неправильность в том, что он не всегда относится именно к проверке на равенство.

• Предыдущее предложение можно записать и так:

if value === expression

 некоторое действие

end

• Не путайте оператор отношения с оператором проверки на равенство (==). Они принципиально различны, хотя во многих случаях ведут себя одинаково. Оператор отношения определен по-разному в разных классах, а для данного класса его поведение может зависеть от типа переданного операнда.

• Не думайте, что проверяемое выражение – это объект, которому сравниваемое значение передается в качестве параметра. На самом деле как раз наоборот (мы это только что видели).

• Это подводит нас к наблюдению, что x===y означает вовсе не то же самое, что y===x! Иногда результат совпадает, но в общем случае оператор отношения не коммутативен. (Именно поэтому нам не нравится термин «оператор ветвящегося равенства» – ведь проверка на равенство всегда коммутативна.) Если перевернуть исходный пример, окажется, что следующий код ведет себя иначе:

case value

 when expression

  некоторое действие

end

• В качестве примера рассмотрим строку str и образец (регулярное выражение) pat, с которым эта строка успешно сопоставляется.

Выражение str =~ pat истинно, как в языке Perl. Поскольку Ruby определяет противоположную семантику оператора =~ в классе Regexp, можно также сказать, что выражение pat =~ str истинно. Следуя той же логике, мы обнаруживаем, что истинно и pat === str (исходя из того, как определен оператор === в классе Regexp). Однако выражение str === pat истинным не является. А значит, фрагмент

case "Hello"

 when /Hell/

  puts "Есть соответствие."

 else

  puts "Нет соответствия."

end

делает не то же самое, что фрагмент

case /Hell/

 when "Hello"

  puts "Есть соответствие."

 else

  puts "Нет соответствия."

end

Если это вас смущает, просто постарайтесь запомнить. А если не смущает, тем лучше!

• Программисты, привыкшие к С, могут быть озадачены отсутствием предложений break в ветвях case. Такое использование break в Ruby необязательно (и недопустимо). Связано это с тем, что «проваливание» редко бывает желательно при многопутевом ветвлении. В конце каждой ветви when имеется неявный переход на конец предложения case. В этом отношении Ruby напоминает Pascal.

• Значения в каждой ветви case могут быть произвольными. На типы никаких ограничений не налагается. Они не обязаны быть константами; допускаются и переменные, и сложные выражения. Кроме того, в ветви может проверяться попадание в диапазон.

• В ветвях case могут находиться пустые действия (пустые предложения). Значения в разных ветвях не обязательно должны быть уникальными – допускаются перекрытия, например:

case x

 when 0

 when 1..5

  puts "Вторая ветвь"

 when 5..10

  puts "Третья ветвь"

 else

  puts "Четвертая ветвь"

end

Если x принимает значение 0, ничего не делается. Для значения 5 печатается строка «Вторая ветвь» – несмотря на то что 5 удовлетворяет и условию в третьей ветви.

• Перекрытие ветвей допускается потому, что они вычисляются в строгом порядке и выполняется закорачивание. Иными словами, если вычисление выражения в какой-то ветви оказалось успешным, то следующие ветви не вычисляются. Поэтому не стоит помещать в ветви case выражения, в которых вызываются методы с побочными эффектами. (Впрочем, такие вызовы в любом случае сомнительны). Имейте также в виду, что такое поведение может замаскировать ошибки, которые произошли бы во время выполнения, если бы выражение вычислялось. Например:

case x

 when 1..10

  puts "Первая ветвь"

 when foobar() # Возможен побочный эффект?

  puts "Вторая ветвь"

 when 5/0      # Деление на нуль!

  puts "Третья ветвь"

 else

  puts "Четвертая ветвь"

end

Если x находится в диапазоне от 1 до 10, то метод foobar() не вызывается, а выражение 5/0 (которое, естественно, привело бы к ошибке) не вычисляется.

1.5.4. Рубизмы и идиомы

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

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

• С помощью ключевого слова alias можно давать глобальным переменным и методам альтернативные имена (синонимы).

• Пронумерованные глобальные переменные $1, $2, $3 и т.д. не могут иметь синонимов.

• Мы не рекомендуем использовать «специальные переменные» $=, $_, $/ и им подобные. Иногда они позволяют написать более компактный код, но при этом он не становится более понятным. Поэтому в данной книге мы прибегаем к ним очень редко, что и вам рекомендуем.

• Не путайте операторы диапазона .. и ... – первый включает верхнюю границу, второй исключает. Так, диапазон 5..10 включает число 10, а диапазон 5...10 – нет.

• С диапазонами связана одна мелкая деталь, которая может вызвать путаницу. Если дан диапазон m..n, то метод end вернет конечную его точку n, равно как и его синоним last. Но те же методы возвращают значение n и для диапазона m...n, хотя n не включается в него. Чтобы различить эти две ситуации, предоставляется метод end_excluded?.

• Не путайте диапазоны с массивами. Следующие два присваивания абсолютно различны:

x = 1..5

x = [1, 2, 3, 4, 5]

Однако есть удобный метод to_a для преобразования диапазона в массив. (Во многих других типах тоже есть такой метод.)

• Часто бывает необходимо присвоить переменной значение лишь в том случае, когда у нее еще нет никакого значения. Поскольку «неприсвоенная» переменная имеет значение nil, можно решить эту задачу так: x = x || 5 или сокращенно x ||= 5. Имейте в виду, что значение false, а равно и nil, будет при этом перезаписано.

• В большинстве языков для обмена значений двух переменных нужна дополнительная временная переменная. В Ruby наличие механизма множественного присваивания делает ее излишней: выражение x, y = y, x обменивает значения x и y.

• Четко отличайте класс от экземпляра. Например, у переменной класса @@foobar областью видимости является весь класс, а переменная экземпляра @foobar заново создается в каждом объекте класса.

• Аналогично метод класса ассоциирован с тем классом, в котором определен; он не принадлежит никакому конкретному объекту и не может вызываться от имени объекта. При вызове метода класса указывается имя класса, а при вызове метода экземпляра – имя объекта.

• В публикациях, посвященных Ruby, часто для обозначения метода экземпляра применяют решеточную нотацию. Например, мы пишем File.chmod, чтобы обозначить метод chmod класса File, и File#chmod для обозначения метода экземпляра с таким же именем. Эта нотация не является частью синтаксиса Ruby. Мы старались не пользоваться ей в этой книге.

• В Ruby константы не являются истинно неизменными. Их нельзя изменять в теле методов экземпляра, но из других мест это вполне возможно.

• Ключевое слово yield пришло из языка CLU и некоторым программистам может быть непонятно. Оно используется внутри итератора, чтобы передать управление блоку, с которым итератор был вызван. В данном случае yield не означает, что нужно получить результат или вернуть значение. Скорее, речь идет о том, чтобы уступить процессор для работы.

• Составные операторы присваивания +=, -= и пр. – это не методы (собственно, это даже не операторы). Это всего лишь «синтаксическая глазурь» или сокращенная форма записи более длинной формы. Поэтому x += y значит в точности то же самое, что x = x + y. Если оператор + перегружен, то оператор += «автоматически» учитывает новую семантику.

• Из-за того, как определены составные операторы присваивания, их нельзя использовать для инициализации переменных. Если первое обращение к переменной x выглядит как x += 1, возникнет ошибка. Это интуитивно понятно для программистов, если только они не привыкли к языку, в котором переменные автоматически инициализируются нулем или пустым значением.

• Такое поведение можно в некотором смысле обойти. Можно определить операторы для объекта nil, так что в случае, когда начальное значение переменной равно nil, мы получим желаемый результат. Так, метод nil.+, приведенный ниже, позволит инициализировать объект типа string или Fixnum, для чего достаточно вернуть аргумент other. Таким образом, nil + other будет равно other.

def nil.+(other)

 other

end

Мы привели этот код для иллюстрации возможностей Ruby, но стоит ли поступать так на практике, оставляем на усмотрение читателя.

• Уместно будет напомнить, что Class – это объект, a Object – это класс. Мы попытаемся прояснить этот вопрос в следующей главе, а пока просто повторяйте это как мантру.

• Некоторые операторы нельзя перегружать, потому что они встроены в сам язык, а не реализованы в виде методов. К таковым относятся =, .., ..., and, or, not, &&, ||, !, != и !~. Кроме того, нельзя перегружать составные операторы присваивания (+=, -= и т.д.). Это не методы и, пожалуй, даже не вполне операторы.

• Имейте в виду, что хотя оператор присваивания перегружать нельзя, тем не менее возможно написать метод экземпляра с именем fоо= (тогда станет допустимым предложение x.foo = 5). Можете рассматривать знак равенства как суффикс.

• Напомним: «голый» оператор разрешения области видимости подразумевает наличие Object перед собой, то есть ::Foo – то же самое, что Objеct::Foo.

• Как уже говорилось, fail – синоним raise.

• Напомним, что определения в Ruby исполняются. Вследствие динамической природы языка можно, например, определить два метода совершенно по-разному в зависимости от значения признака, проверяемого во время выполнения.

• Напомним, что конструкция for (for x in а) на самом деле вызывает итератор each. Любой класс, в котором такой итератор определен, можно обходить в цикле for.

• Не забывайте, что метод, определенный на верхнем уровне, добавляется в модуль Kernel и, следовательно, становится членом класса Object.

• Методы установки (например, fоо=) должны вызываться от имени объекта, иначе анализатор решит, что речь идет о присваивании переменной с таким именем.

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

• Ключевое слово retry применяется также при обработке исключений. Не путайте два этих вида использования.

• Метод объекта initialize всегда является закрытым.

• Когда итератор заканчивается левой фигурной скобкой (или словом end) и возвращает значение, это значение можно использовать для вызова последующих методов, например:

squares = [1,2,3,4,5].collect do |x| x**2 end.reverse

# squares теперь равно [25,16,9,4,1]

• В конце программы на Ruby часто можно встретить идиому

if $0 == __FILE__

Таким образом проверяется, исполняется ли файл как автономный кусок кода (true) или как дополнительный, например библиотека (false). Типичное применение – поместить некую «главную программу» (обычно с тестовым кодом) в конец библиотеки.

• Обычное наследование (порождение подкласса) обозначается символом <:

class Dog < Animal

 # ...

end

Однако для создания синглетного класса (анонимного класса, который расширяет единственный экземпляр) применяется символ <<:

class << platypus

 # ...

end

• При передаче блока итератору есть тонкое различие между фигурными скобками ({}) и операторными скобками do-end. Связано оно с приоритетом:

mymethod param1, foobar do ... end

# Здесь do-end связано с mymethod.

mymethod param1, foobar { ... }

# А здесь {} связано с именем foobar, предполагается, что это метод.

• Традиционно в Ruby однострочные блоки заключают в фигурные скобки, а многострочные – в скобки do-end, например:

my_array.each { |x| puts x }

my_array.each do |x|

 print x

 if x % 2 == 0

  puts " четно."

 else

  puts " нечетно."

 end

end

Это необязательно и в некоторых случаях даже нежелательно.

• Помните, что строки (strings) в некотором смысле двулики: их можно рассматривать как последовательность символов или как последовательность строчек (lines). Кому-то покажется удивительным, что итератор each оперирует строками (здесь под «строкой» понимается группа символов, завершающаяся разделителем записей, который по умолчанию равен символу новой строки). У each есть синоним each_line. Если вы хотите перебирать символы, можете воспользоваться итератором each_byte. Итератор sort также оперирует строками. Для строк (strings) не существует итератора each_index из-за возникающей неоднозначности. Действительно, хотим ли мы обрабатывать строку посимвольно или построчно? Все это со временем войдет в привычку.

• Замыкание (closure) запоминает контекст, в котором было создано. Один из способов создать замыкание – использование объекта Proc. Например:

def power(exponent)

 proc {|base| base**exponent}

end

square = power(2)

cube = power(3)

a = square.call(11) # Результат равен 121.

b = square.call(5)  # Результат равен 25.

с = cube.call(6)    # Результат равен 216.

d = cube.call(8)    # Результат равен 512.

Обратите внимание, что замыкание «знает» значение показателя степени, переданное ему в момент создания.

• Однако помните: в замыкании используется переменная, определенная во внешней области видимости (что вполне допустимо). Это свойство может оказаться полезным, но приведем пример неправильного использования:

$exponent = 0

def power

 proc {|base| base**$exponent}

end

$exponent = 2

square = power

$exponent = 3

cube = power

a = square.call(11) # Неверно! Результат равен 1331.

b = square.call(5) # Неверно! Результат равен 125.

# Оба результата неверны, поскольку используется ТЕКУЩЕЕ

# значение $exponent. Так было бы даже в том случае, когда

# используется локальная переменная, покинувшая область

# видимости (например, с помощью define_method).

с = cube.call(6) # Результат равен 216.

d = cube.call(8) # Результат равен 512.

• Напоследок рассмотрим несколько искусственный пример. Внутри блока итератора times создается новый контекст, так что x – локальная переменная. Переменная closure уже определена на верхнем уровне, поэтому для блока она не будет локальной.

closure = nil # Определим замыкание, чтобы его имя было известно.

1.times do    # Создаем новый контекст.

 x = 5        # Переменная x локальная в этом блоке,

 closure = Proc.new { puts "В замыкании, x = #{x}" }

end

x = 1

# Определяем x на верхнем уровне.

closure.call # Печатается: В замыкании, x = 5

Обратите внимание, что переменная x, которой присвоено значение 1, – это новая переменная, определенная на верхнем уровне. Она не совпадает с одноименной переменной, определенной внутри блока. Замыкание печатает 5, так как запоминает контекст своего создания, в котором была определена переменная x со значением 5.

• Переменные с именами, начинающимися с одного символа @, определенные внутри класса, – это, вообще говоря, переменные экземпляра. Однако если они определены вне любого метода, то становятся переменными экземпляра класса. (Это несколько противоречит общепринятой терминологии ООП, в которой «экземпляр класса» – то же самое, что и «экземпляр>> или «объект».) Пример:

class Myclass

 @x = 1  # Переменная экземпляра класса.

 @y = 2  # Еще одна.

 def mymethod

  @x = 3 # Переменная экземпляра.

  # Заметим, что в этой точке @y недоступна.

 end

end

Переменная экземпляра класса (@y в предыдущем примере – в действительности атрибут объекта класса Myclass, являющегося экземпляром класса Class. (Напомним, что Class – это объект, a Object – это класс.) На переменные экземпляра класса нельзя ссылаться из методов экземпляра и, вообще говоря, они не очень полезны.

attr, attr_reader, attr_writer и attr_accessor – сокращенная запись для определения методов чтения и установки атрибутов. В качестве аргументов они принимают символы (экземпляры класса Symbol).

• Присваивание переменной, имя которой содержит оператор разрешения области видимости, недопустимо. Например, Math::Pi = 3.2 – ошибка.

1.5.5. Ориентация на выражения и прочие вопросы

В Ruby выражения важны почти так же, как предложения. Для программиста на С это звучит знакомо, а для программиста на Pascal – откровенная нелепость. Но Ruby ориентирован на выражения даже в большей степени, чем С.

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

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

x = y = z = 0  # Все переменные сейчас равны 0.

а = b = с = [] # Опасно! a, b и с ссылаются

               # на ОДИН И ТОТ ЖЕ пустой массив.

x = 5

y = x += 2 # Сейчас x и у равны 7.

Напомним однако, что значения типа Fixnum и им подобные хранятся непосредственно, а не как ссылки на объекты.

• Многие управляющие конструкции возвращают значения, в частности if, unless и case. Следующий код корректен; он показывает, что при принятии решения ветви могут быть выражениями, а не полноценными предложениями.

а = 5

x = if а < 8 then 6 else 7 end # x равно 6.

y= if a<8 # y тоже равно 6;

 6        # предложение if может располагаться

else      # на одной строке

 7        # или на нескольких.

end

# unless тоже работает; z присваивается значение 4.

z = unless x == y then 3 else 4 end

t = case a # t получает

 when 0..3 # значение

  "low"    # medium,

 when 4..6

  "medium"

 else

  "high"

end

Здесь мы сделали такие отступы, будто case является присваиванием. Мы воспринимаем такую запись спокойно, хотя вам она может не понравиться.

• Отметим, что циклы while и until, напротив, не возвращают никаких полезных значений; обычно их значением является nil:

i = 0

x = while (i < 5) # x равно nil.

 puts i+=1

end

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

x = 6

y = x == 5 ? 0 : 1 #y равно 1.

x == 5 ? puts("Привет") : puts("Пока") # Печатается: "Пока"

• Предложение return в конце метода можно опускать. Метод всегда возвращает значение последнего вычисленного выражения, в каком бы месте это вычисление ни происходило.

• Когда итератор вызывается с блоком, последнее выражение, вычисленное в блоке, возвращается в качестве значения блока. Если при этом в теле итератора есть предложение x = yield, то x будет присвоено это значение.

 Регулярные выражения. Напомним, что после регулярного выражения можно написать модификатор многострочности /m, и в этом случае точка (.) будет сопоставляться с символом новой строки.

 Регулярные выражения. Опасайтесь соответствий нулевой длины. Если все элементы регулярного выражения необязательны, то такому образцу будет соответствовать «ничто», причем соответствие всегда будет найдено в начале строки. Это типичная ошибка, особенно часто ее допускают новички.

1.6. Жаргон Ruby

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

В Ruby термин «атрибут» носит неофициальный характер. Можно считать, что атрибут – это переменная экземпляра, которая раскрывается внешнему миру с помощью одного из методов семейства attr. Но тут нет полной определенности: могут существовать методы foo и foo=, не соответствующие переменной @foo, как можно было бы ожидать. И, конечно, не все переменные экземпляра считаются атрибутами. Как обычно, нужно придерживаться здравого смысла.

Атрибуты в Ruby можно подразделить на методы чтения (reader) и установки (writer). Метод доступа, или акцессор (accessor), является одновременно методом чтения и установки. Это согласуется с названием метода attr_accessor, но противоречит принятой в других сообществах семантике, согласно которой акцессор дает доступ только для чтения.

Оператор === имеется только в Ruby (насколько мне известно). Обыкновенно он называется оператором ветвящегося равенства (case equality operator), поскольку неявно используется в предложениях case. Но это название, как я уже говорил, не вполне точно, потому что речь идет не только о «равенстве». В данной книге я часто употребляю термин «оператор отношения» (relationship operator). Изобрел его не я, но проследить происхождение мне не удалось, к тому же он употребляется нечасто. Жаргонное название – «оператор тройного равенства» (threequal operator) или просто «три равно».

Оператор <=>, наверное, лучше всего называть оператором сравнения. На жаргоне его называют космическим оператором (spaceship operator), поскольку он напоминает летающую тарелку – так ее изображали в старых видеоиграх.

Термин «поэтический режим» (poetry mode) подчеркивает, что можно опускать ненужные знаки препинания и лексемы (насмешливый намек на отношение поэтов к пунктуации на протяжении последних шестидесяти лет). Поэтический режим также часто означает «опускание скобок при вызове метода».

some_method(1, 2, 3) # Избыточные скобки.

some_method 1, 2, 3  # "Поэтический режим".

Но мне этот принцип представляется более общим. Например, когда хэш передается в качестве последнего или единственного параметра, можно опускать фигурные скобки. В конце строки можно не ставить точку с запятой (а потому никто этого и не делает). В большинстве случаев разрешается опускать ключевое слово then в предложениях if и case.

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

def my_method(a, b, с) # Можно и так: def my_method a, b, с

 # ...

end

Стоит отметить, что в некоторых случаях сложность грамматики Ruby приводит к сбоям анализатора. Во вложенных вызовах методов скобки для ясности лучше оставлять. Иногда в текущей версии Ruby выводятся предупреждения:

def alpha(x)

 x*2

end

def beta(y)

 y*3

end

gamma = 5

delta = alpha beta gamma # 30 – то же, что alpha(beta(gamma))

# Выдается предупреждение:

# warning: parenthesize argument(s) for future version

# предупреждение: заключайте аргумент(ы) в скобки для совместимости с

# с будущими версиями

Термин duck typing («утиная типизация» или просто «утипизация»), насколько я знаю, принадлежит Дейву Томасу (Dave Thomas) и восходит к поговорке: «если кто-то выглядит как утка, ковыляет как утка и крякает как утка, то, наверное, это и есть утка». Точный смысл термина «утипизация» – тема для дискуссий, но мне кажется, что это намек на тенденцию Ruby заботиться не столько о точном классе объекта, сколько о том, какие методы для него можно вызывать и какие операции над ним можно выполнять. В Ruby мы редко пользуемся методом is_a? или kind_of, зато гораздо чаще прибегаем к методу respond_to?. Обычное дело – просто передать объект методу, зная, что при неправильном использовании будет возбуждено исключение. Так оно рано или поздно и случается.

Унарную звездочку, которая служит для расширения массива, можно было бы назвать оператором расширения массива, но не думаю, что кто-нибудь слышал такое выражение. В хакерских кругах ходят словечки «звездочка» (star) и «расплющивание» (splat), а также производные определения – «расплющенный» (splatted) и «сплющенный» (unsplatted). Дэвид Алан Блэк придумал остроумное название «унарный оператор линеаризации» (unary unarray operator)[6]6
  Прошу читателя извинить меня за это нагромождение выдуманных слов, но жаргон вряд ли следует переводить академически правильным языком. Хочется надеяться, что это все же лучше, чем кальки английских слов. (Прим. перев.)


[Закрыть]
.

Термин синглет (singleton) многие считают перегруженным. Это вполне обычное английское слово, означающее вещь, существующую в единственном экземпляре. Пока мы используем его в качестве модификатора, никакой путаницы не возникает.

Но Singleton (Одиночка) – это еще и хорошо известный паттерн проектирования, относящийся к классу, для которого может существовать лишь один объект. В Ruby для реализации этого паттерна имеется библиотека singleton.

Синглетный класс (singleton class) в Ruby подобная классу сущность, методы которой хранятся на уровне объекта, а не класса. Пожалуй, это не «настоящий класс», потому что его нельзя инстанцировать. Ниже приведен пример открытия синглетного класса для строкового объекта с последующим добавлением метода:

str = "hello"

class << str    # Альтернатива:

 def hyphenated # def str.hyphenated

  self.split("").join("-")

 end

end

str.hyphenated # "h-e-l-l-o"

Кто-то предложил использовать термин eigenclass (класс в себе) производное от немецкого слова eigen (свой собственный), коррелирующее с термином «собственное значение» (eigenvalue), применяемым в математике и физике. Остроумно, но в сообществе не прижилось и некоторым активно не нравится.

Вернемся к предыдущему примеру. Поскольку метод hyphenate не существует ни в каком-либо другом объекте, ни в классе, это синглетный метод данного объекта. Это не вызывает неоднозначности. Иногда сам объект называется синглетным, поскольку он единственный в своем роде – больше ни у кого такого метода нет.

Однако вспомним, что в Ruby сам класс является объектом. Поэтому мы можем добавить метод в синглетный класс класса, и этот метод будет уникален для объекта, который – по чистой случайности – оказался классом. Пример:

class MyClass

 class << self # Альтернатива: def self.hello

  def hello    # или: def MyClass.hello

   puts "Привет от #{self}!"

  end

 end

end

Поэтому необязательно создавать объект класса MyClass для вызова этого метода.

MyClass.hello # Привет от MyClass!

Впрочем, вы, наверное, заметили, что это не что иное, как метод класса в Ruby. Иными словами, метод класса синглетный метод объекта-класса. Можно также сказать, что это синглетный метод, определенный для объекта, который случайно оказался классом.

Осталась еще парочка терминов. Переменная класса – это, разумеется, то, имя чего начинается с двух символов @. Возможно, название неудачно из-за нетривиального поведения относительно наследования. Переменная экземпляра класса – нечто совсем иное. Это обычная переменная экземпляра; только объект, которому она принадлежит, является классом. Дополнительную информацию по этой теме вы найдете в главе 11.


    Ваша оценка произведения:

Популярные книги за неделю